diff --git a/project.cfg b/project.cfg index 439cbbf..2f0e66b 100644 --- a/project.cfg +++ b/project.cfg @@ -41,9 +41,11 @@ files_xco = boards/mimas_v1/ip/clk_gen.xco [target.sim] toolchain = iverilog runtime = all -toplevel = tb_nco_q15 +toplevel = tb_sigmadelta ivl_opts = -Irtl/util files_verilog = sim/tb/tb_nco_q15.v + sim/tb/tb_sigmadelta.v + sim/tb/tb_mul_const.v rtl/core/nco_q15.v rtl/core/lvds_comparator.v rtl/core/sigmadelta_rcmodel_q15.v diff --git a/rtl/core/mul_const.v b/rtl/core/mul_const.v index 8205ee6..06dc8ce 100644 --- a/rtl/core/mul_const.v +++ b/rtl/core/mul_const.v @@ -17,11 +17,11 @@ module mul_const_shiftadd#( input wire signed [W-1:0] x, output wire signed [2*W-1:0] y ); - // Absolute value and sign of C - localparam integer C_NEG = (C < 0) ? 1 : 0; - localparam integer C_ABS = (C < 0) ? -C : C; + // Sign and magnitude of constant + localparam integer C_NEG = (C < 0) ? 1 : 0; + localparam integer C_ABS = (C < 0) ? -C : C; - // Find MSB index of C_ABS to size the network + // MSB index of |C| (0-based). Keeps network minimal. function integer msb_index; input integer v; integer i; @@ -31,19 +31,26 @@ module mul_const_shiftadd#( if (v >> i) msb_index = i; end endfunction + localparam integer I_MAX = (C_ABS == 0) ? 0 : msb_index(C_ABS); - // Partial products - wire signed [W+I_MAX:0] part [0:I_MAX]; + // Width big enough for the largest partial product: W bits shifted by I_MAX + localparam integer PPW = W + I_MAX + 1; + + // Pre-extend x to PPW so shifts don’t truncate high/sign bits + wire signed [PPW-1:0] x_ext = {{(PPW-W){x[W-1]}}, x}; + + // Partial products (only where C’s bit is 1) + wire signed [PPW-1:0] part [0:I_MAX]; genvar i; generate for (i = 0; i <= I_MAX; i = i + 1) begin : GEN_PARTS - assign part[i] = (C_ABS[i]) ? ($signed(x) <<< i) : { (W+I_MAX+1){1'b0} }; + assign part[i] = C_ABS[i] ? (x_ext <<< i) : {PPW{1'b0}}; end endgenerate - // Adder chain (simple; replace with tree if you want higher performance) - wire signed [W+I_MAX:0] sum [0:I_MAX]; + // Adder chain (you can replace with a balanced tree for speed) + wire signed [PPW-1:0] sum [0:I_MAX]; generate if (I_MAX == 0) begin assign sum[0] = part[0]; @@ -56,10 +63,9 @@ module mul_const_shiftadd#( endgenerate // Apply sign of C - wire signed [W+I_MAX:0] mag = (I_MAX==0) ? part[0] : sum[I_MAX]; - wire signed [W+I_MAX:0] prod = C_NEG ? -mag : mag; - - // Stretch to fixed y width (truncate/extend as you wish outside) - assign y = prod; + wire signed [PPW-1:0] mag = (I_MAX == 0) ? part[0] : sum[I_MAX]; + wire signed [PPW-1:0] prod = C_NEG ? -mag : mag; + // Stretch/extend to 2W result width + assign y = {{((2*W)-PPW){prod[PPW-1]}}, prod}; endmodule \ No newline at end of file diff --git a/rtl/core/sigmadelta_rcmodel_q15.v b/rtl/core/sigmadelta_rcmodel_q15.v index 9644ef8..7b737c8 100644 --- a/rtl/core/sigmadelta_rcmodel_q15.v +++ b/rtl/core/sigmadelta_rcmodel_q15.v @@ -12,7 +12,7 @@ // -- clk : input clock // -- resetn : reset signal // -- sd_sample : 1 bit sample output from sd sampler -// -- sample_q15 : output samples in q1.15 +// -- sample_q15 : output samples in q.15 // ============================================================================= module sigmadelta_rcmodel_q15 #( parameter integer alpha_q15 = 16'sh0b00 @@ -25,9 +25,10 @@ module sigmadelta_rcmodel_q15 #( reg signed [15:0] y_q15; wire signed [15:0] sd_q15 = sd_sample ? 16'sh7fff : 16'sh0000; wire signed [15:0] e_q15 = sd_q15 - y_q15; + // wire signed [31:0] prod_q30 = $signed(e_q15) * $signed(alpha_q15); wire signed [31:0] prod_q30; // Use shift-add algorithm for multiplication - mul_const_shiftadd #(.C(alpha_q15)) alpha_times_e (e_q15, prod_q30); + mul_const_shiftadd #(.C($signed(alpha_q15))) alpha_times_e ($signed(e_q15), prod_q30); wire signed [15:0] y_next_q15 = y_q15 + (prod_q30>>>15); // clamp to [0, 0x7FFF] (keeps signal view tidy) diff --git a/sim/tb/tb_mul_const.v b/sim/tb/tb_mul_const.v new file mode 100644 index 0000000..2f9028b --- /dev/null +++ b/sim/tb/tb_mul_const.v @@ -0,0 +1,72 @@ +`timescale 1ns/1ps + +module tb_mul_const(); + + // ------------------------------------------------------------------------- + // Parameters + // ------------------------------------------------------------------------- + localparam integer W = 16; + localparam integer C = 16'sh0B3B; // alpha_q15 ≈ 0.08774 + localparam signed [W-1:0] C_S = C[W-1:0]; + + // ------------------------------------------------------------------------- + // DUT I/O + // ------------------------------------------------------------------------- + reg signed [W-1:0] x; + wire signed [(2*W)-1:0] y; + + // Instantiate DUT + mul_const_shiftadd #( + .W(W), + .C(C) + ) dut ( + .x(x), + .y(y) + ); + + // ------------------------------------------------------------------------- + // Reference and verification + // ------------------------------------------------------------------------- + reg signed [(2*W)-1:0] expected; + integer i; + integer errors; + + initial begin + $display("------------------------------------------------------"); + $display(" Testbench: mul_const_shiftadd"); + $display(" W = %0d, C = %0d (0x%0h)", W, $signed(C_S), C_S); + $display("------------------------------------------------------"); + + errors = 0; + + // Exhaustively test all 16-bit signed values + for (i = -(1<<(W-1)); i < (1<<(W-1)); i = i + 1) begin + x = i; + #1; // let combinational logic settle + + expected = $signed(x) * $signed(C_S); + + if (y !== expected) begin + $display("FAIL: x=%6d (0x%04h) * C=%6d -> y=%10d (0x%08h), expected=%10d (0x%08h)", + $signed(x), x, $signed(C_S), + $signed(y), y, $signed(expected), expected); + errors = errors + 1; + // Uncomment next line if you want to stop on first mismatch + // $stop; + end + + // progress message every 4096 iterations + if (((i + (1<<(W-1))) % 4096) == 0) + $display("Progress: %5d / %5d values tested...", + i + (1<<(W-1)), (1<