DSP register inference rules for Nexus plus inference plugin enhancements

Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
diff --git a/dsp_ff-plugin/dsp_ff.cc b/dsp_ff-plugin/dsp_ff.cc
index 6daf965..11555f8 100644
--- a/dsp_ff-plugin/dsp_ff.cc
+++ b/dsp_ff-plugin/dsp_ff.cc
@@ -84,6 +84,9 @@
     struct DspPortType {
         RTLIL::IdString name;
 
+        /// Range of port pins that have FFs (low to high, inclusive)
+        std::pair<int, int> bits;
+
         /// A dict of associated cell ports indexed by their function (like "clk, "rst")
         /// along with the default value to connect when unused.
         dict<RTLIL::IdString, std::pair<RTLIL::IdString, RTLIL::Const>> assoc;
@@ -192,6 +195,33 @@
             return vec;
         };
 
+        // Parses port name as "<name>[<hi>:<lo>]" or just "<name>"
+        auto parsePortName = [&](const std::string& str) {
+            const std::regex expr ("^(.*)\\[([0-9]+):([0-9]+)\\]");
+            std::smatch      match;
+
+            std::tuple<std::string, int, int> data;
+            auto res = std::regex_match(str, match, expr);
+            if (res) {
+                data = std::make_tuple(
+                    std::string(match[1]),
+                    std::stoi(match[2]),
+                    std::stoi(match[3])
+                );
+    
+                if ((std::get<2>(data) > std::get<1>(data)) ||
+                    std::get<2>(data) < 0 || std::get<1>(data) < 0)
+                {
+                    log_error(" invalid port spec: '%s'\n", str.c_str());
+                }
+            }
+            else {
+                data = std::make_tuple(str, -1, -1);
+            }
+
+            return data;
+        };
+
         std::ifstream file (a_FileName);
         std::string line;
 
@@ -203,8 +233,10 @@
         std::vector<DspType>    dspTypes;
         std::vector<FlopType>   flopTypes;
 
+        std::vector<RTLIL::IdString> dspAliases;
+
         std::vector<std::string> tok;
