Merge pull request #369 from antmicro/fix-k6n10f-dsp-inference

Fix k6n10f DSP type inference
diff --git a/ql-qlf-plugin/ql-dsp-io-regs.cc b/ql-qlf-plugin/ql-dsp-io-regs.cc
index d9c2c76..5772513 100644
--- a/ql-qlf-plugin/ql-dsp-io-regs.cc
+++ b/ql-qlf-plugin/ql-dsp-io-regs.cc
@@ -10,133 +10,27 @@
 
 // ============================================================================
 
-const std::vector<std::string> ports2del_mult = {"load_acc", "subtract", "acc_fir", "dly_b"};
-const std::vector<std::string> ports2del_mult_acc = {"acc_fir", "dly_b"};
-const std::vector<std::string> ports2del_mult_add = {"dly_b"};
-const std::vector<std::string> ports2del_extension = {"saturate_enable", "shift_right", "round"};
-
-void ql_dsp_io_regs_pass(RTLIL::Module *module)
-{
-
-    for (auto cell : module->cells_) {
-        std::string cell_type = cell.second->type.str();
-        if (cell_type == RTLIL::escape_id("QL_DSP2") || cell_type == RTLIL::escape_id("QL_DSP3")) {
-            auto dsp = cell.second;
-            bool del_clk = false;
-            bool use_dsp_cfg_params = cell_type == RTLIL::escape_id("QL_DSP3");
-
-            int reg_in_i;
-            int out_sel_i;
-
-            // Get DSP configuration
-            if (use_dsp_cfg_params) {
-                // Read MODE_BITS at correct indexes
-                auto mode_bits = &dsp->getParam(RTLIL::escape_id("MODE_BITS"));
-                RTLIL::Const register_inputs;
-                register_inputs = mode_bits->bits.at(MODE_BITS_REGISTER_INPUTS_ID);
-                reg_in_i = register_inputs.as_int();
-
-                RTLIL::Const output_select;
-                output_select = mode_bits->extract(MODE_BITS_OUTPUT_SELECT_START_ID, MODE_BITS_OUTPUT_SELECT_WIDTH);
-                out_sel_i = output_select.as_int();
-            } else {
-                // Read dedicated configuration ports
-                const RTLIL::SigSpec *register_inputs;
-                register_inputs = &dsp->getPort(RTLIL::escape_id("register_inputs"));
-                if (!register_inputs)
-                    log_error("register_inputs port not found!");
-                auto reg_in_c = register_inputs->as_const();
-                reg_in_i = reg_in_c.as_int();
-
-                const RTLIL::SigSpec *output_select;
-                output_select = &dsp->getPort(RTLIL::escape_id("output_select"));
-                if (!output_select)
-                    log_error("output_select port not found!");
-                auto out_sel_c = output_select->as_const();
-                out_sel_i = out_sel_c.as_int();
-            }
-
-            // Build new type name
-            std::string new_type = cell_type;
-            new_type += "_MULT";
-
-            switch (out_sel_i) {
-            case 1:
-                new_type += "ACC";
-                break;
-            case 2:
-            case 3:
-                new_type += "ADD";
-                break;
-            case 5:
-                new_type += "ACC";
-                break;
-            case 6:
-            case 7:
-                new_type += "ADD";
-                break;
-            default:
-                break;
-            }
-
-            if (reg_in_i)
-                new_type += "_REGIN";
-
-            if (out_sel_i > 3)
-                new_type += "_REGOUT";
-
-            // Set new type name
-            dsp->type = RTLIL::IdString(new_type);
-
-            // Delete ports unused in given type of DSP cell
-            del_clk = (!reg_in_i && out_sel_i <= 3 && out_sel_i != 1);
-
-            std::vector<std::string> ports2del;
-
-            if (del_clk)
-                ports2del.push_back("clk");
-
-            switch (out_sel_i) {
-            case 0:
-            case 4:
-                ports2del.insert(ports2del.end(), ports2del_mult.begin(), ports2del_mult.end());
-                // Mark for deleton additional configuration ports
-                if (!use_dsp_cfg_params) {
-                    ports2del.insert(ports2del.end(), ports2del_extension.begin(), ports2del_extension.end());
-                }
-
-                break;
-            case 1:
-            case 5:
-                ports2del.insert(ports2del.end(), ports2del_mult_acc.begin(), ports2del_mult_acc.end());
-                break;
-            case 2:
-            case 3:
-            case 6:
-            case 7:
-                ports2del.insert(ports2del.end(), ports2del_mult_add.begin(), ports2del_mult_add.end());
-                break;
-            }
-
-            for (auto portname : ports2del) {
-                const RTLIL::SigSpec *port = &dsp->getPort(RTLIL::escape_id(portname));
-                if (!port)
-                    log_error("%s port not found!", portname.c_str());
-                dsp->connections_.erase(RTLIL::escape_id(portname));
-            }
-        }
-    }
-}
-
 struct QlDspIORegs : public Pass {
 
-    QlDspIORegs() : Pass("ql_dsp_io_regs", "Does something") {}
+    const std::vector<std::string> ports2del_mult = {"load_acc", "subtract", "acc_fir", "dly_b"};
+    const std::vector<std::string> ports2del_mult_acc = {"acc_fir", "dly_b"};
+    const std::vector<std::string> ports2del_mult_add = {"dly_b"};
+    const std::vector<std::string> ports2del_extension = {"saturate_enable", "shift_right", "round"};
+
+    /// Temporary SigBit to SigBit helper map.
+    SigMap m_SigMap;
+
+    // ..........................................
+
+    QlDspIORegs() : Pass("ql_dsp_io_regs", "Changes types of QL_DSP2/QL_DSP3 depending on their configuration.") {}
 
     void help() override
     {
         log("\n");
         log("    ql_dsp_io_regs [options] [selection]\n");
         log("\n");
+        log("Looks for QL_DSP2/QL_DSP3 cells and changes their types depending\n");
+        log("on their configuration.\n");
     }
 
     void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
@@ -153,6 +47,172 @@
             ql_dsp_io_regs_pass(module);
         }
     }
