File size: 15,146 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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
// Equivalence testbench: the parallel engine (pcie/rtl/bitacc_engine.sv) against
// the single-op reference core (rtl/bit_accelerator.sv).
//
// Both start from the same random memory image and receive the same operation
// stream. The reference core runs the stream one operation at a time, range
// operations expanded into single-bit operations (tb/gold_ops.svh); the
// engine gets it as a stream with random gaps and random result backpressure.
// Every result (error flag; bit and value when there is no error) and every
// word of the final memory must match. Exits non-zero on any mismatch, or if
// a coverage goal (operation classes, out-of-order completion, full reorder
// buffer, range barriers in both directions) was not reached.
//
//   +ops=N +seed=N        stream length and seed
//   -DLANES=n -DWPL=n     engine geometry (default 4 lanes x 16 words: many conflicts)
`timescale 1ns/1ps
module tb_engine;
`ifndef LANES
  `define LANES 4
`endif
`ifndef WPL
  `define WPL 16
`endif
`ifndef MATCH
  `define MATCH 1
`endif
`ifndef MLANES
  `define MLANES `LANES
`endif
  localparam int LANES = `LANES;
  localparam int WPL   = `WPL;
  localparam int WORDS = LANES * WPL;
  localparam bit WITH_MATCH = `MATCH;
  localparam int MLANES = `MLANES;          // MATCH units
  localparam int MAXOPS = 20000;

  logic clk = 1'b0, reset = 1'b1;
  always #5 clk = ~clk;

  integer checks = 0, fails = 0;
  task automatic check(input bit cond, input string what);
    checks++;
    if (!cond) begin
      fails++;
      if (fails <= 20) $display("FAIL @%0t: %s", $time, what);
    end
  endtask

  // ------------------------------------------------------------ stimulus
  integer ops = 5000, seed = 1, seed0;
  logic [3:0]  s_op  [MAXOPS];
  logic [63:0] s_base[MAXOPS], s_off[MAXOPS];
  logic [31:0] s_len [MAXOPS];
  logic [23:0] s_src [MAXOPS];
  logic [2:0]  s_fn  [MAXOPS];
  logic        s_dry [MAXOPS];
  logic [63:0] init  [WORDS];

  // xorshift32, well distributed in every simulator. (Verilator 5.020's
  // seeded system random function is degenerate: the seed roughly doubles on
  // every call.) Simulators may evaluate operands such as rnd(a) + rnd(b) in
  // different orders, so the same seed can give different streams in Icarus
  // and Verilator; each is checked the same way.
  function automatic logic [31:0] rnd32();
    logic [31:0] x;
    x = (seed == 0) ? 32'd1 : 32'(seed);
    x = x ^ (x << 13); x = x ^ (x >> 17); x = x ^ (x << 5);
    seed = x;
    rnd32 = x;
  endfunction
  function automatic integer rnd(input integer n);
    rnd = (n <= 1) ? 0 : integer'(rnd32() % 32'(n));
  endfunction

  // ------------------------------------------------------------ reference: core + memory
  logic        g_op_valid = 1'b0, g_op_ready;
  logic [63:0] g_base = '0, g_off = '0;
  logic [2:0]  g_opc = '0;
  logic        g_res_valid, g_res_bit, g_error;
  logic        g_mem_valid, g_mem_write;
  logic [63:0] g_mem_addr, g_mem_wdata;
  logic [7:0]  g_mem_wstrb;
  logic        g_mem_rvalid = 1'b0, g_mem_fault = 1'b0;
  logic [63:0] g_mem_rdata = '0;
  logic [63:0] gmem [WORDS];

  bit_accelerator gold (
    .clk, .reset,
    .op_valid(g_op_valid), .op_ready(g_op_ready),
    .base_address(g_base), .bit_offset(g_off), .operation(g_opc),
    .result_valid(g_res_valid), .result_bit(g_res_bit), .error(g_error),
    .mem_valid(g_mem_valid), .mem_write(g_mem_write), .mem_addr(g_mem_addr),
    .mem_wdata(g_mem_wdata), .mem_wstrb(g_mem_wstrb),
    .mem_ready(1'b1), .mem_rvalid(g_mem_rvalid), .mem_rdata(g_mem_rdata),
    .mem_fault(g_mem_fault));

  // Memory slave for the reference: byte addresses 0 .. WORDS*8-1, else fault.
  always @(posedge clk) begin
    g_mem_rvalid <= 1'b0;
    g_mem_fault  <= 1'b0;
    if (!reset && g_mem_valid) begin
      if ((g_mem_addr >> 3) >= WORDS) g_mem_fault <= 1'b1;
      else if (g_mem_write) gmem[g_mem_addr >> 3] <= g_mem_wdata;
      else begin g_mem_rvalid <= 1'b1; g_mem_rdata <= gmem[g_mem_addr >> 3]; end
    end
  end

  logic        g_err_r [MAXOPS];
  logic        g_bit_r [MAXOPS];
  logic [55:0] g_val_r [MAXOPS];
  bit   gold_done = 0;

  `include "gold_ops.svh"
  `include "range_stim.svh"

  task automatic run_gold();
    logic e, b;
    logic [55:0] v;
    for (int n = 0; n < ops; n++) begin
      if (s_op[n] < 4'd8) begin
        gold_op(s_base[n], s_off[n], s_op[n][2:0], e, b);
        v = '0;
      end else
        gold_range(s_op[n], (s_base[n] << 3) + s_off[n], s_len[n], s_src[n], s_fn[n], s_dry[n], e, b, v);
      g_err_r[n] = e;
      g_bit_r[n] = b;
      g_val_r[n] = v;
    end
    gold_done = 1;
  endtask

  // ------------------------------------------------------------ engine
  logic        cmd_valid = 1'b0, cmd_ready;
  logic [3:0]  cmd_op = '0;
  logic [63:0] cmd_base = '0, cmd_offset = '0;
  logic [31:0] cmd_len = '0;
  logic [23:0] cmd_src = '0;
  logic [2:0]  cmd_fn = '0;
  logic        cmd_dry = 1'b0;
  logic        res_valid, res_ready = 1'b0, res_error, res_bit;
  logic [55:0] res_value;
  logic        host_valid = 1'b0, host_ready, host_write = 1'b0, host_rvalid;
  logic [$clog2(WORDS)-1:0] host_word = '0;
  logic [63:0] host_wdata = '0, host_rdata;
  logic        idle;

  bitacc_engine #(.LANES(LANES), .WORDS_PER_LANE(WPL), .WITH_MATCH(WITH_MATCH), .MATCH_LANES(MLANES)) dut (.*);

  logic        e_err_r [MAXOPS];
  logic        e_bit_r [MAXOPS];
  logic [55:0] e_val_r [MAXOPS];
  integer e_results = 0;
  bit   eng_done = 0;

  // result sink with random backpressure
  always @(posedge clk) begin
    if (!reset && res_valid && res_ready) begin
      e_err_r[e_results] <= res_error;
      e_bit_r[e_results] <= res_bit;
      e_val_r[e_results] <= res_value;
      e_results <= e_results + 1;
    end
  end
  // Random backpressure, plus a 200-cycle stall every 2000 cycles that fills
  // the reorder buffer and must hold the engine at its in-flight limit.
  integer sink_cyc = 0;
  always @(negedge clk) begin
    sink_cyc <= sink_cyc + 1;
    res_ready <= ((sink_cyc % 2000) >= 200) && ((rnd32() % 4) != 0);
  end

  task automatic host_write_word(input integer w, input logic [63:0] v);
    @(negedge clk);
    host_valid = 1'b1; host_write = 1'b1; host_word = w; host_wdata = v;
    while (!host_ready) @(negedge clk);
    @(negedge clk); host_valid = 1'b0; host_write = 1'b0;
  endtask

  task automatic host_read_word(input integer w, output logic [63:0] v);
    @(negedge clk);
    host_valid = 1'b1; host_write = 1'b0; host_word = w;
    while (!host_ready) @(negedge clk);
    @(negedge clk); host_valid = 1'b0;
    check(host_rvalid === 1'b1, "host_rvalid one cycle after a read handshake");
    v = host_rdata;
  endtask

  task automatic run_engine();
    integer n = 0;
    while (n < ops) begin
      @(negedge clk);
      if (rnd(20) == 0) cmd_valid = 1'b0;                   // input gap
      else begin
        // cmd_ready does not depend on cmd_valid and only changes on a rising
        // edge, so its value now is the one the engine samples at the next edge.
        cmd_valid = 1'b1; cmd_op = s_op[n]; cmd_base = s_base[n]; cmd_offset = s_off[n];
        cmd_len = s_len[n]; cmd_src = s_src[n]; cmd_fn = s_fn[n]; cmd_dry = s_dry[n];
        if (cmd_ready) n++;
      end
    end
    @(negedge clk); cmd_valid = 1'b0;
    while (e_results < ops) @(negedge clk);
    eng_done = 1;
  endtask

  // ------------------------------------------------------------ coverage, watchdog
  // A completion is out of order when an older operation (between the reorder
  // head and its own slot) has not completed yet. Shadow completion flags per
  // reorder slot: set when a result is written, cleared when it is emitted.
  integer ooo = 0, idle_cycles = 0, last_results = 0, rob_full_cycles = 0;
  integer range_waits = 0, single_waits = 0;   // barrier: range behind lanes, single behind range
  bit older_pending;
  bit cdone [32];
  initial for (int i = 0; i < 32; i++) cdone[i] = 0;
  // Sampled mid-cycle: the engine's registers are stable and hold exactly the
  // values the next rising edge consumes, independent of simulator ordering.
  always @(negedge clk) begin
    if (!reset) begin
      if (dut.inflight == 32) rob_full_cycles <= rob_full_cycles + 1;
      if (dut.d_valid && !dut.d_err && dut.d_range && dut.ops_out != '0) range_waits <= range_waits + 1;
      if (dut.d_valid && !dut.d_err && !dut.d_range && dut.r_busy) single_waits <= single_waits + 1;
      for (int i = 0; i < LANES; i++)
        if (dut.l_done[i]) begin
          older_pending = 0;
          for (int t = dut.head; (t % 32) != dut.l_seq[i]; t++)
            if (!cdone[t % 32]) older_pending = 1;
          if (older_pending) ooo = ooo + 1;
        end
      for (int i = 0; i < LANES; i++) if (dut.l_done[i]) cdone[dut.l_seq[i]] = 1;
      if (dut.d_take && dut.d_err) cdone[dut.d_seq] = 1;
      if (dut.r_done) cdone[dut.r_seq] = 1;
      if (res_valid && res_ready) cdone[dut.head] = 0;
      if (e_results != last_results || dut.idle) idle_cycles <= 0;
      else idle_cycles <= idle_cycles + 1;
      last_results <= e_results;
      if (idle_cycles > 20000) $fatal(1, "engine stalled: no result for 20000 cycles");
      if (dut.inflight > 32) $fatal(1, "more operations in flight than reorder slots");
    end
  end

  // ------------------------------------------------------------ main
  logic [63:0] v;
  integer kind;                                // per-operation stimulus class
  integer pend_i = 0;                          // next entry of a pending MATCH sequence
  integer n_hot = 0, n_any = 0, n_past = 0, n_wrap = 0, n_undef = 0, n_range = 0;
  logic [63:0] ra;

  // Bit-store-wide operations at fixed points of the stream.
  task automatic gen_whole(input int n, input int which);
    s_base[n] = 0; s_off[n] = 0; s_src[n] = 0; s_fn[n] = 3'd3; s_dry[n] = 1'b0;
    case (which)
      0: begin s_op[n] = R_COUNT; s_len[n] = WORDS * 64; end
      1: begin s_op[n] = R_FLIPR; s_len[n] = WORDS * 64; end
      default: begin s_op[n] = R_BULK; s_len[n] = WORDS / 2; s_off[n] = WORDS / 2; end   // upper ^= lower
    endcase
  endtask
  initial begin
    if (!$value$plusargs("ops=%d", ops)) ops = 5000;
    if (!$value$plusargs("seed=%d", seed)) seed = 1;
    seed0 = seed;
    if (ops > MAXOPS) ops = MAXOPS;

    for (int w = 0; w < WORDS; w++) begin
      init[w] = {rnd32(), rnd32()};
      gmem[w] = init[w];
    end
    for (int n = 0; n < ops; n++) begin
      // Blocks of 24: 16 back-to-back operations on 4 words of lane 0 (its queue
      // builds up), then 8 mixed ones, many of which overtake them in other lanes.
      kind = ((n % 24) < 16) ? 0 : 40 + rnd(60);
      s_op[n] = (rnd(12) == 0) ? 4'(5 + rnd(3)) : 4'(rnd(5));
      s_len[n] = '0; s_src[n] = '0; s_fn[n] = '0; s_dry[n] = 1'b0;
      if (s_op[n] > 4'd4) n_undef++;
      if (n == ops / 4 || n == ops / 2 || n == 3 * ops / 4) begin
        n_range++;
        gen_whole(n, n == ops / 4 ? 0 : n == ops / 2 ? 1 : 2);
      end else if (pend_i < rs_q_n || (kind >= 40 && rnd(4) == 0)) begin   // range operation
        n_range++;
        if (pend_i < rs_q_n) begin                   // rest of a MATCH sequence
          s_op[n] = rs_q_op[pend_i]; ra = rs_q_a[pend_i]; s_len[n] = rs_q_len[pend_i];
          s_src[n] = rs_q_src[pend_i]; s_fn[n] = rs_q_fn[pend_i]; pend_i++;
        end else if (rnd(4) == 0) begin
          gen_match_seq();
          s_op[n] = rs_q_op[0]; ra = rs_q_a[0]; s_len[n] = rs_q_len[0];
          s_src[n] = rs_q_src[0]; s_fn[n] = rs_q_fn[0]; pend_i = 1;
        end else
          gen_range(s_op[n], ra, s_len[n], s_src[n], s_fn[n], s_dry[n]);
        if (rnd(2) == 0) begin s_base[n] = 0; s_off[n] = ra; end
        else begin s_base[n] = ra >> 3; s_off[n] = ra & 64'd7; end
      end else if (kind < 40) begin                       // hot spot: 4 words, all in lane 0
        n_hot++;
        s_base[n] = rnd(4) * LANES * 8; s_off[n] = rnd(64);
      end else if (kind < 80) begin              // anywhere in memory
        n_any++;
        s_base[n] = rnd(WORDS * 8); s_off[n] = rnd(64 * 4);
        if (rnd(2) == 0) s_base[n] = (rnd(WPL) * LANES + 1 + rnd(LANES - 1)) * 8;  // not lane 0
      end else if (kind < 95) begin              // just past the end
        n_past++;
        s_base[n] = WORDS * 8 + rnd(16); s_off[n] = rnd(64);
      end else begin                             // arbitrary 64-bit values (wraps)
        n_wrap++;
        s_base[n] = {rnd32(), rnd32()}; s_off[n] = {rnd32(), rnd32()};
      end
    end

    $display("stimulus: hot=%0d anywhere=%0d past_end=%0d wrap=%0d undefined_op=%0d range=%0d",
             n_hot, n_any, n_past, n_wrap, n_undef, n_range);
    check(n_hot > ops / 3 && n_any > ops / 20 && n_past > ops / 80 && n_wrap > ops / 400
          && n_undef > ops / 40 && n_range > ops / 40, "stimulus covers every operation class");
    repeat (3) @(negedge clk);
    reset = 1'b0;
    for (int w = 0; w < WORDS; w++) host_write_word(w, init[w]);
    for (int w = 0; w < WORDS; w++) begin
      host_read_word(w, v);
      check(v === init[w], $sformatf("host read back word %0d", w));
    end

    fork
      run_gold();
      run_engine();
    join

    for (int n = 0; n < ops; n++) begin
      check(e_err_r[n] === g_err_r[n],
            $sformatf("op %0d (%0d, base %h, off %h, len %0d, src %0d, fn %0d): error %b, reference %b",
                      n, s_op[n], s_base[n], s_off[n], s_len[n], s_src[n], s_fn[n], e_err_r[n], g_err_r[n]));
      if (!g_err_r[n])
        check(e_bit_r[n] === g_bit_r[n] && e_val_r[n] === g_val_r[n],
              $sformatf("op %0d (%0d, base %h, off %h, len %0d, src %0d, fn %0d): bit %b value %0d, reference %b %0d",
                        n, s_op[n], s_base[n], s_off[n], s_len[n], s_src[n], s_fn[n],
                        e_bit_r[n], e_val_r[n], g_bit_r[n], g_val_r[n]));
      else
        check(e_bit_r[n] === 1'b0 && e_val_r[n] === '0, $sformatf("op %0d: error result has bit 0, value 0", n));
      range_cover(s_op[n], (s_base[n] << 3) + s_off[n], s_len[n], s_src[n], s_fn[n], s_dry[n],
                  g_err_r[n], g_bit_r[n]);
    end
    range_cover_check();
    check(range_waits > 0, "a range operation waited for busy lanes");
    check(single_waits > 0, "an operation waited for a running range operation");
    check(idle, "engine idle after the stream");
    check(ooo > ops / 50, $sformatf("out-of-order completions exercised (%0d)", ooo));
    check(rob_full_cycles > 0, "reorder buffer filled (in-flight limit exercised)");
    for (int w = 0; w < WORDS; w++) begin
      host_read_word(w, v);
      check(v === gmem[w], $sformatf("final word %0d: %h, reference %h", w, v, gmem[w]));
    end

    $display("checks=%0d fails=%0d lanes=%0d words=%0d ops=%0d seed=%0d out_of_order=%0d rob_full=%0d range_waits=%0d single_waits=%0d reference_ops=%0d",
             checks, fails, LANES, WORDS, ops, seed0, ooo, rob_full_cycles, range_waits, single_waits, gold_ops_issued);
    if (fails != 0) $fatal(1, "FAIL");
    $display("PASS: engine matches the reference core");
    $finish;
  end

  initial begin
    repeat (20) #1_000_000_000;
    $fatal(1, "global timeout");
  end
endmodule