Download bit_accelerator/testbenches/tb_bit_accelerator_v2.sv from Snapkitty/bit-string-accelerator: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/Snapkitty/bit-string-accelerator/resolve/main/bit_accelerator/testbenches/tb_bit_accelerator_v2.sv
- Command line
-
hf download hf://Snapkitty/bit-string-accelerator/bit_accelerator/testbenches/tb_bit_accelerator_v2.sv
-
curl -L -o tb_bit_accelerator_v2.sv https://huggingface.co/Snapkitty/bit-string-accelerator/resolve/main/bit_accelerator/testbenches/tb_bit_accelerator_v2.sv
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 | |