Initial pass scaffold plus rule parser

Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
diff --git a/Makefile b/Makefile
index 801c6a2..9d36450 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@
 #
 # SPDX-License-Identifier:ISC
 
-PLUGIN_LIST := fasm xdc params sdc ql-iob design_introspection integrateinv ql-qlf uhdm
+PLUGIN_LIST := fasm xdc params sdc ql-iob design_introspection integrateinv ql-qlf uhdm dsp_ff
 PLUGINS := $(foreach plugin,$(PLUGIN_LIST),$(plugin).so)
 PLUGINS_INSTALL := $(foreach plugin,$(PLUGIN_LIST),install_$(plugin))
 PLUGINS_CLEAN := $(foreach plugin,$(PLUGIN_LIST),clean_$(plugin))
diff --git a/dsp_ff-plugin/Makefile b/dsp_ff-plugin/Makefile
new file mode 100644
index 0000000..8f90d46
--- /dev/null
+++ b/dsp_ff-plugin/Makefile
@@ -0,0 +1,12 @@
+# Copyright (C) 2020-2021  The SymbiFlow Authors.
+#
+# Use of this source code is governed by a ISC-style
+# license that can be found in the LICENSE file or at
+# https://opensource.org/licenses/ISC
+#
+# SPDX-License-Identifier:ISC
+
+NAME = dsp-ff
+SOURCES = dsp_ff.cc 
+
+include ../Makefile_plugin.common
diff --git a/dsp_ff-plugin/dsp_ff.cc b/dsp_ff-plugin/dsp_ff.cc
new file mode 100644
index 0000000..738e428
--- /dev/null
+++ b/dsp_ff-plugin/dsp_ff.cc
@@ -0,0 +1,601 @@
+#include "kernel/register.h"
+#include "kernel/rtlil.h"
+#include "kernel/sigtools.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+// ============================================================================
+
+struct DspFF : public Pass {
+
+    /// A structure identifying specific pin in a cell instance
+    struct CellPin {
+        RTLIL::Cell*    cell;   /// Cell pointer (nullptr for top-level ports)
+        RTLIL::IdString port;   /// Port name
+        int             bit;    /// Bit index
+
+        CellPin (RTLIL::Cell* _cell,
+                 const RTLIL::IdString& _port,
+                 int _bit = 0) : 
+            cell(_cell),
+            port(_port),
+            bit (_bit)
+        {}
+
+        CellPin (const CellPin& ref) = default;
+        CellPin (CellPin&& ref) = default;
+
+        unsigned int hash () const {
+            unsigned int h = 0;
+            if (cell != nullptr) {
+                h = mkhash_add(h, cell->hash());
+            }
+            h = mkhash_add(h, port.hash());
+            h = mkhash_add(h, bit);
+            return h;
+        }
+
+        bool operator == (const CellPin& ref) const {
+            return  (cell == ref.cell) &&
+                    (port == ref.port) &&
+                    (bit  == ref.bit);
+        }
+    };
+
+    // ..........................................
+
+    struct FlopType {
+        RTLIL::IdString name;
+
+        struct {
+            RTLIL::IdString clk;
+            RTLIL::IdString rst;
+            RTLIL::IdString ena;
+            RTLIL::IdString d;
+            RTLIL::IdString q;
+        } ports;
+
+        struct {
+            std::vector<RTLIL::IdString>           matching;
+            dict<RTLIL::IdString, RTLIL::Const>    required;
+            dict<RTLIL::IdString, RTLIL::Const>    set;
+            dict<RTLIL::IdString, RTLIL::IdString> map;
+        } params;
+    };
+
+    struct DspPortType {
+        RTLIL::IdString name;
+
+        struct {
+            RTLIL::IdString clk;
+            RTLIL::IdString rst;
+            RTLIL::IdString ena;
+        } assoc;
+
+        struct {
+            dict<RTLIL::IdString, RTLIL::Const>    set;
+            dict<RTLIL::IdString, RTLIL::IdString> map;
+        } params;
+
+        dict<RTLIL::IdString, RTLIL::Const> connect;
+    };
+
+    struct DspType {
+        RTLIL::IdString name;
+        std::vector<DspPortType> ports;
+    };
+
+    // ..........................................
+
+    void load_rules(const std::string& a_FileName) {
+
+        // Parses a vector of strings like "<name>=<value>" starting from the
+        // second one on the list
+        auto parseNameValue = [&](const std::vector<std::string>& strs) {
+            const std::regex expr ("(\\S+)=(\\S+)");
+            std::smatch      match;
+
+            std::vector<std::pair<std::string, std::string>> vec;
+
+            for (size_t i=1; i<strs.size(); ++i) {
+                if (std::regex_match(strs[i], match, expr)) {
+                    vec.push_back(std::make_pair(match[1], match[2]));
+                }
+                else {
+                    log_error(" syntax error: '%s'\n", strs[i].c_str());
+                }
+            }
+
+            return vec;
+        };
+
+        std::ifstream file (a_FileName);
+        std::string line;
+
+        log("Loading rules from '%s'...\n", a_FileName.c_str());
+        if (!file) {
+            log_error(" Error opening file!\n");
+        }
+
+        std::vector<DspType>    dspTypes;
+        std::vector<FlopType>   flopTypes;
+
+        std::vector<std::string> tok;
+ 
+        // Parse the file
+        while (1) {
+
+            // Get line
+            std::getline(file, line);
+            if (!file) {
+                break;
+            }
+
+            // Strip comment if any, skip empty lines
+            size_t pos = line.find("#");
+            if (pos != std::string::npos) {
+                line = line.substr(0, pos);
+            }
+            if (line.find_first_not_of(" \r\n\t") == std::string::npos) {
+                continue;
+            }
+
+            // Split the line
+            const auto fields = get_fields(line);
+            log_assert(fields.size() >= 1);
+
+            // DSP section
+            if (fields[0] == "dsp") {
+                if (fields.size() != 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (!tok.empty()) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+                tok.push_back(fields[0]);
+
+                dspTypes.resize(dspTypes.size() + 1);\
+                dspTypes.back().name = RTLIL::escape_id(fields[1]);
+            }
+            else if (fields[0] == "enddsp") {
+                if (fields.size() != 1) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() != 1 || tok.back() != "dsp") {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+                tok.pop_back(); 
+            }
+
+            // DSP port section
+            else if (fields[0] == "port") {
+                if (fields.size() != 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() != 1 || tok.back() != "dsp") {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+                tok.push_back(fields[0]);
+
+                auto& ports = dspTypes.back().ports;
+                ports.resize(ports.size() + 1);
+                ports.back().name = RTLIL::escape_id(fields[1]);
+            }
+            else if (fields[0] == "endport") {
+                if (fields.size() != 1) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() != 2 || tok.back() != "port") {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+                tok.pop_back(); 
+            }
+
+            // Flip-flop type section
+            else if (fields[0] == "ff") {
+                if (fields.size() != 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (!tok.empty()) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+                tok.push_back(fields[0]);
+
+                flopTypes.resize(flopTypes.size() + 1);
+                flopTypes.back().name = RTLIL::escape_id(fields[1]);
+            }
+            else if (fields[0] == "endff") {
+                if (fields.size() != 1) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() != 1 || tok.back() != "ff") {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+                tok.pop_back(); 
+            }
+
+            // Signals
+            else if (fields[0] == "clk") {
+                if (fields.size() != 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                // Associated clock
+                if (tok.back() == "port") {
+                    auto& ports = dspTypes.back().ports;
+                    ports.back().assoc.clk = RTLIL::escape_id(fields[1]);
+                }
+                else if (tok.back() == "ff") {
+                    flopTypes.back().ports.clk = RTLIL::escape_id(fields[1]);
+                }
+            }
+            else if (fields[0] == "rst") {
+                if (fields.size() != 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                // Associated reset
+                if (tok.back() == "port") {
+                    auto& ports = dspTypes.back().ports;
+                    ports.back().assoc.rst = RTLIL::escape_id(fields[1]);
+                }
+                else if (tok.back() == "ff") {
+                    flopTypes.back().ports.rst = RTLIL::escape_id(fields[1]);
+                }
+            }
+            else if (fields[0] == "ena") {
+                if (fields.size() != 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                // Associated enable
+                if (tok.back() == "port") {
+                    auto& ports = dspTypes.back().ports;
+                    ports.back().assoc.ena = RTLIL::escape_id(fields[1]);
+                }
+                else if (tok.back() == "ff") {
+                    flopTypes.back().ports.ena = RTLIL::escape_id(fields[1]);
+                }
+            }
+
+            else if (fields[0] == "d") {
+                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());
+                }
+
+                flopTypes.back().ports.d = RTLIL::escape_id(fields[1]);
+            }
+            else if (fields[0] == "q") {
+                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());
+                }
+
+                flopTypes.back().ports.q = RTLIL::escape_id(fields[1]);
+            }
+
+            // Parameters to set
+            else if (fields[0] == "set") {
+                if (fields.size() < 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                const auto vec = parseNameValue(fields);
+                dict<RTLIL::IdString, RTLIL::Const> set;
+                for (const auto& it : vec) {
+                    set.insert(std::make_pair(
+                        RTLIL::escape_id(it.first),
+                        RTLIL::Const(it.second)
+                    ));
+                }
+
+                if (tok.back() == "port") {
+                    auto& ports = dspTypes.back().ports;
+                    ports.back().params.set.swap(set);
+                }
+                else if (tok.back() == "ff") {
+                    flopTypes.back().params.set.swap(set);
+                }
+            }
+            // Parameters to copy / map
+            else if (fields[0] == "map") {
+                if (fields.size() < 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                const auto vec = parseNameValue(fields);
+                dict<RTLIL::IdString, RTLIL::IdString> map;
+                for (const auto& it : vec) {
+                    map.insert(std::make_pair(
+                        RTLIL::escape_id(it.first),
+                        RTLIL::escape_id(it.second)
+                    ));
+                }
+
+                if (tok.back() == "port") {
+                    auto& ports = dspTypes.back().ports;
+                    ports.back().params.map.swap(map);
+                }
+                else if (tok.back() == "ff") {
+                    flopTypes.back().params.map.swap(map);
+                }
+            }
+            // Connections to make
+            else if (fields[0] == "con") {
+                if (fields.size() < 2) {
+                    log_error(" syntax error: '%s'\n", line.c_str());
+                }
+                if (tok.size() == 0 || tok.back() != "port") {
+                    log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+                }
+
+                const auto vec = parseNameValue(fields);
+                auto& ports = dspTypes.back().ports;
+                for (const auto& it : vec) {
+                    ports.back().connect.insert(std::make_pair(
+                        RTLIL::escape_id(it.first),
+                        RTLIL::Const(it.second)
+                    ));
+                }
+            }
+
+            else {
+                log(" unexpected keyword '%s'\n", fields[0].c_str());
+            }
+        }
+
+        // Convert lists to maps
+        for (const auto& it : dspTypes) {
+            m_DspTypes.insert(std::make_pair(it.name, it));
+        }
+        for (const auto& it : flopTypes) {
+            m_FlopTypes.insert(std::make_pair(it.name, it));
+        }
+    } 
+
+    // TODO: make lambda
+    std::vector<std::string> get_fields(const std::string& a_String,
+                                        const char a_Delim = ' ',
+                                        bool a_KeepEmpty = false)
+    {
+        std::vector<std::string> fields;
+        std::stringstream ss(a_String);
+
+        while (ss.good()) {
+            std::string field;
+            std::getline(ss, field, a_Delim);
+            if (!field.empty() || a_KeepEmpty) {
+                fields.push_back(field);
+            }
+        }
+
+        return fields;
+    }
+
+    void dump_rules() {
+
+        // Dump DSP types
+        log("DSP types:\n");
+        for (const auto& it : m_DspTypes) {
+            const auto& dsp = it.second;
+            log(" %s\n", dsp.name.c_str());
+
+            log(" ports:\n");
+            for (const auto& port : dsp.ports) {
+                log("  %s.%s\n", dsp.name.c_str(), port.name.c_str());
+                log("   clk: %s\n", !port.assoc.clk.empty() ? port.assoc.clk.c_str() : "<none>");
+                log("   rst: %s\n", !port.assoc.rst.empty() ? port.assoc.rst.c_str() : "<none>");
+                log("   ena: %s\n", !port.assoc.ena.empty() ? port.assoc.ena.c_str() : "<none>");
+
+                if (!port.params.set.empty()) {
+                    log("   set params:\n");
+                    for (const auto& it : port.params.set) {
+                        log("    %s=%s\n", it.first.c_str(), it.second.decode_string().c_str());
+                    }
+                }
+                if (!port.params.map.empty()) {
+                    log("   map params:\n");
+                    for (const auto& it : port.params.map) {
+                        log("    %s=%s\n", it.first.c_str(), it.second.c_str());
+                    }
+                }
+                if (!port.connect.empty()) {
+                    log("   connect ports:\n");
+                    for (const auto& it : port.connect) {
+                        log("    %s.%s=%s\n", dsp.name.c_str(), it.first.c_str(), it.second.as_string().c_str());
+                    }
+                }
+            }
+        }
+
+        // Dump flop types
+        log("Flip-flop types:\n");
+        for (const auto& it : m_FlopTypes) {
+            const auto& ff = it.second;
+            log(" %s\n", ff.name.c_str());
+            log("  clk: %s\n", !ff.ports.clk.empty() ? ff.ports.clk.c_str() : "<none>");
+            log("  rst: %s\n", !ff.ports.rst.empty() ? ff.ports.rst.c_str() : "<none>");
+            log("  ena: %s\n", !ff.ports.ena.empty() ? ff.ports.ena.c_str() : "<none>");
+            log("  d  : %s\n", !ff.ports.d.empty()   ? ff.ports.d.c_str()   : "<none>");
+            log("  q  : %s\n", !ff.ports.q.empty()   ? ff.ports.q.c_str()   : "<none>");
+
+            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());
+                }
+            }
+            if (!ff.params.map.empty()) {
+                log("  map params:\n");
+                for (const auto& it : ff.params.map) {
+                    log("   %s=%s\n", it.first.c_str(), it.second.c_str());
+                }
+            }
+        }
+    }
+
+    // ..........................................
+
+    /// Temporary SigBit to SigBit helper map.
+    SigMap m_SigMap;
+    /// Net map
+    dict<RTLIL::SigBit, RTLIL::SigBit> m_NetMap;
+
+    /// DSP types
+    dict<RTLIL::IdString, DspType>    m_DspTypes;
+    /// Flip-flop types
+    dict<RTLIL::IdString, FlopType>   m_FlopTypes;
+
+    // ..........................................
+
+    DspFF() :
+        Pass("dsp_ff", "Integrates flip-flop into DSP blocks")
+    {}
+
+    void help () override {
+        log("\n");
+        log("    dsp_ff -rules <rules.txt> [selection]\n");
+        log("\n");
+        log("Integrates flip-flops with DSP blocks and enables their internal registers.\n");
+        log("\n");
+    }
+
+    void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
+    {
+        log_header(a_Design, "Executing DSP_FF pass.\n");
+
+        std::string rulesFile;
+
+        // Parse args
+        size_t argidx;
+        for (argidx = 1; argidx < a_Args.size(); argidx++) {
+            if (a_Args[argidx] == "-rules" && (argidx + 1) < a_Args.size()) {
+                rulesFile = a_Args[++argidx];
+                continue;
+            }
+
+            break;
+        }
+        extra_args(a_Args, argidx, a_Design);
+
+        // Check args
+        if (rulesFile.empty()) {
+            log_cmd_error("No rules file specified!");
+        }
+
+        // Load rules
+        load_rules(rulesFile);
+        if (log_force_debug) {
+            dump_rules();
+        }
+
+        // Process modules
+        for (auto module : a_Design->selected_modules()) {
+
+            // Setup the SigMap
+            m_SigMap.clear();
+            m_SigMap.set(module);
+
+        }
+
+        // Clear maps
+        m_SigMap.clear();
+    }
+
+/*
+    pool<CellPin> getSinks (const CellPin& a_Driver) {
+
+        auto module = a_Driver.cell->module;
+        pool<CellPin> sinks;
+
+        // The driver has to be an output pin
+        if (!a_Driver.cell->output(a_Driver.port)) {
+            return sinks;
+        }
+
+        // Get the driver sigbit
+        auto driverSigspec = a_Driver.cell->getPort(a_Driver.port);
+        auto driverSigbit = m_SigMap(driverSigspec.bits().at(a_Driver.bit));
+
+        // Look for connected sinks
+        for (auto cell : module->cells()) {
+            for (auto conn : cell->connections()) {
+                auto port = conn.first;
+                auto sigspec = conn.second;
+
+                // Consider only sinks (inputs)
+                if (!cell->input(port)) {
+                    continue;
+                }
+
+                // Check all sigbits
+                auto sigbits = sigspec.bits();
+                for (size_t bit = 0; bit < sigbits.size(); ++bit) {
+
+                    auto sigbit = sigbits[bit];
+                    if (!sigbit.wire) {
+                        continue;
+                    }
+
+                    // Got a sink pin of another cell
+                    sigbit = m_SigMap(sigbit);
+                    if (sigbit == driverSigbit) {
+                        sinks.insert(CellPin(cell, port, bit));
+                    }
+                }
+            }
+        }
+
+        // Look for connected top-level output ports
+        for (auto conn : module->connections()) {
+            auto dst = conn.first;
+            auto src = conn.second;
+
+            auto sigbits = dst.bits();
+            for (size_t bit = 0; bit < sigbits.size(); ++bit) {
+
+                auto sigbit = sigbits[bit];
+                if (!sigbit.wire) {
+                    continue;
+                }
+
+                if (!sigbit.wire->port_output) {
+                    continue;
+                }
+
+                sigbit = m_SigMap(sigbit);
+                if (sigbit == driverSigbit) {
+                    sinks.insert(CellPin(nullptr, sigbit.wire->name, bit));
+                }
+            }
+        }
+
+        return sinks;
+    }
+*/
+
+} DspFF;
+
+PRIVATE_NAMESPACE_END
+