+
+    // Returns a pair of mask and value describing constant bit connections of
+    // a SigSpec
+    std::pair<uint32_t, uint32_t> get_constant_mask_value(const RTLIL::SigSpec *sigspec)
+    {
+        uint32_t mask = 0L;
+        uint32_t value = 0L;
+
+        auto sigbits = sigspec->bits();
+        for (ssize_t i = (sigbits.size() - 1); i >= 0; --i) {
+            auto other = m_SigMap(sigbits[i]);
+
+            mask <<= 1;
+            value <<= 1;
+
+            // A known constant
+            if (!other.is_wire() && other.data != RTLIL::Sx) {
+                mask |= 0x1;
+                value |= (other.data == RTLIL::S1);
+            }
+        }
+
+        return std::make_pair(mask, value);
+    }
+
+    void ql_dsp_io_regs_pass(RTLIL::Module *module)
+    {
+        // Setup the SigMap
+        m_SigMap.clear();
+        m_SigMap.set(module);
+
+        for (auto cell : module->cells_) {
+            std::string cell_type = cell.second->type.str();
+            if (cell_type == RTLIL::escape_id("QL_DSP2") || cell_type == RTLIL::escape_id("QL_DSP3")) {
+                auto dsp = cell.second;
+                bool del_clk = true;
+                bool use_dsp_cfg_params = (cell_type == RTLIL::escape_id("QL_DSP3"));
+
+                int reg_in_i;
+                int out_sel_i;
+
+                // Get DSP configuration
+                if (use_dsp_cfg_params) {
+                    // Read MODE_BITS at correct indexes
+                    auto mode_bits = &dsp->getParam(RTLIL::escape_id("MODE_BITS"));
+                    RTLIL::Const register_inputs;
+                    register_inputs = mode_bits->bits.at(MODE_BITS_REGISTER_INPUTS_ID);
+                    reg_in_i = register_inputs.as_int();
+
+                    RTLIL::Const output_select;
+                    output_select = mode_bits->extract(MODE_BITS_OUTPUT_SELECT_START_ID, MODE_BITS_OUTPUT_SELECT_WIDTH);
+                    out_sel_i = output_select.as_int();
+                } else {
+                    // Read dedicated configuration ports
+                    const RTLIL::SigSpec *register_inputs;
+                    register_inputs = &dsp->getPort(RTLIL::escape_id("register_inputs"));
+                    if (!register_inputs)
+                        log_error("register_inputs port not found!");
+                    auto reg_in_c = register_inputs->as_const();
+                    reg_in_i = reg_in_c.as_int();
+
+                    const RTLIL::SigSpec *output_select;
+                    output_select = &dsp->getPort(RTLIL::escape_id("output_select"));
+                    if (!output_select)
+                        log_error("output_select port not found!");
+                    auto out_sel_c = output_select->as_const();
+                    out_sel_i = out_sel_c.as_int();
+                }
+
+                // Get the feedback port
+                const RTLIL::SigSpec *feedback;
+                feedback = &dsp->getPort(RTLIL::escape_id("feedback"));
+                if (!feedback)
+                    log_error("feedback port not found!");
+
+                // Check if feedback is or can be set to 0 which implies MACC
+                auto feedback_con = get_constant_mask_value(feedback);
+                bool have_macc = (feedback_con.second == 0x0);
+                // log("mask=0x%08X value=0x%08X\n", consts.first, consts.second);
+                // log_error("=== END HERE ===\n");
+
+                // Build new type name
+                std::string new_type = cell_type;
+                new_type += "_MULT";
+
+                if (have_macc) {
+                    switch (out_sel_i) {
+                    case 1:
+                    case 2:
+                    case 3:
+                    case 5:
+                    case 7:
+                        del_clk = false;
+                        new_type += "ACC";
+                        break;
+                    default:
+                        break;
+                    }
+                } else {
+                    switch (out_sel_i) {
+                    case 1:
+                    case 2:
+                    case 3:
+                    case 5:
+                    case 7:
+                        new_type += "ADD";
+                        break;
+                    default:
+                        break;
+                    }
+                }
+
+                if (reg_in_i) {
+                    del_clk = false;
+                    new_type += "_REGIN";
+                }
+
+                if (out_sel_i > 3) {
+                    del_clk = false;
+                    new_type += "_REGOUT";
+                }
+
+                // Set new type name
+                dsp->type = RTLIL::IdString(new_type);
+
+                std::vector<std::string> ports2del;
+
+                if (del_clk)
+                    ports2del.push_back("clk");
+
+                switch (out_sel_i) {
+                case 0:
+                case 4:
+                case 6:
+                    ports2del.insert(ports2del.end(), ports2del_mult.begin(), ports2del_mult.end());
+                    // Mark for deleton additional configuration ports
+                    if (!use_dsp_cfg_params) {
+                        ports2del.insert(ports2del.end(), ports2del_extension.begin(), ports2del_extension.end());
+                    }
+                    break;
+                case 1:
+                case 2:
+                case 3:
+                case 5:
+                case 7:
+                    if (have_macc) {
+                        ports2del.insert(ports2del.end(), ports2del_mult_acc.begin(), ports2del_mult_acc.end());
+                    } else {
+                        ports2del.insert(ports2del.end(), ports2del_mult_add.begin(), ports2del_mult_add.end());
+                    }
+                    break;
+                }
+
+                for (auto portname : ports2del) {
+                    const RTLIL::SigSpec *port = &dsp->getPort(RTLIL::escape_id(portname));
+                    if (!port)
+                        log_error("%s port not found!", portname.c_str());
+                    dsp->connections_.erase(RTLIL::escape_id(portname));
+                }
+            }
+        }
+
+        // Clear the sigmap
+        m_SigMap.clear();
+    }
+
 } QlDspIORegs;
 
 PRIVATE_NAMESPACE_END
