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
+
+// ............................................................................