Merge pull request #232 from antmicro/k6n10f_dsp_model
k6n10f DSP behavioral model
diff --git a/Makefile_test.common b/Makefile_test.common
index 16e016e..84af520 100644
--- a/Makefile_test.common
+++ b/Makefile_test.common
@@ -62,6 +62,7 @@
fi \
else \
if [ $$$$RETVAL -ne 0 ]; then \
+ echo "Unexpected runtime error"; \
printf "Test %-20s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
false; \
fi \
@@ -69,6 +70,29 @@
endef
+define test_sim_tpl =
+$(1): $(1)/ok
+ @printf "Test %-18s \e[32mPASSED\e[0m @ %s\n" $(1) $(CURDIR);
+
+$(1)/$$(notdir $(1).vvp): $(1)/$$(notdir $(1).v)
+ @iverilog -vvvv -g2005 -o $$@ $$< -I../ -DVCD_FILE=\"$(1)/$$(notdir $(1).vcd)\" >$(1)/$$(notdir $(1).vvp.log) 2>&1; \
+ if [ $$$$? -ne 0 ]; then \
+ printf "Test %-18s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
+ false; \
+ fi
+
+$(1)/ok: $(1)/$$(notdir $(1).vvp) $(1)/$$(notdir $(1).v)
+ @vvp -vvvv $$< >$(1)/$$(notdir $(1).log) 2>&1; \
+ if [ $$$$? -ne 0 ]; then \
+ printf "Test %-18s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
+ false; \
+ else \
+ touch $$@; \
+ true; \
+ fi
+
+endef
+
define unit_test_tpl =
$(1): $(1)/$(1).test
@$$<
@@ -83,7 +107,7 @@
diff_test = diff $(1)/$(1).golden.$(2) $(1)/$(1).$(2)
-all: $(TESTS) $(UNIT_TESTS)
+all: $(TESTS) $(SIM_TESTS) $(UNIT_TESTS)
$(GTEST_DIR)/build/lib/libgtest.a $(GTEST_DIR)/build/lib/libgtest_main.a:
@mkdir -p $(GTEST_DIR)/build
@@ -91,12 +115,14 @@
cmake ..; \
make
-.PHONY: all clean $(TESTS) $(UNIT_TESTS)
+.PHONY: all clean $(TESTS) $(SIM_TESTS) $(UNIT_TESTS)
$(foreach test,$(TESTS),$(eval $(call test_tpl,$(test))))
+$(foreach test,$(SIM_TESTS),$(eval $(call test_sim_tpl,$(test))))
$(foreach test,$(UNIT_TESTS),$(eval $(call unit_test_tpl,$(test))))
clean:
@rm -rf $(foreach test,$(TESTS),$(test)/$(test).sdc $(test)/$(test)_[0-9].sdc $(test)/$(test).txt $(test)/$(test).eblif $(test)/$(test).json)
+ @rm -rf $(foreach test,$(SIM_TESTS),$(test)/*.vvp $(test)/*.vcd)
@rm -rf $(foreach test,$(UNIT_TESTS),$(test)/$(test).test.o $(test)/$(test).test.d $(test)/$(test).test)
@find . -name "ok" -or -name "*.log" | xargs rm -rf
diff --git a/environment.yml b/environment.yml
index cc60e2b..d6f473d 100644
--- a/environment.yml
+++ b/environment.yml
@@ -13,3 +13,4 @@
dependencies:
- litex-hub::yosys
- litex-hub::surelog
+ - litex-hub::iverilog
diff --git a/ql-qlf-plugin/qlf_k6n10f/cells_sim.v b/ql-qlf-plugin/qlf_k6n10f/cells_sim.v
index 296fc81..6270f49 100644
--- a/ql-qlf-plugin/qlf_k6n10f/cells_sim.v
+++ b/ql-qlf-plugin/qlf_k6n10f/cells_sim.v
@@ -685,17 +685,17 @@
(* blackbox *)
module QL_DSP2 ( // TODO: Name subject to change
- input [19:0] a,
- input [17:0] b,
- input [ 3:0] acc_fir,
- output [37:0] z,
- output [17:0] dly_b,
+ input [NBITS_A-1:0] a,
+ input [NBITS_B-1:0] b,
+ input [NBITS_AF-1:0] acc_fir,
+ output [NBITS_Z-1:0] z,
+ output [NBITS_B-1:0] dly_b,
(* clkbuf_sink *)
input clk,
input reset,
- input [1:0] feedback,
+ input [2:0] feedback,
input load_acc,
input unsigned_a,
input unsigned_b,
@@ -709,14 +709,359 @@
input register_inputs
);
- parameter [19:0] COEFF_0 = 20'd0;
- parameter [19:0] COEFF_1 = 20'd0;
- parameter [19:0] COEFF_2 = 20'd0;
- parameter [19:0] COEFF_3 = 20'd0;
+ parameter [NBITS_COEF-1:0] COEFF_0 = 20'd0;
+ parameter [NBITS_COEF-1:0] COEFF_1 = 20'd0;
+ parameter [NBITS_COEF-1:0] COEFF_2 = 20'd0;
+ parameter [NBITS_COEF-1:0] COEFF_3 = 20'd0;
+
+ localparam NBITS_ACC = 64;
+ localparam NBITS_A = 20;
+ localparam NBITS_B = 18;
+ localparam NBITS_Z = 38;
+ localparam NBITS_COEF = 20;
+ localparam NBITS_AF = 4;
+
+ wire [NBITS_Z-1:0] dsp_full_z;
+ wire [(NBITS_Z/2)-1:0] dsp_frac0_z;
+ wire [(NBITS_Z/2)-1:0] dsp_frac1_z;
+
+ wire [NBITS_B-1:0] dsp_full_dly_b;
+ wire [(NBITS_B/2)-1:0] dsp_frac0_dly_b;
+ wire [(NBITS_B/2)-1:0] dsp_frac1_dly_b;
+
+ assign z = f_mode ? {dsp_frac1_z, dsp_frac0_z} : dsp_full_z;
+ assign dly_b = f_mode ? {dsp_frac1_dly_b, dsp_frac0_dly_b} : dsp_full_dly_b;
+
+ // Output used when fmode == 1
+ dsp_t1_sim #(
+ .NBITS_A(NBITS_A/2),
+ .NBITS_B(NBITS_B/2),
+ .NBITS_ACC(NBITS_ACC/2),
+ .NBITS_Z(NBITS_Z/2),
+ .NBITS_COEF(NBITS_COEF/2),
+ .NBITS_AF(NBITS_AF/2)
+ ) dsp_frac0 (
+ .a_i(a[(NBITS_A/2)-1:0]),
+ .b_i(b[(NBITS_B/2)-1:0]),
+ .z_o(dsp_frac0_z),
+ .dly_b_o(dsp_frac0_dly_b),
+
+ .acc_fir_i(acc_fir[(NBITS_AF/2)-1:0]),
+ .feedback_i(feedback),
+ .load_acc_i(load_acc),
+
+ .unsigned_a_i(unsigned_a),
+ .unsigned_b_i(unsigned_b),
+
+ .clock_i(clk),
+ .reset_n_i(~reset),
+
+ .saturate_enable_i(saturate_enable),
+ .output_select_i(output_select),
+ .round_i(round),
+ .shift_right_i(shift_right),
+ .subtract_i(subtract),
+ .register_inputs_i(register_inputs),
+ .coef_0_i(COEFF_0[(NBITS_COEF/2)-1:0]),
+ .coef_1_i(COEFF_1[(NBITS_COEF/2)-1:0]),
+ .coef_2_i(COEFF_2[(NBITS_COEF/2)-1:0]),
+ .coef_3_i(COEFF_3[(NBITS_COEF/2)-1:0])
+ );
+
+ // Output used when fmode == 1
+ dsp_t1_sim #(
+ .NBITS_A(NBITS_A/2),
+ .NBITS_B(NBITS_B/2),
+ .NBITS_ACC(NBITS_ACC/2),
+ .NBITS_Z(NBITS_Z/2),
+ .NBITS_COEF(NBITS_COEF/2),
+ .NBITS_AF(NBITS_AF/2)
+ ) dsp_frac1 (
+ .a_i(a[NBITS_A-1:NBITS_A/2]),
+ .b_i(b[NBITS_B-1:NBITS_B/2]),
+ .z_o(dsp_frac1_z),
+ .dly_b_o(dsp_frac1_dly_b),
+
+ .acc_fir_i(acc_fir[NBITS_AF-1:NBITS_AF/2]),
+ .feedback_i(feedback),
+ .load_acc_i(load_acc),
+
+ .unsigned_a_i(unsigned_a),
+ .unsigned_b_i(unsigned_b),
+
+ .clock_i(clk),
+ .reset_n_i(~reset),
+
+ .saturate_enable_i(saturate_enable),
+ .output_select_i(output_select),
+ .round_i(round),
+ .shift_right_i(shift_right),
+ .subtract_i(subtract),
+ .register_inputs_i(register_inputs),
+ .coef_0_i(COEFF_0[NBITS_COEF-1:NBITS_COEF/2]),
+ .coef_1_i(COEFF_1[NBITS_COEF-1:NBITS_COEF/2]),
+ .coef_2_i(COEFF_2[NBITS_COEF-1:NBITS_COEF/2]),
+ .coef_3_i(COEFF_3[NBITS_COEF-1:NBITS_COEF/2])
+ );
+
+ // Output used when fmode == 0
+ dsp_t1_sim #(
+ .NBITS_A(NBITS_A),
+ .NBITS_B(NBITS_B),
+ .NBITS_ACC(NBITS_ACC),
+ .NBITS_Z(NBITS_Z),
+ .NBITS_COEF(NBITS_COEF),
+ .NBITS_AF(NBITS_AF)
+ ) dsp_full (
+ .a_i(a),
+ .b_i(b),
+ .z_o(dsp_full_z),
+ .dly_b_o(dsp_full_dly_b),
+
+ .acc_fir_i(acc_fir),
+ .feedback_i(feedback),
+ .load_acc_i(load_acc),
+
+ .unsigned_a_i(unsigned_a),
+ .unsigned_b_i(unsigned_b),
+
+ .clock_i(clk),
+ .reset_n_i(~reset),
+
+ .saturate_enable_i(saturate_enable),
+ .output_select_i(output_select),
+ .round_i(round),
+ .shift_right_i(shift_right),
+ .subtract_i(subtract),
+ .register_inputs_i(register_inputs),
+ .coef_0_i(COEFF_0),
+ .coef_1_i(COEFF_1),
+ .coef_2_i(COEFF_2),
+ .coef_3_i(COEFF_3)
+ );
+endmodule
+
+module dsp_t1_sim # (
+ parameter NBITS_ACC = 64,
+ parameter NBITS_A = 20,
+ parameter NBITS_B = 18,
+ parameter NBITS_Z = 38,
+ parameter NBITS_COEF = 20,
+ parameter NBITS_AF = 4
+)(
+ input [NBITS_A-1:0] a_i,
+ input [NBITS_B-1:0] b_i,
+ output [NBITS_Z-1:0] z_o,
+ output [NBITS_B-1:0] dly_b_o,
+
+ input [NBITS_AF-1:0] acc_fir_i,
+ input [2:0] feedback_i,
+ input load_acc_i,
+
+ input unsigned_a_i,
+ input unsigned_b_i,
+
+ input clock_i,
+ input reset_n_i,
+
+ input saturate_enable_i,
+ input [2:0] output_select_i,
+ input round_i,
+ input [5:0] shift_right_i,
+ input subtract_i,
+ input register_inputs_i,
+ input [NBITS_COEF-1:0] coef_0_i,
+ input [NBITS_COEF-1:0] coef_1_i,
+ input [NBITS_COEF-1:0] coef_2_i,
+ input [NBITS_COEF-1:0] coef_3_i
+);
+
+// FIXME: The version of Icarus Verilog from Conda seems not to recognize the
+// $error macro. Disable this sanity check for now because of that.
+`ifndef __ICARUS__
+ if (NBITS_ACC < NBITS_A + NBITS_B)
+ $error("NBITS_ACC must be > NBITS_A + NBITS_B");
+`endif
+
+ // Input registers
+ reg [NBITS_A-1:0] r_a;
+ reg [NBITS_B-1:0] r_b;
+ reg [NBITS_AF-1:0] r_acc_fir;
+ reg r_unsigned_a;
+ reg r_unsigned_b;
+ reg r_load_acc;
+ reg [2:0] r_feedback;
+ reg [5:0] r_shift_d1;
+ reg [5:0] r_shift_d2;
+ reg r_subtract;
+ reg r_sat;
+ reg r_rnd;
+
+ always @(posedge clock_i or negedge reset_n_i) begin
+ if (~reset_n_i) begin
+
+ r_a <= 'h0;
+ r_b <= 'h0;
+
+ r_acc_fir <= 0;
+ r_unsigned_a <= 0;
+ r_unsigned_b <= 0;
+ r_feedback <= 0;
+ r_shift_d1 <= 0;
+ r_shift_d2 <= 0;
+ r_subtract <= 0;
+ r_load_acc <= 0;
+ r_sat <= 0;
+ r_rnd <= 0;
+
+ end else begin
+
+ r_a <= a_i;
+ r_b <= b_i;
+
+ r_acc_fir <= acc_fir_i;
+ r_unsigned_a <= unsigned_a_i;
+ r_unsigned_b <= unsigned_b_i;
+ r_feedback <= feedback_i;
+ r_shift_d1 <= shift_right_i;
+ r_shift_d2 <= r_shift_d1;
+ r_subtract <= subtract_i;
+ r_load_acc <= load_acc_i;
+ r_sat <= r_sat;
+ r_rnd <= r_rnd;
+
+ end
+ end
+
+ // Registered / non-registered input path select
+ wire [NBITS_A-1:0] a = register_inputs_i ? r_a : a_i;
+ wire [NBITS_B-1:0] b = register_inputs_i ? r_b : b_i;
+
+ wire [NBITS_AF-1:0] acc_fir = register_inputs_i ? r_acc_fir : acc_fir_i;
+ wire unsigned_a = register_inputs_i ? r_unsigned_a : unsigned_a_i;
+ wire unsigned_b = register_inputs_i ? r_unsigned_b : unsigned_b_i;
+ wire [2:0] feedback = register_inputs_i ? r_feedback : feedback_i;
+ wire load_acc = register_inputs_i ? r_load_acc : load_acc_i;
+ wire subtract = register_inputs_i ? r_subtract : subtract_i;
+ wire sat = register_inputs_i ? r_sat : saturate_enable_i;
+ wire rnd = register_inputs_i ? r_rnd : round_i;
+
+ // Shift right control
+ wire [5:0] shift_d1 = register_inputs_i ? r_shift_d1 : shift_right_i;
+ wire [5:0] shift_d2 = output_select_i[1] ? shift_d1 : r_shift_d2;
+ //localparam SHIFT_SEL = {register_inputs_i, output_select_i[1]};
+ //wire [5:0] shift_right = (SHIFT_SEL == 2'b00) ? shift_right_i :
+ //(SHIFT_SEL == 2'b01) ? r_shift_d1 :
+ //(SHIFT_SEL == 2'b10) ? r_shift_d1 :
+ //[>(SHIFT_SEL == 2'b11) ?<] r_shift_d2;
+
+ // Multiplier
+ wire unsigned_mode = unsigned_a & unsigned_b;
+ wire [NBITS_A-1:0] mult_a;
+ assign mult_a = (feedback == 3'h0) ? a :
+ (feedback == 3'h1) ? a :
+ (feedback == 3'h2) ? a :
+ (feedback == 3'h3) ? acc[NBITS_A-1:0] :
+ (feedback == 3'h4) ? coef_0_i :
+ (feedback == 3'h5) ? coef_1_i :
+ (feedback == 3'h6) ? coef_2_i :
+ coef_3_i; // if feedback == 3'h7
+
+ wire [NBITS_B-1:0] mult_b = (feedback == 2'h2) ? {NBITS_B{1'b0}} : b;
+
+ wire [NBITS_A-1:0] mult_sgn_a = mult_a[NBITS_A-1];
+ wire [NBITS_A-1:0] mult_mag_a = (mult_sgn_a) ? (~mult_a + 1) : mult_a;
+ wire [NBITS_B-1:0] mult_sgn_b = mult_b[NBITS_B-1];
+ wire [NBITS_B-1:0] mult_mag_b = (mult_sgn_b) ? (~mult_b + 1) : mult_b;
+
+ wire [NBITS_A+NBITS_B-1:0] mult_mag = mult_mag_a * mult_mag_b;
+ wire mult_sgn = mult_sgn_a ^ mult_sgn_b;
+
+ wire [NBITS_A+NBITS_B-1:0] mult = (unsigned_a && unsigned_b) ?
+ (mult_a * mult_b) : (mult_sgn ? (~mult_mag + 1) : mult_mag);
+
+ // Sign extension
+ wire [NBITS_ACC-1:0] mult_xtnd = unsigned_mode ?
+ {{(NBITS_ACC-NBITS_A-NBITS_B){1'b0}}, mult[NBITS_A+NBITS_B-1:0]} :
+ {{(NBITS_ACC-NBITS_A-NBITS_B){mult[NBITS_A+NBITS_B-1]}}, mult[NBITS_A+NBITS_B-1:0]};
+
+ wire [NBITS_ACC-1:0] a_xtnd = (unsigned_a) ?
+ { {(NBITS_ACC - NBITS_A - NBITS_AF){1'b0}}, acc_fir, {a} } :
+ { {(NBITS_ACC - NBITS_A - NBITS_AF){acc_fir[NBITS_AF-1]}}, acc_fir, {a[NBITS_A-1:0]} };
+
+ // Adder
+ wire [NBITS_ACC-1:0] add_a = (subtract_i) ? (~mult_xtnd + 1) : mult_xtnd;
+ wire [NBITS_ACC-1:0] add_b = (feedback_i == 3'h0) ? acc :
+ (feedback_i == 3'h1) ? {{NBITS_ACC}{1'b0}} : a_xtnd;
+
+ wire [NBITS_ACC-1:0] add_o = add_a + add_b;
+
+ // Accumulator
+ reg [NBITS_ACC-1:0] acc;
+ always @(posedge clock_i or negedge reset_n_i)
+ if (~reset_n_i) acc <= 'h0;
+ else begin
+ if (load_acc)
+ acc <= add_o;
+ else
+ acc <= acc;
+ end
+
+ // Adder/accumulator output selection
+ wire [NBITS_ACC-1:0] acc_out = (output_select_i[1]) ? add_o : acc;
+
+ // Round, shift, saturate
+ wire [NBITS_ACC-1:0] acc_rnd = (rnd && (shift_right_i != 0)) ? (acc_out + ({{(NBITS_ACC-1){1'b0}}, 1'b1} << (shift_right_i - 1))) :
+ acc_out;
+
+ wire [NBITS_ACC-1:0] acc_shr = (unsigned_mode) ? (acc_rnd >> shift_right_i) :
+ (acc_rnd >>> shift_right_i);
+
+ wire [NBITS_ACC-1:0] acc_sat_u = (acc_shr[NBITS_ACC-1:NBITS_Z] != 0) ? {{(NBITS_ACC-NBITS_Z){1'b0}},{NBITS_Z{1'b1}}} :
+ {{(NBITS_ACC-NBITS_Z){1'b0}},{acc_shr[NBITS_Z-1:0]}};
+
+ wire [NBITS_ACC-1:0] acc_sat_s = ((|acc_shr[NBITS_ACC-1:NBITS_Z-1] == 1'b0) ||
+ (&acc_shr[NBITS_ACC-1:NBITS_Z-1] == 1'b1)) ? {{(NBITS_ACC-NBITS_Z){1'b0}},{acc_shr[NBITS_Z-1:0]}} :
+ {{(NBITS_ACC-NBITS_Z){1'b0}},{acc_shr[NBITS_ACC-1],{NBITS_Z-1{~acc_shr[NBITS_ACC-1]}}}};
+
+ wire [NBITS_ACC-1:0] acc_sat = (sat) ? ((unsigned_mode) ? acc_sat_u : acc_sat_s) : acc_shr;
+
+ // Output signals
+ wire [NBITS_Z-1:0] z0;
+ reg [NBITS_Z-1:0] z1;
+ wire [NBITS_Z-1:0] z2;
+
+ assign z0 = mult_xtnd[NBITS_Z-1:0];
+ assign z2 = acc_sat[NBITS_Z-1:0];
+
+ always @(posedge clock_i or negedge reset_n_i)
+ if (!reset_n_i)
+ z1 <= 0;
+ else begin
+ z1 <= (output_select_i == 3'b100) ? z0 : z2;
+ end
+
+ // Output mux
+ assign z_o = (output_select_i == 3'h0) ? z0 :
+ (output_select_i == 3'h1) ? z2 :
+ (output_select_i == 3'h2) ? z2 :
+ (output_select_i == 3'h3) ? z2 :
+ (output_select_i == 3'h4) ? z1 :
+ (output_select_i == 3'h5) ? z1 :
+ (output_select_i == 3'h6) ? z1 :
+ z1; // if output_select_i == 3'h7
+
+ // B input delayed passthrough
+ reg [NBITS_B-1:0] dly_b_o;
+
+ always @(posedge clock_i or negedge reset_n_i)
+ if (!reset_n_i)
+ dly_b_o <= 0;
+ else
+ dly_b_o <= b_i;
endmodule
-(* blackbox *) // TODO: add sim model
module dsp_t1_20x18x64 (
input [19:0] a_i,
input [17:0] b_i,
@@ -728,7 +1073,7 @@
input clock_i,
input reset_i,
- input [1:0] feedback_i,
+ input [2:0] feedback_i,
input load_acc_i,
input unsigned_a_i,
input unsigned_b_i,
@@ -746,9 +1091,38 @@
parameter [19:0] COEFF_2 = 20'd0;
parameter [19:0] COEFF_3 = 20'd0;
+ QL_DSP2 #(
+ .COEFF_0(COEFF_0),
+ .COEFF_1(COEFF_1),
+ .COEFF_2(COEFF_2),
+ .COEFF_3(COEFF_3)
+ ) dsp (
+ .a(a_i),
+ .b(b_i),
+ .z(z_o),
+ .dly_b(dly_b_o),
+
+ .f_mode(1'b0), // 20x18x64 DSP
+
+ .acc_fir(acc_fir_i),
+ .feedback(feedback_i),
+ .load_acc(load_acc_i),
+
+ .unsigned_a(unsigned_a_i),
+ .unsigned_b(unsigned_b_i),
+
+ .clk(clock_i),
+ .reset(reset_i),
+
+ .saturate_enable(saturate_enable_i),
+ .output_select(output_select_i),
+ .round(round_i),
+ .shift_right(shift_right_i),
+ .subtract(subtract_i),
+ .register_inputs(register_inputs_i)
+ );
endmodule
-(* blackbox *) // TODO: add sim model
module dsp_t1_10x9x32 (
input [ 9:0] a_i,
input [ 8:0] b_i,
@@ -760,7 +1134,7 @@
input clock_i,
input reset_i,
- input [1:0] feedback_i,
+ input [2:0] feedback_i,
input load_acc_i,
input unsigned_a_i,
input unsigned_b_i,
@@ -778,4 +1152,37 @@
parameter [9:0] COEFF_2 = 10'd0;
parameter [9:0] COEFF_3 = 10'd0;
+ wire [18:0] z_rem;
+ wire [8:0] dly_b_rem;
+
+ QL_DSP2 #(
+ .COEFF_0({10'd0, COEFF_0}),
+ .COEFF_1({10'd0, COEFF_1}),
+ .COEFF_2({10'd0, COEFF_2}),
+ .COEFF_3({10'd0, COEFF_3})
+ ) dsp (
+ .a({10'd0, a_i}),
+ .b({9'd0, b_i}),
+ .z({z_rem, z_o}),
+ .dly_b({dly_b_rem, dly_b_o}),
+
+ .f_mode(1'b1), // 10x9x32 DSP
+
+ .acc_fir({2'd0, acc_fir_i}),
+ .feedback(feedback_i),
+ .load_acc(load_acc_i),
+
+ .unsigned_a(unsigned_a_i),
+ .unsigned_b(unsigned_b_i),
+
+ .clk(clock_i),
+ .reset(reset_i),
+
+ .saturate_enable(saturate_enable_i),
+ .output_select(output_select_i),
+ .round(round_i),
+ .shift_right(shift_right_i),
+ .subtract(subtract_i),
+ .register_inputs(register_inputs_i)
+ );
endmodule
diff --git a/ql-qlf-plugin/tests/Makefile b/ql-qlf-plugin/tests/Makefile
index 1cc48ca..77938e3 100644
--- a/ql-qlf-plugin/tests/Makefile
+++ b/ql-qlf-plugin/tests/Makefile
@@ -26,6 +26,11 @@
qlf_k6n10f/dsp_macc
# qlf_k6n10_bram \
+SIM_TESTS = \
+ qlf_k6n10f/sim_dsp_mult \
+ qlf_k6n10f/sim_dsp_mult_r \
+ qlf_k6n10f/sim_dsp_fir
+
include $(shell pwd)/../../Makefile_test.common
consts_verify = true
diff --git a/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_fir/sim_dsp_fir.v b/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_fir/sim_dsp_fir.v
new file mode 100644
index 0000000..2f953c0
--- /dev/null
+++ b/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_fir/sim_dsp_fir.v
@@ -0,0 +1,143 @@
+// 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
+
+`include "qlf_k6n10f/cells_sim.v"
+
+module tb();
+
+ // Clock
+ reg clk;
+ initial clk <= 1'b0;
+ always #0.5 clk <= ~clk;
+
+ // Reset
+ reg rst;
+ initial begin
+ rst <= 1'b0;
+ #2 rst <= 1'b1;
+ #2 rst <= 1'b0;
+ end
+
+ // Filter control
+ reg [2:0] fcnt;
+ reg [3:0] dcnt;
+
+ initial begin
+ fcnt <= 0;
+ dcnt <= 0;
+ end
+
+ // MAC cycle counter
+ always @(posedge clk)
+ if (rst) fcnt <= 0;
+ else begin
+ if (fcnt == 4)
+ fcnt <= 0;
+ else
+ fcnt <= fcnt + 1;
+ end
+
+ wire stb = (fcnt == 4);
+
+ // Data address counter
+ always @(posedge clk)
+ if (rst) dcnt <= 0;
+ else if (stb) dcnt <= dcnt + 1;
+
+ // Filter coeffs (S0.19)
+ reg signed [19:0] coeff;
+ always @(*) case (fcnt)
+ 2'd0: coeff <= 20'h0000B;
+ 2'd1: coeff <= 20'h0000E;
+ 2'd2: coeff <= 20'h0000E;
+ 2'd3: coeff <= 20'h0000F;
+
+ default: coeff <= 20'h00000;
+ endcase
+
+ // Input data (S0.17)
+ reg signed [17:0] data;
+ always @(*) case (dcnt)
+ 'd0: data <= 18'h00400;
+ 'd1: data <= 18'h00000;
+ 'd2: data <= 18'h00000;
+ 'd3: data <= 18'h00000;
+ 'd4: data <= 18'h00000;
+ 'd5: data <= 18'h00000;
+ 'd6: data <= 18'h00000;
+ 'd7: data <= 18'h00000;
+ 'd8: data <= 18'h00800;
+ default data <= 18'h00000;
+ endcase
+
+ // UUT
+ wire signed [3:0] acc_fir_i = 4'h0;
+ wire signed [19:0] A = coeff;
+ wire signed [17:0] B = data;
+ wire signed [37:0] Z;
+
+ dsp_t1_sim # (
+ ) uut (
+ .clock_i (clk),
+ .reset_n_i (~rst),
+ .a_i ((!stb) ? A : 20'h0),
+ .b_i ((!stb) ? B : 18'h0),
+ .acc_fir_i ((!stb) ? acc_fir_i : 4'h0),
+ .unsigned_a_i (1'b0),
+ .unsigned_b_i (1'b0),
+ .feedback_i (stb),
+ .load_acc_i (1'b1),
+ .shift_right_i (6'd10),
+ .register_inputs_i (1'b0),
+ .output_select_i (3'h1),
+ .round_i (1'b1),
+ .saturate_enable_i (1'b1),
+ .subtract_i (1'b0),
+ .z_o (Z)
+ );
+
+ // Output counter
+ integer ocnt;
+ initial ocnt <= 0;
+
+ always @(posedge clk)
+ if (stb) ocnt <= ocnt + 1;
+
+ // Expected output data
+ reg signed [31:0] odata;
+ always @(*) case (ocnt)
+ 'd0: odata <= 32'h000036;
+ 'd1: odata <= 32'h000000;
+ 'd2: odata <= 32'h000000;
+ 'd3: odata <= 32'h000000;
+ 'd4: odata <= 32'h000000;
+ 'd5: odata <= 32'h000000;
+ 'd6: odata <= 32'h000000;
+ 'd7: odata <= 32'h000000;
+ 'd8: odata <= 32'h00006C;
+ default: odata <= 32'h000000;
+ endcase
+
+ // Error detection
+ wire error = stb && (odata !== Z[31:0]);
+
+ // Error counting
+ integer error_count;
+ initial error_count <= 0;
+ always @(posedge clk) begin
+ if (error) error_count <= error_count + 1;
+ end
+
+ // Simulation control / data dump
+ initial begin
+ $dumpfile(`VCD_FILE);
+ $dumpvars(0, tb);
+ #150 $finish_and_return( (error_count == 0) ? 0 : -1 );
+ end
+
+endmodule
diff --git a/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_mult/sim_dsp_mult.v b/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_mult/sim_dsp_mult.v
new file mode 100644
index 0000000..f162756
--- /dev/null
+++ b/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_mult/sim_dsp_mult.v
@@ -0,0 +1,71 @@
+// 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
+
+`include "qlf_k6n10f/cells_sim.v"
+`timescale 1ns/1ps
+
+module tb();
+
+ // Clock
+ reg clk;
+ initial clk <= 1'b0;
+ always #0.5 clk <= ~clk;
+
+ // Reset
+ reg rst;
+ initial begin
+ rst <= 1'b0;
+ #1 rst <= 1'b1;
+ #2 rst <= 1'b0;
+ end
+
+ // Input data / reference
+ reg signed [19:0] A;
+ reg signed [17:0] B;
+ reg signed [37:0] C;
+
+ always @(posedge clk) begin
+ A = $random;
+ B = $random;
+
+ C <= A * B;
+ end
+
+ // UUT
+ wire signed [37:0] Z;
+
+ dsp_t1_sim # (
+ ) uut (
+ .a_i (A),
+ .b_i (B),
+ .unsigned_a_i (1'h0),
+ .unsigned_b_i (1'h0),
+ .feedback_i (3'h0),
+ .register_inputs_i (1'h0),
+ .output_select_i (3'h0),
+ .z_o (Z)
+ );
+
+ // Error detection
+ wire error = (Z !== C);
+
+ // Error counting
+ integer error_count;
+ initial error_count <= 0;
+ always @(posedge clk) begin
+ if (error) error_count <= error_count + 1;
+ end
+
+ // Simulation control / data dump
+ initial begin
+ $dumpfile(`VCD_FILE);
+ $dumpvars(0, tb);
+ #10000 $finish_and_return( (error_count == 0) ? 0 : -1 );
+ end
+
+endmodule
diff --git a/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_mult_r/sim_dsp_mult_r.v b/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_mult_r/sim_dsp_mult_r.v
new file mode 100644
index 0000000..6187e1a
--- /dev/null
+++ b/ql-qlf-plugin/tests/qlf_k6n10f/sim_dsp_mult_r/sim_dsp_mult_r.v
@@ -0,0 +1,80 @@
+// 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
+
+`include "qlf_k6n10f/cells_sim.v"
+
+module tb();
+
+ // Clock
+ reg clk;
+ initial clk <= 1'b0;
+ always #0.5 clk <= ~clk;
+
+ // Reset
+ reg rst;
+ initial begin
+ rst <= 1'b0;
+ #1 rst <= 1'b1;
+ #2 rst <= 1'b0;
+ end
+
+ // Input data / reference
+ reg signed [19:0] A;
+ reg signed [17:0] B;
+ reg signed [37:0] C;
+
+ // Shift data change half a clock cycle
+ // to make registered inputs apparent
+ initial begin
+ forever begin
+ A = $random;
+ B = $random;
+
+ C <= A * B;
+ #1.5;
+ end
+ end
+
+ // UUT
+ wire signed [37:0] Z;
+
+ dsp_t1_sim # (
+ ) uut (
+ .a_i (A),
+ .b_i (B),
+ .unsigned_a_i (1'h0),
+ .unsigned_b_i (1'h0),
+ .feedback_i (3'h0),
+ .register_inputs_i (1'h1),
+ .output_select_i (3'h0),
+ .clock_i (clk),
+ .z_o (Z)
+ );
+
+ // Error detection
+ reg [37:0] r_C;
+ always @(posedge clk)
+ r_C <= C;
+
+ wire error = (Z !== r_C);
+
+ // Error counting
+ integer error_count;
+ initial error_count <= 0;
+ always @(posedge clk) begin
+ if (error) error_count <= error_count + 1;
+ end
+
+ // Simulation control / data dump
+ initial begin
+ $dumpfile(`VCD_FILE);
+ $dumpvars(0, tb);
+ #100 $finish_and_return( (error_count == 0) ? 0 : -1 );
+ end
+
+endmodule