File size: 11,661 Bytes
0ba037b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | // Self-checking testbench for rtl/bit_accelerator.sv.
//
// A memory slave with randomized backpressure, read latency and fault injection
// drives the DUT. Every accepted operation is checked against a reference model:
// - mem_addr of every request equals ((base<<3)+offset)>>6<<3 (64-bit wrap)
// - write data equals the expected modified word, with all byte strobes set
// - GET/TEST/illegal opcodes never write
// - exactly one result_valid pulse per operation, with error and result_bit
// valid in that same cycle
// - op_ready is low from acceptance until the result pulse
// - reset cancels an in-flight operation: no result, no write after reset
// Directed cases run first, then a seeded random phase (+seed=N, +ops=N).
`timescale 1ns/1ps
module tb_bit_accelerator;
localparam int DEPTH = 64; // memory words, indexed by mem_addr[8:3]
localparam int TIMEOUT = 200; // cycles before an operation is declared hung
logic clk = 1'b0;
logic reset = 1'b1;
logic op_valid = 1'b0, op_ready;
logic [63:0] base_address = '0, bit_offset = '0;
logic [2:0] operation = '0;
logic result_valid, result_bit, error;
logic mem_valid, mem_write;
logic [63:0] mem_addr, mem_wdata;
logic [7:0] mem_wstrb;
logic mem_ready = 1'b0, mem_rvalid = 1'b0, mem_fault = 1'b0;
logic [63:0] mem_rdata = '0;
bit_accelerator dut (.*);
always #5 clk = ~clk;
// ---------------------------------------------------------------- memory slave
logic [63:0] mem [0:DEPTH-1];
integer seed = 1;
integer ready_pct = 100; // probability (%) that mem_ready is high
integer max_lat = 0; // read response latency, 0..max_lat extra cycles
bit fault_read_next = 1'b0; // fault the next accepted read
bit fault_write_next = 1'b0; // fault the next accepted write
bit rd_pending = 1'b0, rd_fault = 1'b0;
integer rd_delay = 0;
logic [63:0] rd_addr = '0;
integer writes_seen = 0;
function automatic integer rnd(input integer n); // 0..n-1
rnd = (n <= 1) ? 0 : ($unsigned($random(seed)) % n);
endfunction
always @(posedge clk) begin
mem_rvalid <= 1'b0;
mem_fault <= 1'b0;
mem_ready <= (rnd(100) < ready_pct);
if (reset) begin
rd_pending <= 1'b0;
end else begin
if (mem_valid && mem_ready && !mem_write) begin
rd_pending <= 1'b1;
rd_delay <= rnd(max_lat + 1);
rd_addr <= mem_addr;
rd_fault <= fault_read_next;
fault_read_next <= 1'b0;
end
if (rd_pending) begin
if (rd_delay == 0) begin
rd_pending <= 1'b0;
if (rd_fault) mem_fault <= 1'b1;
else begin
mem_rvalid <= 1'b1;
mem_rdata <= mem[rd_addr[8:3]];
end
end else rd_delay <= rd_delay - 1;
end
if (mem_valid && mem_ready && mem_write) begin
writes_seen <= writes_seen + 1;
if (fault_write_next) begin
mem_fault <= 1'b1; // seen by the DUT in WRITE_WAIT
fault_write_next <= 1'b0;
end else begin
mem[mem_addr[8:3]] <= mem_wdata;
end
end
end
end
// -------------------------------------------------------------- bookkeeping
integer checks = 0, fails = 0;
task automatic check(input bit cond, input string what);
checks++;
if (!cond) begin
fails++;
$display("FAIL @%0t: %s", $time, what);
end
endtask
// Request-level checks against the operation currently in flight.
bit in_flight = 1'b0;
logic [63:0] exp_addr;
logic [63:0] exp_wdata;
bit exp_write;
integer results_seen = 0;
always @(posedge clk) begin
if (!reset) begin
if (mem_valid) begin
check(in_flight, "memory request while no operation is in flight");
check(mem_addr === exp_addr,
$sformatf("mem_addr %h, expected %h", mem_addr, exp_addr));
if (mem_write) begin
check(exp_write, "write issued for an operation that must not write");
check(mem_wstrb === 8'hFF, $sformatf("mem_wstrb %h, expected ff", mem_wstrb));
check(mem_wdata === exp_wdata,
$sformatf("mem_wdata %h, expected %h", mem_wdata, exp_wdata));
end
end
if (in_flight && !result_valid) check(!op_ready, "op_ready high while busy");
if (result_valid) results_seen <= results_seen + 1;
end
end
// ------------------------------------------------------------- reference model
function automatic logic [63:0] eff_bit(input logic [63:0] b, input logic [63:0] o);
eff_bit = (b << 3) + o;
endfunction
// Run one operation and check its result. fault_rd / fault_wr inject a fault on
// the read or write of this operation. Returns when result_valid has been seen.
task automatic run_op(input logic [63:0] b, input logic [63:0] o, input logic [2:0] op,
input bit fault_rd, input bit fault_wr);
logic [63:0] eb, word, onehot;
logic [5:0] idx;
bit exp_bit, exp_err, writes;
integer waited, w0;
eb = eff_bit(b, o);
idx = eb[5:0];
onehot = 64'd1 << idx;
word = mem[(eb >> 6) % DEPTH];
writes = (op == 3'b010 || op == 3'b011 || op == 3'b100);
exp_err = fault_rd || (!fault_rd && op > 3'b100) || (writes && fault_wr);
case (op)
3'b010: begin exp_wdata = word | onehot; exp_bit = 1'b1; end
3'b011: begin exp_wdata = word & ~onehot; exp_bit = 1'b0; end
3'b100: begin exp_wdata = word ^ onehot; exp_bit = !word[idx]; end
default: begin exp_wdata = word; exp_bit = word[idx]; end
endcase
exp_addr = (eb >> 6) << 3;
exp_write = writes && !fault_rd;
@(negedge clk);
fault_read_next = fault_rd;
fault_write_next = fault_wr;
base_address = b; bit_offset = o; operation = op; op_valid = 1'b1;
waited = 0;
while (!op_ready) begin @(negedge clk); waited++; end
@(posedge clk); w0 = writes_seen; // accepted on this edge
@(negedge clk); op_valid = 1'b0; in_flight = 1'b1;
base_address = '1; bit_offset = '1; operation = 3'b111; // must be ignored now
waited = 0;
while (!result_valid && waited < TIMEOUT) begin @(negedge clk); waited++; end
check(result_valid, $sformatf("timeout: op %0d base %h off %h", op, b, o));
if (result_valid) begin
check(error === exp_err,
$sformatf("op %0d base %h off %h: error %b, expected %b (sampled with result_valid)",
op, b, o, error, exp_err));
if (!exp_err)
check(result_bit === exp_bit,
$sformatf("op %0d base %h off %h: result_bit %b, expected %b",
op, b, o, result_bit, exp_bit));
check(writes_seen - w0 == (exp_write ? 1 : 0),
$sformatf("op %0d: %0d writes, expected %0d", op, writes_seen - w0, exp_write));
if (exp_write && !fault_wr)
check(mem[(eb >> 6) % DEPTH] === exp_wdata, "memory word not updated as expected");
if (!exp_write || fault_wr)
check(mem[(eb >> 6) % DEPTH] === word, "memory word changed by a non-writing op");
end
@(negedge clk);
check(!result_valid, "result_valid longer than one cycle");
in_flight = 1'b0;
endtask
// Start an operation and assert reset `after` cycles into it.
task automatic reset_during_op(input logic [2:0] op, input integer after);
integer w0, r0;
logic [63:0] snapshot [0:DEPTH-1];
for (int k = 0; k < DEPTH; k++) snapshot[k] = mem[k];
exp_addr = 64'd0; exp_wdata = mem[0] ^ 64'd1; exp_write = (op >= 3'b010 && op <= 3'b100);
if (op == 3'b010) exp_wdata = mem[0] | 64'd1;
if (op == 3'b011) exp_wdata = mem[0] & ~64'd1;
@(negedge clk);
base_address = 0; bit_offset = 0; operation = op; op_valid = 1'b1;
while (!op_ready) @(negedge clk);
@(posedge clk);
@(negedge clk); op_valid = 1'b0; in_flight = 1'b1;
repeat (after) @(negedge clk);
if (result_valid) begin
in_flight = 1'b0; // finished before the reset point; nothing to cancel
end else begin
reset = 1'b1;
@(negedge clk);
w0 = writes_seen; r0 = results_seen;
reset = 1'b0; in_flight = 1'b0;
// Restore memory so a write committed before reset does not affect later checks.
for (int k = 0; k < DEPTH; k++) mem[k] = snapshot[k];
repeat (10) @(negedge clk);
check(results_seen == r0, $sformatf("result_valid after reset cancelled op %0d", op));
check(writes_seen == w0, $sformatf("write after reset cancelled op %0d", op));
check(op_ready, "DUT not idle after reset");
end
endtask
// --------------------------------------------------------------------- stimulus
integer ops = 2000;
integer seed0;
logic [63:0] rb, ro;
logic [2:0] rop;
initial begin
if (!$value$plusargs("seed=%d", seed)) seed = 1;
if (!$value$plusargs("ops=%d", ops)) ops = 2000;
seed0 = seed;
for (int k = 0; k < DEPTH; k++) mem[k] = '0;
mem[0] = 64'h8000_0000_0000_0001;
mem[1] = 64'h0000_0000_0000_0040;
repeat (3) @(negedge clk);
reset = 1'b0;
// Directed: the original eight cases (base 0, ideal memory).
run_op(0, 0, 3'b000, 0, 0); // GET word0 bit0 -> 1
run_op(0, 63, 3'b000, 0, 0); // GET word0 bit63 -> 1
run_op(0, 64, 3'b000, 0, 0); // GET word1 bit0 -> 0
run_op(0, 7, 3'b001, 0, 0); // TEST word0 bit7 -> 0
run_op(0, 1, 3'b010, 0, 0); // SET
run_op(0, 1, 3'b001, 0, 0); // TEST -> 1
run_op(0, 1, 3'b011, 0, 0); // CLEAR
run_op(0, 1, 3'b100, 0, 0); // TOGGLE
// Directed: non-zero base (byte address), word crossing, high/wrapping addresses.
run_op(8, 6, 3'b000, 0, 0); // byte 8 = word 1, bit 6 -> 1
run_op(7, 7, 3'b010, 0, 0); // bit 63 of word 0 via base 7
run_op(7, 8, 3'b100, 0, 0); // crosses into word 1
run_op(64'h1FFF_FFFF_FFFF_FFFF, 64'd8, 3'b000, 0, 0); // (base<<3)+8 wraps to 0
run_op(64'hFFFF_FFFF_FFFF_FFF8, 0, 3'b000, 0, 0); // base<<3 drops top 3 bits
run_op(0, 64'hFFFF_FFFF_FFFF_FFFF, 3'b010, 0, 0); // last bit of address space
// Directed: illegal opcodes report error and never write.
run_op(0, 3, 3'b101, 0, 0);
run_op(0, 3, 3'b110, 0, 0);
run_op(0, 3, 3'b111, 0, 0);
// Directed: faults. A read fault aborts before any write; a write fault
// reports error on the same cycle as result_valid.
run_op(0, 5, 3'b000, 1, 0);
run_op(0, 5, 3'b010, 1, 0);
run_op(0, 5, 3'b010, 0, 1);
run_op(0, 5, 3'b011, 0, 1);
run_op(0, 5, 3'b100, 0, 1);
run_op(0, 5, 3'b000, 0, 0); // a clean op right after faults has error=0
// Directed: reset at every point of a read-modify-write and of a read.
for (int a = 0; a < 8; a++) reset_during_op(3'b100, a);
for (int a = 0; a < 4; a++) reset_during_op(3'b000, a);
// Random: backpressure, variable read latency, faults, all opcodes.
ready_pct = 60; max_lat = 3;
for (int n = 0; n < ops; n++) begin
rb = {$random(seed), $random(seed)};
ro = {$random(seed), $random(seed)};
if (rnd(2) == 0) begin rb = rnd(64); ro = rnd(1024); end // dense region
rop = rnd(8);
if (rnd(10) == 0) rop = 3'b101 + rnd(3);
else rop = rnd(5);
run_op(rb, ro, rop, rnd(20) == 0, rnd(20) == 0);
end
$display("checks=%0d fails=%0d seed=%0d ops=%0d", checks, fails, seed0, ops);
if (fails == 0) $display("PASS: bit accelerator tests");
else $fatal(1, "FAIL: %0d of %0d checks failed", fails, checks);
$finish;
end
initial begin
#50_000_000;
$fatal(1, "global timeout");
end
endmodule
|