diff --git a/ql-qlf-plugin/tests/Makefile b/ql-qlf-plugin/tests/Makefile
index 84dc8b3..f2284c7 100644
--- a/ql-qlf-plugin/tests/Makefile
+++ b/ql-qlf-plugin/tests/Makefile
@@ -32,7 +32,8 @@
 	pp3_bram \
 	qlf_k6n10f/dsp_mult \
 	qlf_k6n10f/dsp_simd \
-	qlf_k6n10f/dsp_macc
+	qlf_k6n10f/dsp_macc \
+	qlf_k6n10f/dsp_madd
 #	qlf_k6n10_bram \
 
 SIM_TESTS = \
@@ -73,4 +74,5 @@
 qlf_k6n10f-dsp_mult_verify = true
 qlf_k6n10f-dsp_simd_verify = true
 qlf_k6n10f-dsp_macc_verify = true
+qlf_k6n10f-dsp_madd_verify = true
 #qlf_k6n10_bram_verify = true
diff --git a/ql-qlf-plugin/tests/qlf_k6n10f/dsp_macc/dsp_macc.tcl b/ql-qlf-plugin/tests/qlf_k6n10f/dsp_macc/dsp_macc.tcl
index d577d0a..377689a 100644
--- a/ql-qlf-plugin/tests/qlf_k6n10f/dsp_macc/dsp_macc.tcl
+++ b/ql-qlf-plugin/tests/qlf_k6n10f/dsp_macc/dsp_macc.tcl
@@ -75,6 +75,6 @@
 test_dsp_design "macc_simple_arst"          "_MULTACC"
 test_dsp_design "macc_simple_ena"           "_MULTACC"
 test_dsp_design "macc_simple_arst_clr_ena"  "_MULTACC"
