bit-string-accelerator / bit_accelerator /testbenches /tb_bit_accelerator_v2.sv
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
0ba037b verified
Raw History Blame Contribute Delete
11.9 kB
// Cycle-accurate testbench for bit_accelerator_v2.
// Inputs are driven on negedge; every posedge logs the pre-edge value of each
// signal, so log[c] is exactly what the DUT/memory saw during cycle c.
// Spec (1-cycle-latency memory): accept@a, READ_REQ@a+1, READ_RESP@a+2, RESULT_VALID@a+3.
module tb_bit_accelerator_v2;
localparam int MAXC = 4096;
logic clk = 1'b0;
logic reset;
logic op_valid, op_ready;
logic [63:0] base_address, bit_offset;
logic [2:0] operation;
logic result_valid, result_bit, error;
logic mem_valid, mem_write, mem_ready;
logic [63:0] mem_addr, mem_wdata, mem_rdata;
logic [7:0] mem_wstrb;
logic mem_rvalid = 1'b0, mem_fault = 1'b0;
bit_accelerator_v2 dut (.*);
always #5 clk = ~clk;
// ---------------- memory model (1-cycle read latency) ----------------
logic [63:0] memory [0:1023];
bit stall_en;
int stall_lo, stall_hi;
int cyc = 0;
assign mem_ready = !(stall_en && cyc >= stall_lo && cyc <= stall_hi);
bit fault_next_read = 1'b0; // answer the next accepted read with mem_fault
always_ff @(posedge clk) begin
mem_rvalid <= 1'b0;
mem_fault <= 1'b0;
if (mem_valid && mem_ready) begin
if (mem_write) begin
for (int i = 0; i < 8; i++)
if (mem_wstrb[i]) memory[mem_addr[12:3]][i*8 +: 8] <= mem_wdata[i*8 +: 8];
end else if (fault_next_read) begin
mem_fault <= 1'b1;
fault_next_read <= 1'b0;
end else begin
mem_rvalid <= 1'b1;
mem_rdata <= memory[mem_addr[12:3]];
end
end
end
// ---------------- per-cycle log ----------------
logic l_opv [0:MAXC-1], l_opr [0:MAXC-1];
logic l_mv [0:MAXC-1], l_mw [0:MAXC-1], l_mr [0:MAXC-1];
logic l_rv [0:MAXC-1], l_res [0:MAXC-1], l_bit [0:MAXC-1], l_err [0:MAXC-1];
logic [63:0] l_wd [0:MAXC-1];
always @(posedge clk) begin
l_opv[cyc] = op_valid; l_opr[cyc] = op_ready;
l_mv[cyc] = mem_valid; l_mw[cyc] = mem_write; l_mr[cyc] = mem_ready;
l_rv[cyc] = mem_rvalid; l_res[cyc] = result_valid; l_bit[cyc] = result_bit;
l_err[cyc] = error;
l_wd[cyc] = mem_wdata;
cyc <= cyc + 1;
end
// ---------------- checking helpers ----------------
int fails = 0, checks = 0;
task automatic check(input bit cond, input string msg);
checks++;
if (!cond) begin
fails++;
$display(" FAIL: %s", msg);
end
endtask
task automatic init_mem();
for (int i = 0; i < 1024; i++) memory[i] = {32'hA5A50000 | i[15:0], 32'h0000F00D | (i[15:0] << 16)};
memory[0] = 64'h0000_0000_0000_0020; // bit 5 set only
memory[1] = 64'h0000_0000_0000_0000;
endtask
task automatic do_reset();
@(negedge clk);
reset = 1'b1; op_valid = 1'b0;
@(negedge clk);
reset = 1'b0;
@(negedge clk);
endtask
// Issue one op; returns the cycle index at which it was accepted.
task automatic issue(input logic [2:0] op, input logic [63:0] base, input logic [63:0] off,
output int acc);
@(negedge clk);
op_valid = 1'b1; operation = op; base_address = base; bit_offset = off;
acc = cyc; // op_valid/op_ready are sampled at the next posedge, cycle == cyc
@(negedge clk);
op_valid = 1'b0;
endtask
task automatic wait_cycles(input int n);
repeat (n) @(negedge clk);
endtask
function automatic int count_results(input int lo, input int hi);
int n = 0;
for (int c = lo; c <= hi; c++) if (l_res[c]) n++;
return n;
endfunction
// ---------------- tests ----------------
task automatic t_get(input string name, input logic [63:0] base, input logic [63:0] off, input bit exp_bit);
int a;
$display("TEST %s", name);
do_reset();
issue(3'b000, base, off, a);
wait_cycles(10);
check(l_opv[a] && l_opr[a], "op accepted at a");
check(l_mv[a+1] && !l_mw[a+1], "READ_REQUEST at a+1");
check(!l_mv[a+2], "no second request at a+2");
check(l_rv[a+2], "READ_RESPONSE at a+2");
check(l_res[a+3], "RESULT_VALID at a+3");
check(l_bit[a+3] == exp_bit, "result_bit value");
check(!l_res[a+4], "result_valid is one-cycle (low at a+4)");
check(count_results(a, a+10) == 1, "exactly one result pulse");
check(l_opr[a+4] && !l_opr[a+1] && !l_opr[a+3], "op_ready low while busy, high after result");
endtask
task automatic t_modify(input string name, input logic [2:0] op, input logic [63:0] base,
input logic [63:0] off, input logic [63:0] exp_word);
int a;
logic [63:0] widx;
widx = ((base << 3) + off) >> 6;
$display("TEST %s", name);
do_reset();
issue(op, base, off, a);
wait_cycles(12);
check(l_mv[a+1] && !l_mw[a+1], "READ_REQUEST at a+1");
check(l_rv[a+2], "READ_RESPONSE at a+2");
check(!l_mv[a+3], "MODIFY: no mem request at a+3");
check(l_mv[a+4] && l_mw[a+4] && l_mr[a+4], "WRITE accepted (valid&&write&&ready) at a+4");
check(!l_mv[a+5], "no request after accepted write at a+5");
check(l_res[a+6], "RESULT_VALID at a+6");
check(count_results(a, a+12) == 1, "exactly one result pulse");
check(memory[widx[9:0]] == exp_word, "memory word after RMW");
endtask
task automatic t_write_stall();
int a;
logic [63:0] mem0_pre;
$display("TEST write stall (mem_ready low a+4..a+6)");
do_reset();
mem0_pre = memory[0];
@(negedge clk);
stall_en = 1'b1; stall_lo = cyc + 1 + 4; stall_hi = cyc + 1 + 6; // issue() accepts at cyc+1
issue(3'b010, 64'd0, 64'd0, a);
check(a + 4 == stall_lo, "stall window aligned to write request");
wait_cycles(14);
stall_en = 1'b0;
check(l_mv[a+4] && l_mw[a+4] && !l_mr[a+4], "write request held, not accepted a+4");
check(l_mv[a+5] && l_mw[a+5] && !l_mr[a+5], "write request held a+5");
check(l_mv[a+6] && l_mw[a+6] && !l_mr[a+6], "write request held a+6");
check(l_wd[a+4] == l_wd[a+5] && l_wd[a+5] == l_wd[a+6], "wdata stable during stall");
check(l_mv[a+7] && l_mw[a+7] && l_mr[a+7], "write accepted at a+7");
check(!l_mv[a+8], "no request after accept");
check(count_results(a, a+7) == 0, "no result mem0_pre write accepted");
check(l_res[a+9], "RESULT_VALID at a+9 (2 cycles after accept)");
check(count_results(a, a+14) == 1, "exactly one result pulse");
check(memory[0] == (mem0_pre | 64'h1), "write committed once");
endtask
task automatic t_read_stall();
int a;
$display("TEST read stall (mem_ready low a+1..a+2)");
do_reset();
@(negedge clk);
stall_en = 1'b1; stall_lo = cyc + 1 + 1; stall_hi = cyc + 1 + 2;
issue(3'b000, 64'd0, 64'd5, a);
wait_cycles(10);
stall_en = 1'b0;
check(l_mv[a+1] && !l_mr[a+1] && l_mv[a+2] && !l_mr[a+2], "read request held during stall");
check(l_mv[a+3] && l_mr[a+3], "read accepted at a+3");
check(l_rv[a+4], "read response at a+4");
check(l_res[a+5] && l_bit[a+5], "RESULT_VALID at a+5 with bit=1");
check(count_results(a, a+10) == 1, "exactly one result pulse");
endtask
// Reset asserted for one cycle at cycle (a + k): operation must be cancelled.
task automatic t_reset_at(input int k, input string where);
int a;
logic [63:0] mem0_pre;
$display("TEST reset cancels op during %s (reset@a+%0d)", where, k);
do_reset();
mem0_pre = memory[0];
issue(3'b010, 64'd0, 64'd0, a);
while (cyc < a + k) @(negedge clk);
reset = 1'b1;
@(negedge clk);
reset = 1'b0;
wait_cycles(12);
check(count_results(a, a+16) == 0, "no result_valid for cancelled op");
check(!mem_valid && !result_valid, "idle outputs after reset");
check(op_ready, "op_ready after reset");
if (k <= 4)
check(memory[0] == mem0_pre, "no write committed (cancelled before write accepted)");
// a fresh op must work normally afterwards
begin
int b;
issue(3'b000, 64'd0, 64'd5, b);
wait_cycles(8);
check(l_res[b+3] && l_bit[b+3], "fresh op after reset completes normally");
end
endtask
// Read fault: the op ends at a+3 with error=1 in the result cycle and never writes.
task automatic t_read_fault(input string name, input logic [2:0] op);
int a, b;
logic [63:0] mem0_pre;
$display("TEST read fault on %s", name);
do_reset();
mem0_pre = memory[0];
@(negedge clk);
fault_next_read = 1'b1;
issue(op, 64'd0, 64'd5, a);
wait_cycles(10);
check(l_mv[a+1] && !l_mw[a+1], "READ_REQUEST at a+1");
check(!l_rv[a+2], "no read data on a faulted read");
check(l_res[a+3], "RESULT_VALID at a+3");
check(l_err[a+3], "error high in the result cycle");
check(count_results(a, a+10) == 1, "exactly one result pulse");
for (int c = a + 1; c <= a + 10; c++)
check(!(l_mv[c] && l_mw[c]), "no write after a read fault");
check(memory[0] == mem0_pre, "memory unchanged after a read fault");
// The next operation clears error.
issue(3'b000, 64'd0, 64'd5, b);
wait_cycles(8);
check(l_res[b+3] && !l_err[b+3] && l_bit[b+3], "next op completes with error=0");
endtask
// Opcodes 101..111 are not defined; v2 executes them as a read with no write.
task automatic t_undefined_opcode(input logic [2:0] op);
int a;
logic [63:0] mem0_pre;
$display("TEST undefined opcode %b reads and never writes", op);
do_reset();
mem0_pre = memory[0];
issue(op, 64'd0, 64'd5, a);
wait_cycles(10);
check(l_res[a+3] && l_bit[a+3] && !l_err[a+3], "completes like GET at a+3");
for (int c = a + 1; c <= a + 10; c++)
check(!(l_mv[c] && l_mw[c]), "no write for an undefined opcode");
check(memory[0] == mem0_pre, "memory unchanged");
endtask
initial begin
reset = 1'b1; op_valid = 1'b0; operation = '0; base_address = '0; bit_offset = '0;
stall_en = 1'b0; stall_lo = 0; stall_hi = 0;
init_mem();
t_get("GET bit5 (set)", 64'd0, 64'd5, 1'b1);
t_get("GET bit4 (clear)", 64'd0, 64'd4, 1'b0);
t_get("GET word1 bit 0 via off=64", 64'd0, 64'd64, 1'b0);
t_modify("SET bit0", 3'b010, 64'd0, 64'd0, 64'h21);
init_mem();
t_modify("CLEAR bit5", 3'b011, 64'd0, 64'd5, 64'h00);
init_mem();
t_modify("TOGGLE bit5", 3'b100, 64'd0, 64'd5, 64'h00);
init_mem();
t_modify("SET cross-word (off=70 -> word1 bit6)", 3'b010, 64'd0, 64'd70, 64'h40);
init_mem();
t_modify("SET base=8 off=3 -> word1 bit3", 3'b010, 64'd8, 64'd3, 64'h08);
init_mem();
t_write_stall();
init_mem();
t_read_stall();
init_mem();
t_reset_at(1, "READ_REQUEST");
init_mem();
t_reset_at(2, "READ_WAIT");
init_mem();
t_reset_at(3, "MODIFY");
init_mem();
t_reset_at(4, "WRITE_REQUEST");
init_mem();
t_reset_at(5, "WRITE_WAIT");
init_mem();
t_read_fault("GET", 3'b000);
init_mem();
t_read_fault("SET", 3'b010);
init_mem();
t_read_fault("TOGGLE", 3'b100);
init_mem();
t_undefined_opcode(3'b101);
t_undefined_opcode(3'b111);
$display("\nchecks=%0d fails=%0d", checks, fails);
if (fails != 0) $fatal(1, "FAILED");
$display("ALL PASS");
$finish;
end
initial begin
#2000000;
$fatal(1, "TIMEOUT");
end
endmodule