test: add vector-driven Verilator testbench with Python model cross-check
Add gen_verilator_vectors.py to convert test_vectors.json into hex files for $readmemh, and tb_ldpc_vectors.sv to drive 20 test vectors through the RTL decoder and verify bit-exact matching against the Python model. All 11 converged vectors pass with exact decoded word, convergence flag, and zero syndrome weight. All 9 non-converged vectors match the Python model's decoded word, iteration count, and syndrome weight exactly. Three RTL bugs fixed in ldpc_decoder_core.sv during testing: - Magnitude overflow: -32 (6'b100000) negation overflowed 5-bit field to 0; now clamped to max magnitude 31 - Converged flag persistence: moved clearing from IDLE to INIT so host can read results after decode completes - msg_cn2vn zeroing: bypass stale array reads on first iteration (iter_cnt==0) to avoid Verilator scheduling issues with large 3D array initialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -201,8 +201,9 @@ module ldpc_decoder_core #(
|
||||
iter_cnt <= '0;
|
||||
row_idx <= '0;
|
||||
col_idx <= '0;
|
||||
converged <= 1'b0;
|
||||
syndrome_ok <= 1'b0;
|
||||
// Note: converged, iter_used, syndrome_weight, decoded_bits
|
||||
// are NOT cleared here so the host can read them after decode.
|
||||
// They are cleared in INIT when a new decode starts.
|
||||
end
|
||||
|
||||
INIT: begin
|
||||
@@ -214,10 +215,12 @@ module ldpc_decoder_core #(
|
||||
for (int r = 0; r < M_BASE; r++)
|
||||
for (int c = 0; c < N_BASE; c++)
|
||||
for (int z = 0; z < Z; z++)
|
||||
msg_cn2vn[r][c][z] <= '0;
|
||||
msg_cn2vn[r][c][z] <= {Q{1'b0}};
|
||||
row_idx <= '0;
|
||||
col_idx <= '0;
|
||||
iter_cnt <= '0;
|
||||
converged <= 1'b0;
|
||||
syndrome_ok <= 1'b0;
|
||||
end
|
||||
|
||||
LAYER_READ: begin
|
||||
@@ -235,7 +238,12 @@ module ldpc_decoder_core #(
|
||||
|
||||
shifted_z = (z + H_BASE[row_idx][col_idx]) % Z;
|
||||
bit_idx = int'(col_idx) * Z + shifted_z;
|
||||
old_msg = msg_cn2vn[row_idx][col_idx][z];
|
||||
// On first iteration (iter_cnt==0), old messages are zero
|
||||
// since no CN update has run yet. Use 0 directly rather
|
||||
// than reading msg_cn2vn, which may not be reliably zeroed
|
||||
// by the INIT state in all simulation tools.
|
||||
old_msg = (iter_cnt == 0) ?
|
||||
{Q{1'b0}} : msg_cn2vn[row_idx][col_idx][z];
|
||||
belief_val = beliefs[bit_idx];
|
||||
|
||||
vn_to_cn[col_idx][z] <= sat_sub(belief_val, old_msg);
|
||||
@@ -363,10 +371,19 @@ module ldpc_decoder_core #(
|
||||
logic signed [Q-1:0] outs [DC];
|
||||
|
||||
// Extract signs and magnitudes
|
||||
// Note: -32 (100000) has magnitude 32 which overflows 5-bit field to 0.
|
||||
// Clamp to 31 (max representable magnitude) to avoid corruption.
|
||||
sign_xor = 1'b0;
|
||||
for (int i = 0; i < DC; i++) begin
|
||||
logic [Q-1:0] abs_val; // wider to detect overflow
|
||||
signs[i] = in[i][Q-1];
|
||||
mags[i] = in[i][Q-1] ? (~in[i][Q-2:0] + 1) : in[i][Q-2:0];
|
||||
if (in[i][Q-1]) begin
|
||||
abs_val = ~in[i] + 1'b1;
|
||||
// If abs_val overflowed (input was most negative), clamp
|
||||
mags[i] = (abs_val[Q-1]) ? {(Q-1){1'b1}} : abs_val[Q-2:0];
|
||||
end else begin
|
||||
mags[i] = in[i][Q-2:0];
|
||||
end
|
||||
sign_xor = sign_xor ^ signs[i];
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user