- 
+
         // Parse the file
         while (1) {
 
@@ -229,7 +261,7 @@
 
             // DSP section
             if (fields[0] == "dsp") {
-                if (fields.size() != 2) {
+                if (fields.size() < 2) {
                     log_error(" syntax error: '%s'\n", line.c_str());
                 }
                 if (!tok.empty()) {
@@ -239,6 +271,11 @@
 
                 dspTypes.resize(dspTypes.size() + 1);\
                 dspTypes.back().name = RTLIL::escape_id(fields[1]);
+
+                dspAliases.clear();
+                for (size_t i=2; i<fields.size(); ++i) {
+                    dspAliases.push_back(RTLIL::escape_id(fields[i]));
+                }
             }
             else if (fields[0] == "enddsp") {
                 if (fields.size() != 1) {
@@ -247,7 +284,13 @@
                 if (tok.size() != 1 || tok.back() != "dsp") {
                     log_error(" unexpected keyword '%s'\n", fields[0].c_str());
                 }
-                tok.pop_back(); 
+                tok.pop_back();
+
+                const auto dspType = dspTypes.back();
+                for (const auto& alias : dspAliases) {
+                    dspTypes.push_back(dspType);
+                    dspTypes.back().name = alias;
+                }
             }
 
             // DSP port section
@@ -260,12 +303,18 @@
                 }
                 tok.push_back(fields[0]);
 
+                auto spec = parsePortName(fields[1]);
+
                 auto& ports = dspTypes.back().ports;
                 ports.resize(ports.size() + 1);
-                ports.back().name = RTLIL::escape_id(fields[1]);
-                ports.back().assoc.insert(std::make_pair(RTLIL::escape_id("clk"), std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
-                ports.back().assoc.insert(std::make_pair(RTLIL::escape_id("rst"), std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
-                ports.back().assoc.insert(std::make_pair(RTLIL::escape_id("ena"), std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
+                ports.back().name = RTLIL::escape_id(std::get<0>(spec));
+                ports.back().bits = std::make_pair(std::get<2>(spec), std::get<1>(spec));
+                ports.back().assoc.insert(std::make_pair(RTLIL::escape_id("clk"),
+                    std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
+                ports.back().assoc.insert(std::make_pair(RTLIL::escape_id("rst"),
+                    std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
+                ports.back().assoc.insert(std::make_pair(RTLIL::escape_id("ena"),
+                    std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
             }
             else if (fields[0] == "endport") {
                 if (fields.size() != 1) {
@@ -289,11 +338,16 @@
 
                 flopTypes.resize(flopTypes.size() + 1);
                 flopTypes.back().name = RTLIL::escape_id(fields[1]);
-                flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("clk"), RTLIL::IdString()));
-                flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("rst"), RTLIL::IdString()));
-                flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("ena"), RTLIL::IdString()));
-                flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("d"),   RTLIL::IdString()));
-                flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("q"),   RTLIL::IdString()));
+                flopTypes.back().ports.insert(std::make_pair(
+                    RTLIL::escape_id("clk"), RTLIL::IdString()));
+                flopTypes.back().ports.insert(std::make_pair(
+                    RTLIL::escape_id("rst"), RTLIL::IdString()));
+                flopTypes.back().ports.insert(std::make_pair(
+                    RTLIL::escape_id("ena"), RTLIL::IdString()));
+                flopTypes.back().ports.insert(std::make_pair(
+                    RTLIL::escape_id("d"),   RTLIL::IdString()));
+                flopTypes.back().ports.insert(std::make_pair(
+                    RTLIL::escape_id("q"),   RTLIL::IdString()));
             }
             else if (fields[0] == "endff") {
                 if (fields.size() != 1) {
@@ -397,6 +451,19 @@
                 flopTypes.back().ports[RTLIL::escape_id("q")] = RTLIL::escape_id(fields[1]);
             }
 
+            // Parameters that has to match for a flip-flop
+            else if (fields[0] == "match") {
+                if (fields.size() < 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || tok.back() != "ff") {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                for (size_t i=1; i<fields.size(); ++i) {
+                    flopTypes.back().params.matching.push_back(RTLIL::escape_id(fields[i]));
+                }
+            }
             // Parameters to set
             else if (fields[0] == "set") {
                 if (fields.size() < 2) {
@@ -469,15 +536,21 @@
             }
 
             else {
-                log(" unexpected keyword '%s'\n", fields[0].c_str());
+                log_error(" unexpected keyword '%s'\n", fields[0].c_str());
             }
         }
 
         // Convert lists to maps
         for (const auto& it : dspTypes) {
+            if (m_DspTypes.count(it.name)) {
+                log_error(" duplicated rule for DSP '%s'\n", it.name.c_str());
+            }
             m_DspTypes.insert(std::make_pair(it.name, it));
         }
         for (const auto& it : flopTypes) {
+            if (m_FlopTypes.count(it.name)) {
+                log_error(" duplicated rule for flip-flop '%s'\n", it.name.c_str());
+            }
             m_FlopTypes.insert(std::make_pair(it.name, it));
         }
     } 
@@ -492,7 +565,14 @@
 
             log(" ports:\n");
             for (const auto& port : dsp.ports) {
-                log("  %s.%s\n", dsp.name.c_str(), port.name.c_str());
+
+                std::string range;
+                if (port.bits.first != -1 && port.bits.second != -1) {
+                    range = stringf("[%d:%d]", port.bits.second, port.bits.first);
+                }
+
+                log("  %s.%s%s\n", dsp.name.c_str(), port.name.c_str(), range.c_str());
+
                 for (const auto& it : port.assoc) {
                     log("   %.3s: %s\n", it.first.c_str(), !it.second.first.empty() ? it.second.first.c_str() : "<none>");
                 }
@@ -529,6 +609,12 @@
             }
 
             if (!ff.params.set.empty()) {
+                log("  params that must match:\n");
+                for (const auto& it : ff.params.matching) {
+                    log("   %s\n", it.c_str());
+                }
+            }
+            if (!ff.params.set.empty()) {
                 log("  set params:\n");
                 for (const auto& it : ff.params.set) {
                     log("   %s=%s\n", it.first.c_str(), it.second.decode_string().c_str());
@@ -656,7 +742,7 @@
     bool checkDspPort (RTLIL::Cell* a_Cell, const DspPortType& a_PortRule) {
         bool isOk = true;
 
-        // The cell parameters must not be set
+        // The cell register control parameters must not be set
         for (const auto& it : a_PortRule.params.set) {
             const auto curr = a_Cell->getParam(it.first);
             if (curr == it.second) {
@@ -790,6 +876,7 @@
         return isOk;
     }
 
+    /// Returns a string with either wire name or constant value for a SigBit
     static std::string sigBitName (const RTLIL::SigBit& a_SigBit) {
         if (a_SigBit.is_wire()) {
             RTLIL::Wire* w = a_SigBit.wire;
@@ -816,6 +903,7 @@
             a_Cell->type.c_str(), a_PortRule.name.c_str(), a_Cell->name.c_str());
 
         // Check if the port can be used for FF integration
+        log_assert(a_Cell->output(a_PortRule.name) || a_Cell->input(a_PortRule.name));
         if (!checkDspPort(a_Cell, a_PortRule)) {
             log_debug("  port check failed\n");
             return;
@@ -837,7 +925,12 @@
                 continue;
             }
 
-            log_assert(a_Cell->output(a_PortRule.name) || a_Cell->input(a_PortRule.name));
+            // Skip bits out of the specified range
+            if ((a_PortRule.bits.first  >= 0 && (int)i < a_PortRule.bits.first) ||
+                (a_PortRule.bits.second >= 0 && (int)i > a_PortRule.bits.second))
+            {
+                continue;
+            }
 
             pool<CellPin> others;
 
@@ -952,6 +1045,11 @@
                     flops[i].second,
                     flops[i].first->type.c_str(), flops[i].first->name.c_str());
             }
+            else if ((a_PortRule.bits.first  >= 0 && (int)i < a_PortRule.bits.first) ||
+                     (a_PortRule.bits.second >= 0 && (int)i > a_PortRule.bits.second))
+            {
+                log_debug("  %2zu. (excluded)\n", i);
+            }
             else {
                 log_debug("  %2zu. None\n", i);
             }
@@ -1252,7 +1350,7 @@
             }
         }
 
-        // No driver found
+        // No driver found. FIXME: Implement a cleaner way of indicating that
         return CellPin(nullptr, RTLIL::IdString(), -1);
     }
 
diff --git a/dsp_ff-plugin/nexus-dsp_rules.txt b/dsp_ff-plugin/nexus-dsp_rules.txt
index 642f847..fa11c3f 100644
--- a/dsp_ff-plugin/nexus-dsp_rules.txt
+++ b/dsp_ff-plugin/nexus-dsp_rules.txt
@@ -1,4 +1,4 @@
-dsp MULT36X36 # 36x36 mode
+dsp MULT9X9 MULT18X18 MULT18X36 MULT36X36
   port A
     clk CLK 0
     rst RSTA 0
@@ -25,6 +25,127 @@
   endport
 enddsp
 
+dsp MULTPREADD9X9 MULTPREADD18X18 MULTADDSUB18X18 MULTADDSUB36X36
+  port A
+    clk CLK 0
+    rst RSTA 0
+    ena CEA 1
+
+    set REGINPUTA=REGISTER
+    map GSR=GSR
+  endport
+  port B
+    clk CLK 0
+    rst RSTB 0
+    ena CEB 1
+
+    set REGINPUTB=REGISTER
+    map GSR=GSR
+  endport
+  port C
+    clk CLK 0
+    rst RSTC 0
+    ena CEC 1
+
+    set REGINPUTC=REGISTER
+    map GSR=GSR
+  endport
+  port Z
+    clk CLK 0
+    rst RSTOUT 0
+    ena CEOUT 1
+
+    set REGOUTPUT=REGISTER
+    map GSR=GSR
+  endport
+enddsp
+
+# TODO: Uncomment when support for multiple port registers controlled by
+# a common parameter is added:
+
+#dsp MULTADDSUB9X9WIDE
+#  port A0
+#    clk CLK 0
+#    rst RSTA0A1 0
+#    ena CEA0A1 1
+#
+#    set REGINPUTAB0=REGISTER
+#    map GSR=GSR
+#  endport
+#  port A1
+#    clk CLK 0
+#    rst RSTA0A1 0
+#    ena CEA0A1 1
+#
+#    set REGINPUTAB1=REGISTER
+#    map GSR=GSR
+#  endport
+#  port A2
+#    clk CLK 0
+#    rst RSTA2A3 0
+#    ena CEA2A3 1
+#
+#    set REGINPUTAB2=REGISTER
+#    map GSR=GSR
+#  endport
+#  port A3
+#    clk CLK 0
+#    rst RSTA2A3 0
+#    ena CEA2A3 1
+#
+#    set REGINPUTAB3=REGISTER
+#    map GSR=GSR
+#  endport
+#  port B0
+#    clk CLK 0
+#    rst RSTB0B1 0
+#    ena CEB0B1 1
+#
+#    set REGINPUTAB0=REGISTER
+#    map GSR=GSR
+#  endport
+#  port B1
+#    clk CLK 0
+#    rst RSTB0B1 0
+#    ena CEB0B1 1
+#
+#    set REGINPUTAB1=REGISTER
+#    map GSR=GSR
+#  endport
+#  port B2
+#    clk CLK 0
+#    rst RSTB2B3 0
+#    ena CEB2B3 1
+#
+#    set REGINPUTAB2=REGISTER
+#    map GSR=GSR
+#  endport
+#  port B3
+#    clk CLK 0
+#    rst RSTB2B3 0
+#    ena CEB2B3 1
+#
+#    set REGINPUTAB3=REGISTER
+#    map GSR=GSR
+#  endport
+#  port C
+#    clk CLK 0
+#    rst RSTC0
+#    ena CEC 1
+#
+#    set REGINPUTC=REGISTER
+#    map GSR=GSR
+#  endport
+#  port Z
+#    clk CLK 0
+#    rst RSTOUT 0
+#    ena CEOUT 1
+#
+#    set REGOUTPUT=REGISTER
+#    map GSR=GSR
+#  endport
+#enddsp
+
 ff FD1P3DX
   clk CK
   rst CD