SIMD DSP inference pass for QuickLogic qlf_k6n10f

Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
diff --git a/ql-qlf-plugin/Makefile b/ql-qlf-plugin/Makefile
index 2819055..56ced8f 100644
--- a/ql-qlf-plugin/Makefile
+++ b/ql-qlf-plugin/Makefile
@@ -11,14 +11,15 @@
           ql-dsp.cc \
           pp3_braminit.cc \
           quicklogic_eqn.cc \
-          ql-edif.cc
+          ql-edif.cc \
+          ql-dsp-simd.cc
 
 include ../Makefile_plugin.common
 
 COMMON          = common
 QLF_K4N8_DIR    = qlf_k4n8
 QLF_K6N10_DIR   = qlf_k6n10
-QLF_K6N10F_DIR   = qlf_k6n10f
+QLF_K6N10F_DIR  = qlf_k6n10f
 PP3_DIR         = pp3
 VERILOG_MODULES = $(COMMON)/cells_sim.v         \
                   $(QLF_K4N8_DIR)/arith_map.v   \
diff --git a/ql-qlf-plugin/ql-dsp-simd.cc b/ql-qlf-plugin/ql-dsp-simd.cc
new file mode 100644
index 0000000..df7ed08
--- /dev/null
+++ b/ql-qlf-plugin/ql-dsp-simd.cc
@@ -0,0 +1,297 @@
+// Copyright (C) 2020-2022  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
+
+#include "kernel/log.h"
+#include "kernel/register.h"
+#include "kernel/rtlil.h"
+#include "kernel/sigtools.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+// ============================================================================
+
+struct QlDspSimdPass : public Pass {
+
+    QlDspSimdPass () : Pass("ql_dsp_simd", "Infers QuickLogic k6n10f DSP pairs that can operate in SIMD mode") {}
+
+    void help() override {
+        log("\n");
+        log("    ql_dsp_simd [selection]\n");
+        log("\n");
+        log("    This pass identifies k6n10f DSP cells with identical configuration\n");
+        log("    and packs pairs of them together into other DSP cells that can\n");
+        log("    perform SIMD operation.\n");
+    }
+
+    // ..........................................
+
+    /// Describes DSP config unique to a whole DSP cell
+    struct DspConfig {
+
+        // Port connections
+        dict<RTLIL::IdString, RTLIL::SigSpec> connections;
+
+        // TODO: Possibly include parameters here. For now we have just
+        // connections.
+
+        DspConfig () = default;
+
+        DspConfig (const DspConfig& ref) = default;
+        DspConfig (DspConfig&& ref) = default;
+
+        unsigned int hash () const {
+            return connections.hash();
+        }
+
+        bool operator == (const DspConfig& ref) const {
+            return connections == ref.connections;
+        }
+    };
+
+    // ..........................................
+
+    // DSP control and config ports to consider and how to map them to ports
+    // of the target DSP cell
+    const std::vector<std::pair<std::string, std::string>> m_DspCfgPorts = {
+        std::make_pair("clock_i",           "clk"),
+        std::make_pair("reset_i",           "reset"),
+
+        std::make_pair("feedback_i",        "feedback"),
+        std::make_pair("load_acc_i",        "load_acc"),
+        std::make_pair("unsigned_a_i",      "unsigned_a"),
+        std::make_pair("unsigned_b_i",      "unsigned_b"),
+
+        std::make_pair("output_select_i",   "output_select"),
+        std::make_pair("saturate_enable_i", "saturate_enable"),
+        std::make_pair("shift_right_i",     "shift_right"),
+        std::make_pair("round_i",           "round"),
+        std::make_pair("subtract_i",        "subtract"),
+        std::make_pair("register_inputs_i", "register_inputs")
+    };
+
+    // DSP data ports and how to map them to ports of the target DSP cell
+    const std::vector<std::pair<std::string, std::string>> m_DspDataPorts = {
+        std::make_pair("a_i",       "a"),
+        std::make_pair("b_i",       "b"),
+        std::make_pair("acc_fir_i", "acc_fir"),
+        std::make_pair("z_o",       "z"),
+        std::make_pair("dly_b_o",   "dly_b"),
+    };
+
+    // Source DSP cell type (SISD)
+    const RTLIL::IdString m_SisdDspType = RTLIL::escape_id("dsp_t1_10x9x32");
+    // Target DSP cell type for the SIMD mode
+    const RTLIL::IdString m_SimdDspType = RTLIL::escape_id("QL_DSP2");
+
+    /// Temporary SigBit to SigBit helper map.
+    SigMap m_SigMap;
+
+    // ..........................................
+
+    void execute (std::vector<std::string> a_Args, RTLIL::Design* a_Design) override {
+        log_header(a_Design, "Executing QL_DSP_SIMD pass.\n");
+
+        // Parse args
+        extra_args(a_Args, 1, a_Design);   
+
+        // Process modules
+        for (auto module : a_Design->selected_modules()) {
+
+            // Setup the SigMap
+            m_SigMap.clear();
+            m_SigMap.set(module);
+
+            // Assemble DSP cell groups
+            dict<DspConfig, std::vector<RTLIL::Cell*>> groups;
+            for (auto cell : module->selected_cells()) {
+
+                // Check if this is a DSP cell
+                if (cell->type != m_SisdDspType) {
+                    continue;
+                }
+
+                // Skip if it has the (* keep *) attribute set
+                if (cell->has_keep_attr()) {
+                    continue;
+                }
+
+                // Add to a group
+                const auto key = getDspConfig(cell);
+                groups[key].push_back(cell);
+
+            }
+
+            std::vector<const RTLIL::Cell*> cellsToRemove;
+
+            // Map cell pairs to the target DSP SIMD cell
+            for (const auto& it : groups) {
+                const auto& group  = it.second;
+                const auto& config = it.first;
+
+                // Ensure an even number
+                size_t count = group.size();
+                if (count & 1) count--;
+
+                // Map SIMD pairs
+                for (size_t i=0; i < count; i+=2) {
+                    const RTLIL::Cell* dsp_a = group[i];
+                    const RTLIL::Cell* dsp_b = group[i+1];
+
+                    std::string name = stringf("simd_%s_%s",
+                        RTLIL::unescape_id(dsp_a->name).c_str(),
+                        RTLIL::unescape_id(dsp_b->name).c_str()
+                    );
+
+                    log(" SIMD: %s (%s) + %s (%s) => %s (%s)\n",
+                        RTLIL::unescape_id(dsp_a->name).c_str(),
+                        RTLIL::unescape_id(dsp_a->type).c_str(),
+                        RTLIL::unescape_id(dsp_b->name).c_str(),
+                        RTLIL::unescape_id(dsp_b->type).c_str(),
+                        RTLIL::unescape_id(name).c_str(),
+                        RTLIL::unescape_id(m_SimdDspType).c_str()
+                    );
+
+                    // Create the new cell
+                    RTLIL::Cell* simd = module->addCell(
+                        RTLIL::escape_id(name),
+                        m_SimdDspType
+                    );
+
+                    // Check if the target cell is known (important to know
+                    // its port widths)
+                    if (!simd->known()) {
+                        log_error(" The target cell type '%s' is not known!",
+                            RTLIL::unescape_id(m_SimdDspType).c_str()
+                        );
+                    }
+
+                    // Connect common ports
+                    for (const auto& it : m_DspCfgPorts) {
+                        auto sport = RTLIL::escape_id(it.first);
+                        auto dport = RTLIL::escape_id(it.second);
+
+                        simd->setPort(dport, config.connections.at(sport));
+                    }
+
+                    // Connect data ports
+                    for (const auto& it : m_DspDataPorts) {
+                        auto sport = RTLIL::escape_id(it.first);
+                        auto dport = RTLIL::escape_id(it.second);
+
+                        RTLIL::SigSpec sigspec;
+                        size_t width = getPortWidth(simd, dport);
+
+                        // A part
+                        if (dsp_a->hasPort(sport)) {
+                            const auto& sig = dsp_a->getPort(sport);
+                            sigspec.append(sig);
+                            if (sig.bits().size() < width / 2) {
+                                sigspec.append(RTLIL::SigSpec(RTLIL::Sx,
+                                    sig.bits().size() - width / 2)
+                                );
+                            }
+                        } else {                            
+                            sigspec.append(RTLIL::SigSpec(RTLIL::Sx, width / 2));
+                        }
+
+                        // B part
+                        if (dsp_b->hasPort(sport)) {
+                            const auto& sig = dsp_b->getPort(sport);
+                            sigspec.append(sig);
+                            if (sig.bits().size() < width / 2) {
+                                sigspec.append(RTLIL::SigSpec(RTLIL::Sx,
+                                    sig.bits().size() - width / 2)
+                                );
+                            }
+                        } else {                            
+                            sigspec.append(RTLIL::SigSpec(RTLIL::Sx, width / 2));
+                        }
+
+                        simd->setPort(dport, sigspec);
+                    }
+
+                    // Enable the fractured mode by connecting the control
+                    // port.
+                    simd->setPort(RTLIL::escape_id("f_mode"), RTLIL::S1);
+
+                    // Mark DSP parts for removal
+                    cellsToRemove.push_back(dsp_a);
+                    cellsToRemove.push_back(dsp_b);
+                }
+            }
+
+            // Remove old cells
+            for (const auto& cell : cellsToRemove) {
+                module->remove(const_cast<RTLIL::Cell*>(cell));
+            }
+        }
+
+        // Clear
+        m_SigMap.clear();
+    }
+
+    // ..........................................
+
+    /// Looks up port width in the cell definition and returns it. Returns 0
+    /// if it cannot be determined.
+    size_t getPortWidth (RTLIL::Cell* a_Cell, RTLIL::IdString a_Port) {
+
+        if (!a_Cell->known()) {
+            return 0;
+        }
+
+        // Get the module defining the cell (the previous condition ensures
+        // that the pointers are valid)
+        RTLIL::Module* mod = a_Cell->module->design->module(a_Cell->type);
+        if (mod == nullptr) {
+            return 0;
+        }
+
+        // Get the wire representing the port
+        RTLIL::Wire* wire = mod->wire(a_Port);
+        if (wire == nullptr) {
+            return 0;
+        }
+
+        return wire->width;
+    }
+
+    /// Given a DSP cell populates and returns a DspConfig struct for it.
+    DspConfig getDspConfig (RTLIL::Cell* a_Cell) {
+        DspConfig config;
+
+        for (const auto& it : m_DspCfgPorts) {
+            auto port = RTLIL::escape_id(it.first);
+
+            // Port unconnected
+            if (!a_Cell->hasPort(port)) {
+                config.connections[port] = RTLIL::SigSpec(RTLIL::Sx);
+                continue;
+            }
+
+            // Get the port connection and map it to unique SigBits
+            const auto& orgSigSpec = a_Cell->getPort(port);
+            const auto& orgSigBits = orgSigSpec.bits();
+
+            RTLIL::SigSpec newSigSpec;
+            for (size_t i=0; i<orgSigBits.size(); ++i) {
+                auto newSigBit = m_SigMap(orgSigBits[i]);
+                newSigSpec.append(newSigBit);
+            }
+       
+            // Store
+            config.connections[port] = newSigSpec;
+        }
+
+        return config;
+    }
+
+} QlDspSimdPass;
+
+PRIVATE_NAMESPACE_END