-test_dsp_design "macc_simple_preacc"        "_MULTADD"
-test_dsp_design "macc_simple_preacc_clr"    "_MULTADD"
+test_dsp_design "macc_simple_preacc"        "_MULTACC"
+test_dsp_design "macc_simple_preacc_clr"    "_MULTACC"
 
diff --git a/ql-qlf-plugin/tests/qlf_k6n10f/dsp_madd/dsp_madd.tcl b/ql-qlf-plugin/tests/qlf_k6n10f/dsp_madd/dsp_madd.tcl
new file mode 100644
index 0000000..eeefa60
--- /dev/null
+++ b/ql-qlf-plugin/tests/qlf_k6n10f/dsp_madd/dsp_madd.tcl
@@ -0,0 +1,78 @@
+# For some tests the equiv_induct pass seems to hang if opt_expr + opt_clean
+# are not invoked after techmapping. Therefore this function is used instead
+# of the equiv_opt pass.
+proc check_equiv {top use_cfg_params} {
+    hierarchy -top ${top}
+
+    design -save preopt
+
+    if {${use_cfg_params} == 1} {
+        synth_quicklogic -family qlf_k6n10f -top ${top} -use_dsp_cfg_params
+    } else {
+        stat
+        synth_quicklogic -family qlf_k6n10f -top ${top}
+    }
+
+    design -stash postopt
+
+    design -copy-from preopt  -as gold A:top
+    design -copy-from postopt -as gate A:top
+
+    techmap -wb -autoproc -map +/quicklogic/qlf_k6n10f/cells_sim.v
+    techmap -wb -autoproc -map +/quicklogic/qlf_k6n10f/dsp_sim.v
+    yosys proc
+    opt_expr
+    opt_clean -purge
+
+    async2sync
+    equiv_make gold gate equiv
+    equiv_induct equiv
+    equiv_status -assert equiv
+
+    return
+}
+
+proc test_dsp_design {top expected_cell_suffix} {
+    set TOP ${top}
+    # Infer DSP with configuration bits passed through ports
+    # We expect QL_DSP2 cells inferred
+    set USE_DSP_CFG_PARAMS 0
+    design -load read
+    hierarchy -top ${TOP}_ports
+    check_equiv ${TOP}_ports ${USE_DSP_CFG_PARAMS}
+    design -load postopt
+    yosys cd ${TOP}_ports
+    select -assert-count 1 t:QL_DSP2${expected_cell_suffix}
+    select -assert-count 1 t:*
+
+    # Infer DSP with configuration bits passed through parameters
+    # We expect QL_DSP3 cells inferred
+    set USE_DSP_CFG_PARAMS 1
+    design -load read
+    hierarchy -top ${TOP}_params
+    check_equiv ${TOP}_params ${USE_DSP_CFG_PARAMS}
+    design -load postopt
+    yosys cd ${TOP}_params
+    select -assert-count 1 t:QL_DSP3${expected_cell_suffix}
+    select -assert-count 1 t:*
+
+    return
+}
+
+yosys -import
+if { [info procs quicklogic_eqn] == {} } { plugin -i ql-qlf}
+yosys -import  ;# ingest plugin commands
+
+read_verilog dsp_madd.v
+design -save read
+
+test_dsp_design "madd_simple"               "_MULTADD"
+
+#test_dsp_design "macc_simple"               "_MULTACC"
+#test_dsp_design "macc_simple_clr"           "_MULTACC"
+#test_dsp_design "macc_simple_arst"          "_MULTACC"
+#test_dsp_design "macc_simple_ena"           "_MULTACC"
+#test_dsp_design "macc_simple_arst_clr_ena"  "_MULTACC"
+#test_dsp_design "macc_simple_preacc"        "_MULTACC"
+#test_dsp_design "macc_simple_preacc_clr"    "_MULTACC"
+
diff --git a/ql-qlf-plugin/tests/qlf_k6n10f/dsp_madd/dsp_madd.v b/ql-qlf-plugin/tests/qlf_k6n10f/dsp_madd/dsp_madd.v
new file mode 100644
index 0000000..45987d3
--- /dev/null
+++ b/ql-qlf-plugin/tests/qlf_k6n10f/dsp_madd/dsp_madd.v
@@ -0,0 +1,96 @@
+// Copyright 2020-2022 F4PGA Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+module madd_simple_ports (
+    input  wire [ 9:0] A,
+    input  wire [ 8:0] B,
+    input  wire [ 1:0] C,
+    output reg  [18:0] Z
+);
+
+    // There is no support for autmoatic inference of multiply+add hence the
+    // DSP cell needs to be instanced manually.
+
+    // B * coeff[C] + A
+    dsp_t1_10x9x32_cfg_ports # (
+        .COEFF_0            (10'h011),
+        .COEFF_1            (10'h022),
+        .COEFF_2            (10'h033),
+        .COEFF_3            (10'h044)
+    ) dsp (
+        .a_i                (A),
+        .b_i                (B),
+        .acc_fir_i          (6'd0),
+        .z_o                (Z),
+        .dly_b_o            (),
+
+        .feedback_i         ({1'b1, C}), // 4-7
+        .output_select_i    (3'd3),
+        .register_inputs_i  (1'b0),
+
+        .unsigned_a_i       (1'b1),
+        .unsigned_b_i       (1'b1),
+        .load_acc_i         (1'b1),
+
+        .saturate_enable_i  (1'b0),
+        .shift_right_i      (6'd0),
+        .round_i            (1'b0),
+        .subtract_i         (1'b0)
+    );
+
+endmodule
+
+module madd_simple_params (
+    input  wire [ 9:0] A,
+    input  wire [ 8:0] B,
+    input  wire [ 1:0] C,
+    output reg  [18:0] Z
+);
+
+    // There is no support for autmoatic inference of multiply+add hence the
+    // DSP cell needs to be instanced manually.
+
+    // B * coeff[C] + A
+    dsp_t1_10x9x32_cfg_params # (
+        .COEFF_0            (10'h011),
+        .COEFF_1            (10'h022),
+        .COEFF_2            (10'h033),
+        .COEFF_3            (10'h044),
+
+        .OUTPUT_SELECT      (3'd3),
+        .SATURATE_ENABLE    (1'b0),
+        .SHIFT_RIGHT        (6'd0),
+        .ROUND              (1'b0),
+        .REGISTER_INPUTS    (1'b0)
+    ) dsp (
+        .a_i                (A),
+        .b_i                (B),
+        .acc_fir_i          (6'd0),
+        .z_o                (Z),
+        .dly_b_o            (),
+
+        .feedback_i         ({1'b1, C}), // 4-7
+
+        .unsigned_a_i       (1'b1),
+        .unsigned_b_i       (1'b1),
+        .load_acc_i         (1'b1),
+
+        .subtract_i         (1'b0)
+    );
+
+endmodule
+
+// ............................................................................