// Self-checking testbench for cv_x1_pro_axi (Corvel / RISCON Labs). // Runs unchanged in Icarus Verilog (-g2012) and Verilator (--binary --timing). // Cycle-based AXI4-Lite and AXI4-Stream BFMs with seeded xorshift32 valid/ready // randomisation, protocol monitors, and expected results produced by the // independent Python golden model (gen_x1_vectors.py). // Plusargs: +seed= (default 1) +vec= (default "vec") `timescale 1ns/1ps module tb_cv_x1_pro_axi; // ------------------------------------------------------------------ clock/reset reg clk = 1'b0; reg rst_n = 1'b0; always #5 clk = ~clk; // ------------------------------------------------------------------ DUT signals reg [11:0] awaddr = 12'd0; reg awvalid = 1'b0; wire awready; reg [31:0] wdata = 32'd0; reg [3:0] wstrb = 4'd0; reg wvalid = 1'b0; wire wready; wire [1:0] bresp; wire bvalid; reg bready = 1'b0; reg [11:0] araddr = 12'd0; reg arvalid = 1'b0; wire arready; wire [31:0] rdata; wire [1:0] rresp; wire rvalid; reg rready = 1'b0; reg s_tvalid = 1'b0; wire s_tready; reg [63:0] s_tdata = 64'd0; reg [19:0] s_tuser = 20'd0; reg s_tlast = 1'b0; wire m_tvalid; reg m_tready = 1'b0; wire [31:0] m_tdata; wire [1:0] m_tuser; wire m_tlast; cv_x1_pro_axi u_dut ( .aclk(clk), .aresetn(rst_n), .s_axil_awaddr(awaddr), .s_axil_awprot(3'b000), .s_axil_awvalid(awvalid), .s_axil_awready(awready), .s_axil_wdata(wdata), .s_axil_wstrb(wstrb), .s_axil_wvalid(wvalid), .s_axil_wready(wready), .s_axil_bresp(bresp), .s_axil_bvalid(bvalid), .s_axil_bready(bready), .s_axil_araddr(araddr), .s_axil_arprot(3'b000), .s_axil_arvalid(arvalid), .s_axil_arready(arready), .s_axil_rdata(rdata), .s_axil_rresp(rresp), .s_axil_rvalid(rvalid), .s_axil_rready(rready), .s_axis_tvalid(s_tvalid), .s_axis_tready(s_tready), .s_axis_tdata(s_tdata), .s_axis_tuser(s_tuser), .s_axis_tlast(s_tlast), .m_axis_tvalid(m_tvalid), .m_axis_tready(m_tready), .m_axis_tdata(m_tdata), .m_axis_tuser(m_tuser), .m_axis_tlast(m_tlast)); // ------------------------------------------------------------------ monitors axil_monitor #(.AW(12)) u_axil_mon ( .clk(clk), .rst_n(rst_n), .awaddr(awaddr), .awvalid(awvalid), .awready(awready), .wdata(wdata), .wstrb(wstrb), .wvalid(wvalid), .wready(wready), .bresp(bresp), .bvalid(bvalid), .bready(bready), .araddr(araddr), .arvalid(arvalid), .arready(arready), .rdata(rdata), .rresp(rresp), .rvalid(rvalid), .rready(rready)); axis_monitor #(.PW(85), .CHECK_TREADY_INDEP(0)) u_s_mon ( .clk(clk), .rst_n(rst_n), .tvalid(s_tvalid), .tready(s_tready), .payload({s_tlast, s_tuser, s_tdata})); axis_monitor #(.PW(35), .CHECK_TREADY_INDEP(1)) u_m_mon ( .clk(clk), .rst_n(rst_n), .tvalid(m_tvalid), .tready(m_tready), .payload({m_tuser, m_tlast, m_tdata})); // ------------------------------------------------------------------ constants localparam [1:0] OKAY = 2'b00, SLVERR = 2'b10; localparam [11:0] R_ID = 12'h000, R_VER = 12'h004, R_CAPS = 12'h008, R_CTRL = 12'h00C, R_STATUS = 12'h010, R_ERR = 12'h014, R_PMAC = 12'h018, R_PSTL = 12'h01C, R_PBNK = 12'h020, R_WBASE = 12'h100; localparam [31:0] ID_V = 32'h4356_5831, VER_V = 32'h0001_0000, CAPS_V = 32'h1341_0408; localparam integer QN = 16384, PN = 8192; // ------------------------------------------------------------------ state integer errors = 0; integer n_rchk = 0, n_bchk = 0, n_echk = 0; integer seed = 1; string vecdir; reg [31:0] r_aw, r_w, r_b, r_ar, r_r, r_s, r_m, r_seq; integer p_aw = 256, p_w = 256, p_b = 256, p_ar = 256, p_r = 256, p_sv = 256, p_mr = 256; // write queue reg [11:0] wq_addr [0:QN-1]; reg [31:0] wq_data [0:QN-1]; reg [3:0] wq_strb [0:QN-1]; reg [1:0] wq_resp [0:QN-1]; integer wq_n = 0, aw_i = 0, w_i = 0, b_i = 0; // read queue reg [11:0] rq_addr [0:QN-1]; reg [31:0] rq_data [0:QN-1]; reg [31:0] rq_mask [0:QN-1]; reg [1:0] rq_resp [0:QN-1]; integer rq_n = 0, ar_i = 0, r_i = 0; reg [31:0] last_rdata = 32'd0; // stream queues reg [84:0] sq [0:QN-1]; integer sq_n = 0, s_i = 0; reg [34:0] eq [0:QN-1]; integer eq_n = 0, e_i = 0; integer tiles_out = 0, stall_ref = 0; // since last reset integer cyc = 0, lat_min = 1 << 30, lat_max = 0, lat_n = 0; integer t_in [0:QN-1]; // cycle of each s_axis handshake (queue index) always @(negedge clk) cyc = cyc + 1; // stable at the rising edge always @(posedge clk) if (rst_n && s_tvalid && s_tready) t_in[s_i] = cyc; // phase vectors reg [31:0] w_A [0:63]; reg [31:0] w_C [0:63]; reg [84:0] t_A [0:PN-1], t_G [0:PN-1], t_R [0:PN-1], t_B [0:PN-1], t_C [0:PN-1]; reg [34:0] e_A [0:PN-1], e_G [0:PN-1], e_R [0:PN-1], e_B [0:PN-1], e_C [0:PN-1]; reg [31:0] cnt [0:15]; function automatic [31:0] xs32(input [31:0] x); reg [31:0] y; begin y = x ^ (x << 13); y = y ^ (y >> 17); y = y ^ (y << 5); xs32 = y; end endfunction task automatic tb_err(input string s); begin errors = errors + 1; if (errors <= 30) $display("[%0t] TB ERROR: %0s", $time, s); if (errors == 20) begin $display("SUMMARY tb_errors>=%0d (stopped early)", errors); $display("RESULT: FAIL"); $finish; end end endtask // global watchdog: a hung handshake ends the run with FAIL instead of waiting out every timeout integer watchdog_cycles = 0; always @(posedge clk) begin watchdog_cycles = watchdog_cycles + 1; if (watchdog_cycles == 60000000) begin $display("[%0t] TB ERROR: global watchdog expired", $time); $display("RESULT: FAIL"); $finish; end if (u_axil_mon.errors + u_s_mon.errors + u_m_mon.errors >= 20) begin $display("SUMMARY monitor_errors>=20 (stopped early)"); $display("RESULT: FAIL"); $finish; end end // ================================================================== BFMs // AW channel driver always @(posedge clk or negedge rst_n) begin : awd integer nxt; if (!rst_n) begin awvalid <= 1'b0; aw_i <= wq_n; end else begin r_aw = xs32(r_aw); nxt = aw_i + ((awvalid && awready) ? 1 : 0); if (awvalid && !awready) begin // hold address and AWVALID until AWREADY end else if (nxt < wq_n && {24'd0, r_aw[7:0]} < p_aw) begin awvalid <= 1'b1; awaddr <= wq_addr[nxt]; end else begin awvalid <= 1'b0; end aw_i <= nxt; end end // W channel driver always @(posedge clk or negedge rst_n) begin : wd integer nxt; if (!rst_n) begin wvalid <= 1'b0; w_i <= wq_n; end else begin r_w = xs32(r_w); nxt = w_i + ((wvalid && wready) ? 1 : 0); if (wvalid && !wready) begin end else if (nxt < wq_n && {24'd0, r_w[7:0]} < p_w) begin wvalid <= 1'b1; wdata <= wq_data[nxt]; wstrb <= wq_strb[nxt]; end else begin wvalid <= 1'b0; end w_i <= nxt; end end // B channel receiver always @(posedge clk or negedge rst_n) begin : bd if (!rst_n) begin bready <= 1'b0; b_i <= wq_n; end else begin r_b = xs32(r_b); if (bvalid && bready) begin if (b_i >= wq_n) tb_err("unexpected B response"); else begin n_bchk = n_bchk + 1; if (bresp !== wq_resp[b_i]) tb_err($sformatf("BRESP %0d != %0d for write #%0d addr 0x%03h", bresp, wq_resp[b_i], b_i, wq_addr[b_i])); end b_i <= b_i + 1; end bready <= ({24'd0, r_b[7:0]} < p_b); end end // AR channel driver always @(posedge clk or negedge rst_n) begin : ard integer nxt; if (!rst_n) begin arvalid <= 1'b0; ar_i <= rq_n; end else begin r_ar = xs32(r_ar); nxt = ar_i + ((arvalid && arready) ? 1 : 0); if (arvalid && !arready) begin end else if (nxt < rq_n && {24'd0, r_ar[7:0]} < p_ar) begin arvalid <= 1'b1; araddr <= rq_addr[nxt]; end else begin arvalid <= 1'b0; end ar_i <= nxt; end end // R channel receiver always @(posedge clk or negedge rst_n) begin : rd_rx if (!rst_n) begin rready <= 1'b0; r_i <= rq_n; end else begin r_r = xs32(r_r); if (rvalid && rready) begin if (r_i >= rq_n) tb_err("unexpected R response"); else begin n_rchk = n_rchk + 1; last_rdata <= rdata; if (rresp !== rq_resp[r_i] || ((rdata ^ rq_data[r_i]) & rq_mask[r_i]) != 32'd0) tb_err($sformatf("read #%0d addr 0x%03h: got 0x%08h/%0d expected 0x%08h/%0d mask 0x%08h", r_i, rq_addr[r_i], rdata, rresp, rq_data[r_i], rq_resp[r_i], rq_mask[r_i])); end r_i <= r_i + 1; end rready <= ({24'd0, r_r[7:0]} < p_r); end end // AXI4-Stream master BFM (query + descriptor) always @(posedge clk or negedge rst_n) begin : sd integer nxt; if (!rst_n) begin s_tvalid <= 1'b0; s_i <= sq_n; end else begin r_s = xs32(r_s); nxt = s_i + ((s_tvalid && s_tready) ? 1 : 0); if (s_tvalid && !s_tready) begin end else if (nxt < sq_n && {24'd0, r_s[7:0]} < p_sv) begin s_tvalid <= 1'b1; {s_tlast, s_tuser, s_tdata} <= sq[nxt]; end else begin s_tvalid <= 1'b0; end s_i <= nxt; end end // AXI4-Stream receiver: TREADY changes 1 ns after the falling edge, i.e. in the // middle of the clock cycle (see axis_monitor), after the sequencer has run. always @(negedge clk) begin #1; r_m = xs32(r_m); m_tready = ({24'd0, r_m[7:0]} < p_mr); end always @(posedge clk or negedge rst_n) begin : md if (!rst_n) begin e_i <= eq_n; tiles_out <= 0; stall_ref <= 0; end else begin if (m_tvalid && !m_tready) stall_ref <= stall_ref + 1; if (m_tvalid && m_tready) begin if (e_i >= eq_n) tb_err("unexpected result beat"); else begin n_echk = n_echk + 1; if (cyc - t_in[e_i] < lat_min) lat_min = cyc - t_in[e_i]; if (cyc - t_in[e_i] > lat_max) lat_max = cyc - t_in[e_i]; lat_n = lat_n + 1; if ({m_tuser, m_tlast, m_tdata} !== eq[e_i]) tb_err($sformatf("result #%0d: got bank %0d last %0d data 0x%08h, expected bank %0d last %0d data 0x%08h", e_i, m_tuser, m_tlast, m_tdata, eq[e_i][34:33], eq[e_i][32], eq[e_i][31:0])); end e_i <= e_i + 1; tiles_out <= tiles_out + 1; end end end // ================================================================== sequencer helpers task automatic wr(input [11:0] a, input [31:0] d, input [3:0] s, input [1:0] resp); begin wq_addr[wq_n] = a; wq_data[wq_n] = d; wq_strb[wq_n] = s; wq_resp[wq_n] = resp; wq_n = wq_n + 1; end endtask task automatic rd(input [11:0] a, input [31:0] d, input [31:0] m, input [1:0] resp); begin rq_addr[rq_n] = a; rq_data[rq_n] = d; rq_mask[rq_n] = m; rq_resp[rq_n] = resp; rq_n = rq_n + 1; end endtask task automatic wait_axil; integer guard; begin guard = 0; while (!(b_i == wq_n && r_i == rq_n && aw_i == wq_n && w_i == wq_n && ar_i == rq_n) && guard < 100000) begin @(negedge clk); guard = guard + 1; end if (guard >= 100000) tb_err("timeout waiting for AXI4-Lite"); end endtask task automatic wait_stream; integer guard; begin guard = 0; while (!(e_i == eq_n && s_i == sq_n) && guard < 100000) begin @(negedge clk); guard = guard + 1; end if (guard >= 100000) tb_err("timeout waiting for stream"); end endtask task automatic profile(input integer k); begin case (k % 5) 0: begin p_aw = 256; p_w = 256; p_b = 256; p_ar = 256; p_r = 256; p_sv = 256; p_mr = 256; end 1: begin p_aw = 128; p_w = 128; p_b = 128; p_ar = 128; p_r = 128; p_sv = 128; p_mr = 128; end 2: begin p_aw = 32; p_w = 256; p_b = 200; p_ar = 64; p_r = 128; p_sv = 200; p_mr = 40; end 3: begin p_aw = 256; p_w = 32; p_b = 64; p_ar = 256; p_r = 32; p_sv = 40; p_mr = 220; end default: begin p_aw = 200; p_w = 200; p_b = 30; p_ar = 200; p_r = 30; p_sv = 256; p_mr = 128; end endcase end endtask task automatic push_tiles(input integer which, input integer n); integer k; begin for (k = 0; k < n; k = k + 1) begin case (which) 0: begin sq[sq_n] = t_A[k]; eq[eq_n] = e_A[k]; end 1: begin sq[sq_n] = t_G[k]; eq[eq_n] = e_G[k]; end 2: begin sq[sq_n] = t_R[k]; eq[eq_n] = e_R[k]; end 3: begin sq[sq_n] = t_B[k]; eq[eq_n] = e_B[k]; end default: begin sq[sq_n] = t_C[k]; eq[eq_n] = e_C[k]; end endcase sq_n = sq_n + 1; eq_n = eq_n + 1; end end endtask task automatic check_reset_values(input integer tag); begin rd(R_ID, ID_V, 32'hFFFF_FFFF, OKAY); rd(R_VER, VER_V, 32'hFFFF_FFFF, OKAY); rd(R_CAPS, CAPS_V, 32'hFFFF_FFFF, OKAY); rd(R_CTRL, 32'h2, 32'hFFFF_FFFF, OKAY); rd(R_STATUS, 32'h400, 32'hFFFF_FFFF, OKAY); rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); rd(R_PMAC, 32'h0, 32'hFFFF_FFFF, OKAY); rd(R_PSTL, 32'h0, 32'hFFFF_FFFF, OKAY); rd(R_PBNK, 32'h0, 32'hFFFF_FFFF, OKAY); rd(R_WBASE, 32'h0, 32'hFFFF_FFFF, OKAY); rd(12'h1FC, 32'h0, 32'hFFFF_FFFF, OKAY); rd(12'h024, 32'h0, 32'hFFFF_FFFF, SLVERR); rd(12'h0FC, 32'h0, 32'hFFFF_FFFF, SLVERR); rd(12'h200, 32'h0, 32'hFFFF_FFFF, SLVERR); rd(12'hFFC, 32'h0, 32'hFFFF_FFFF, SLVERR); wait_axil; $display("[%0t] reset values checked (%0d)", $time, tag); end endtask task automatic check_outputs_in_reset(input string where); begin if (bvalid !== 1'b0 || rvalid !== 1'b0 || m_tvalid !== 1'b0 || s_tready !== 1'b0) tb_err($sformatf("valid/ready not cleared in reset: %s", where)); if (awready !== 1'b1 || wready !== 1'b1 || arready !== 1'b1) tb_err($sformatf("address/data ready not 1 in reset: %s", where)); if ((^{awready, wready, bvalid, bresp, arready, rvalid, rresp, rdata, s_tready, m_tvalid, m_tdata, m_tuser, m_tlast}) === 1'bx) tb_err($sformatf("X/Z on outputs in reset: %s", where)); end endtask task automatic check_status_idle(input integer sticky); begin rd(R_STATUS, 32'h400 | (sticky << 9), 32'hFFFF_FFFF, OKAY); rd(R_PMAC, tiles_out & 32'hFFFF, 32'hFFFF_FFFF, OKAY); rd(R_PSTL, stall_ref & 32'hFFFF, 32'hFFFF_FFFF, OKAY); rd(R_PBNK, {16'd0, u_dut.u_core.perf_bank_conflicts}, 32'hFFFF_FFFF, OKAY); wait_axil; end endtask // random register traffic, concurrently on all five channels task automatic random_reg_traffic(input integer n, output reg [2:0] err_exp); integer k, sel, a; begin err_exp = 3'b000; for (k = 0; k < n; k = k + 1) begin r_seq = xs32(r_seq); sel = r_seq[2:0]; a = r_seq[13:8]; case (sel) 0, 1, 2: wr(R_WBASE + {a[5:0], 2'b00}, w_A[a], 4'hF, OKAY); 3: begin wr(R_WBASE + {a[5:0], 2'b00}, r_seq, {1'b0, r_seq[18:16]}, SLVERR); err_exp[1] = 1'b1; end 4: if (r_seq[17:16] == 2'd3) wr(R_CTRL, 32'h2, 4'hF, OKAY); // CTRL: value unchanged else begin wr(R_ID + {r_seq[17:16], 2'b00}, r_seq, 4'hF, SLVERR); err_exp[2] = 1'b1; end 5: begin wr(12'h200 + {r_seq[25:16], 2'b00} % 12'hE00, r_seq, 4'hF, SLVERR); err_exp[2] = 1'b1; end 6: begin wr(R_PMAC + {r_seq[17:16] % 2'd3, 2'b00}, r_seq, 4'hF, SLVERR); err_exp[2] = 1'b1; end default: wr(R_CTRL, 32'h2, 4'hF, OKAY); endcase r_seq = xs32(r_seq); sel = r_seq[2:0]; a = r_seq[13:8]; case (sel) 0: rd(R_ID, ID_V, 32'hFFFF_FFFF, OKAY); 1: rd(R_VER, VER_V, 32'hFFFF_FFFF, OKAY); 2: rd(R_CAPS, CAPS_V, 32'hFFFF_FFFF, OKAY); 3: rd(R_CTRL, 32'h2, 32'hFFFF_FFFF, OKAY); 4: rd(R_WBASE + {a[5:0], 2'b00}, 32'h0, 32'hFFFF_FFFF, OKAY); 5: rd(12'h300 + {r_seq[25:16], 2'b00} % 12'hC00, 32'h0, 32'hFFFF_FFFF, SLVERR); 6: rd(12'h024 + {r_seq[21:16], 2'b00}, 32'h0, 32'hFFFF_FFFF, SLVERR); // 0x024..0x120 region default: rd(R_PBNK, 32'h0, 32'h0, OKAY); endcase // 0x024 + offset may land in the weight window (0x100..0x1FC): expect OKAY / 0 if (sel == 6 && (12'h024 + {r_seq[21:16], 2'b00}) >= 12'h100) rq_resp[rq_n-1] = OKAY; end end endtask // ================================================================== test sequence integer k, n_A, n_G, n_R, n_B, n_C, st_A, st_B, st_C, rst_at, chk_before_reset, e_base; integer tp_e0, tp_e1, tp_c0, tp_c1; reg [2:0] err_exp, err_acc; initial begin if (!$value$plusargs("seed=%d", seed)) seed = 1; if (!$value$plusargs("vec=%s", vecdir)) vecdir = "vec"; r_aw = 32'h1234_5678 ^ (seed * 32'h9E37_79B9); r_w = r_aw ^ 32'h0F0F_0F0F; r_b = r_aw ^ 32'h3C3C_3C3C; r_ar = r_aw ^ 32'h5A5A_5A5A; r_r = r_aw ^ 32'h6969_6969; r_s = r_aw ^ 32'h7777_1111; r_m = r_aw ^ 32'h2468_ACE0; r_seq = r_aw ^ 32'h1357_9BDF; $readmemh($sformatf("%s/x1_A_w.hex", vecdir), w_A); $readmemh($sformatf("%s/x1_C_w.hex", vecdir), w_C); $readmemh($sformatf("%s/x1_counts.hex", vecdir), cnt); n_A = cnt[0]; n_G = cnt[1]; n_R = cnt[2]; n_B = cnt[3]; n_C = cnt[4]; st_A = cnt[5]; st_B = cnt[8]; st_C = cnt[9]; $readmemh($sformatf("%s/x1_A_t.hex", vecdir), t_A, 0, n_A - 1); $readmemh($sformatf("%s/x1_A_e.hex", vecdir), e_A, 0, n_A - 1); $readmemh($sformatf("%s/x1_G_t.hex", vecdir), t_G, 0, n_G - 1); $readmemh($sformatf("%s/x1_G_e.hex", vecdir), e_G, 0, n_G - 1); $readmemh($sformatf("%s/x1_R_t.hex", vecdir), t_R, 0, n_R - 1); $readmemh($sformatf("%s/x1_R_e.hex", vecdir), e_R, 0, n_R - 1); $readmemh($sformatf("%s/x1_B_t.hex", vecdir), t_B, 0, n_B - 1); $readmemh($sformatf("%s/x1_B_e.hex", vecdir), e_B, 0, n_B - 1); $readmemh($sformatf("%s/x1_C_t.hex", vecdir), t_C, 0, n_C - 1); $readmemh($sformatf("%s/x1_C_e.hex", vecdir), e_C, 0, n_C - 1); `ifdef VERILATOR $display("tb_cv_x1_pro_axi simulator=verilator seed=%0d tiles A=%0d G=%0d R=%0d B=%0d C=%0d", seed, n_A, n_G, n_R, n_B, n_C); `else $display("tb_cv_x1_pro_axi simulator=icarus seed=%0d tiles A=%0d G=%0d R=%0d B=%0d C=%0d", seed, n_A, n_G, n_R, n_B, n_C); `endif profile(0); // ---------------- T0: reset and reset values repeat (3) @(negedge clk); check_outputs_in_reset("initial reset"); rst_n = 1'b1; check_reset_values(0); // ---------------- T1: directed register tests wr(R_ID, 32'hFFFF_FFFF, 4'hF, SLVERR); wr(R_VER, 32'h0, 4'hF, SLVERR); wr(R_CAPS, 32'h0, 4'hF, SLVERR); wr(R_STATUS, 32'h0, 4'hF, SLVERR); wr(R_PMAC, 32'h0, 4'hF, SLVERR); wr(R_PSTL, 32'h0, 4'hF, SLVERR); wr(R_PBNK, 32'h0, 4'hF, SLVERR); wr(12'h024, 32'h0, 4'hF, SLVERR); wr(12'h0FC, 32'h0, 4'hF, SLVERR); wr(12'hFFC, 32'h0, 4'hF, SLVERR); // AXI imposes no ordering between the read and write channels: every read // that depends on a preceding write waits for that write's response. wait_axil; rd(R_ERR, 32'h4, 32'hFFFF_FFFF, OKAY); rd(R_ID, ID_V, 32'hFFFF_FFFF, OKAY); // RO write had no effect wait_axil; wr(R_ERR, 32'h4, 4'hF, OKAY); wait_axil; rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); wr(R_WBASE, 32'h1234_5678, 4'h7, SLVERR); wr(R_WBASE + 12'h4, 32'h0, 4'h0, SLVERR); wait_axil; rd(R_ERR, 32'h2, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_ERR, 32'h0, 4'hE, OKAY); wait_axil; rd(R_ERR, 32'h2, 32'hFFFF_FFFF, OKAY); // byte 0 not enabled wait_axil; wr(R_ERR, 32'hFFFF_FFFF, 4'hF, OKAY); wait_axil; rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_CTRL, 32'h0, 4'h0, OKAY); wait_axil; rd(R_CTRL, 32'h2, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_CTRL, 32'h0, 4'hE, OKAY); wait_axil; rd(R_CTRL, 32'h2, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_CTRL, 32'h0, 4'hF, OKAY); wait_axil; rd(R_CTRL, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_CTRL, 32'hFFFF_FFFE, 4'hF, OKAY); wait_axil; rd(R_CTRL, 32'h2, 32'hFFFF_FFFF, OKAY); wait_axil; wr(12'h00D, 32'h0, 4'hF, OKAY); // byte offset ignored wait_axil; rd(12'h00E, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; wr(12'h00F, 32'h2, 4'hF, OKAY); wait_axil; rd(12'h00C, 32'h2, 32'hFFFF_FFFF, OKAY); rd(R_WBASE + 12'h0F0, 32'h0, 32'hFFFF_FFFF, OKAY); rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; $display("[%0t] T1 directed register tests done", $time); // ---------------- T2: random concurrent register traffic under five profiles err_acc = 3'b000; for (k = 0; k < 5; k = k + 1) begin @(negedge clk); profile(k); random_reg_traffic(160, err_exp); err_acc = err_acc | err_exp; wait_axil; rd(R_ERR, {29'd0, err_exp}, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_ERR, 32'h7, 4'hF, OKAY); wait_axil; rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; end $display("[%0t] T2 random register traffic done (error classes seen 0x%0h)", $time, err_acc); // ---------------- T3: weight load A, stream disabled check, stream A @(negedge clk); profile(1); for (k = 0; k < 64; k = k + 1) wr(R_WBASE + k * 4, w_A[k], 4'hF, OKAY); wait_axil; e_base = s_i; push_tiles(0, n_A); repeat (40) @(negedge clk); if (s_i != e_base || u_s_mon.n_xfer != 0) tb_err("stream accepted a tile while STREAM_EN=0"); rd(R_STATUS, 32'h400, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_CTRL, 32'h3, 4'hF, OKAY); k = 0; while (!(e_i == eq_n && s_i == sq_n)) begin repeat (500) @(negedge clk); profile(k); k = k + 1; rd(R_STATUS, 32'h0, 32'h0, OKAY); rd(R_PMAC, 32'h0, 32'h0, OKAY); rd(R_ID, ID_V, 32'hFFFF_FFFF, OKAY); wr(R_CTRL, 32'h3, 4'hF, OKAY); wr(12'h800, 32'h0, 4'hF, SLVERR); end wait_stream; wait_axil; rd(R_ERR, 32'h4, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_ERR, 32'h7, 4'hF, OKAY); wait_axil; check_status_idle(st_A); $display("[%0t] T3 stream A done: %0d results compared", $time, n_echk); // ---------------- T4: busy guard @(negedge clk); profile(0); p_mr = 0; repeat (3) @(negedge clk); push_tiles(1, n_G); repeat (30) @(negedge clk); rd(R_STATUS, 32'h0000_0903 | (st_A << 9), 32'hFFFF_FB0F, OKAY); // M_TVALID=1, BUSY=1, INFLIGHT=3 wr(R_WBASE + 12'h0FC, w_A[63], 4'hF, SLVERR); // rejected: WGUARD=1 and busy wait_axil; rd(R_ERR, 32'h1, 32'hFFFF_FFFF, OKAY); wait_axil; wr(R_ERR, 32'h1, 4'hF, OKAY); wr(R_CTRL, 32'h1, 4'hF, OKAY); // WGUARD=0 wr(R_WBASE + 12'h0FC, w_A[63], 4'hF, OKAY); // accepted while busy (address not in use) wr(R_CTRL, 32'h3, 4'hF, OKAY); wait_axil; rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; if (e_i != eq_n - n_G) tb_err("result delivered while TREADY was held low"); @(negedge clk); profile(1); wait_stream; check_status_idle(st_A); $display("[%0t] T4 busy-guard test done", $time); // ---------------- T5: reset in the middle of stream + AXI4-Lite traffic @(negedge clk); profile(1); e_base = e_i; push_tiles(2, n_R); for (k = 0; k < 300; k = k + 1) begin rd(R_ID, ID_V, 32'hFFFF_FFFF, OKAY); wr(12'h400 + k * 4, k, 4'hF, SLVERR); end rst_at = 300 + ((seed * 37) % 400); repeat (rst_at) @(posedge clk); #3; // asynchronous assertion between clock edges chk_before_reset = e_i - e_base; rst_n = 1'b0; #1; check_outputs_in_reset("mid-traffic reset"); repeat (4) @(negedge clk); check_outputs_in_reset("held reset"); rst_n = 1'b1; // released on the falling edge (synchronous to clk) $display("[%0t] T5 reset asserted after %0d cycles; %0d results checked before reset", $time, rst_at, chk_before_reset); if (chk_before_reset == 0 || chk_before_reset >= n_R) tb_err("reset did not interrupt the stream"); check_reset_values(1); // ---------------- T6: zero weights after reset wr(R_CTRL, 32'h3, 4'hF, OKAY); wait_axil; push_tiles(3, n_B); wait_stream; check_status_idle(st_B); $display("[%0t] T6 post-reset zero-weight stream done", $time); // ---------------- T7: weights C, extreme workload, full-throughput then random profiles wr(R_CTRL, 32'h2, 4'hF, OKAY); for (k = 0; k < 64; k = k + 1) wr(R_WBASE + k * 4, w_C[k], 4'hF, OKAY); wr(R_CTRL, 32'h3, 4'hF, OKAY); wait_axil; @(negedge clk); profile(0); push_tiles(4, n_C); repeat (100) @(negedge clk); tp_e0 = e_i; tp_c0 = cyc; repeat (500) @(negedge clk); tp_e1 = e_i; tp_c1 = cyc; k = 2; while (!(e_i == eq_n && s_i == sq_n)) begin repeat (700) @(negedge clk); profile(k); k = k + 1; rd(R_STATUS, 32'h0, 32'h0, OKAY); end wait_stream; wait_axil; check_status_idle(st_B | st_C); rd(R_ERR, 32'h0, 32'hFFFF_FFFF, OKAY); wait_axil; $display("[%0t] T7 stream C done", $time); repeat (10) @(negedge clk); $display("SUMMARY tb_errors=%0d monitor_errors=%0d", errors, u_axil_mon.errors + u_s_mon.errors + u_m_mon.errors); $display("SUMMARY checks: register_reads_compared=%0d write_responses_compared=%0d stream_results_compared=%0d", n_rchk, n_bchk, n_echk); $display("SUMMARY axi4lite: writes=%0d (OKAY %0d, SLVERR %0d) reads=%0d (OKAY %0d, SLVERR %0d)", u_axil_mon.n_b, u_axil_mon.n_b_okay, u_axil_mon.n_b_slverr, u_axil_mon.n_r, u_axil_mon.n_r_okay, u_axil_mon.n_r_slverr); $display("SUMMARY axi4lite timing: last_of_AW_W_handshake_to_B_handshake_min_cycles=%0d AR_to_R_handshake_min_cycles=%0d min_cycles_between_AW_handshakes=%0d min_cycles_between_AR_handshakes=%0d", u_axil_mon.wr_lat_min, u_axil_mon.rd_lat_min, u_axil_mon.aw_gap_min, u_axil_mon.ar_gap_min); $display("SUMMARY axi4lite ordering: aw_before_w=%0d w_before_aw=%0d same_cycle=%0d back_to_back_aw=%0d back_to_back_ar=%0d max_outstanding_wr=%0d max_outstanding_rd=%0d", u_axil_mon.n_aw_first, u_axil_mon.n_w_first, u_axil_mon.n_same, u_axil_mon.n_back_to_back_aw, u_axil_mon.n_back_to_back_ar, u_axil_mon.max_wr_out, u_axil_mon.max_rd_out); $display("SUMMARY axi4stream: s_axis_transfers=%0d s_back_to_back=%0d m_axis_transfers=%0d m_back_to_back=%0d m_stall_cycles=%0d m_tready_midcycle_toggles_checked=%0d", u_s_mon.n_xfer, u_s_mon.n_back_to_back, u_m_mon.n_xfer, u_m_mon.n_back_to_back, u_m_mon.n_stall, u_m_mon.n_ready_toggle_checks); $display("SUMMARY timing: s_to_m_handshake_latency_cycles min=%0d max=%0d over %0d results; always-valid/always-ready window: %0d results in %0d cycles", lat_min, lat_max, lat_n, tp_e1 - tp_e0, tp_c1 - tp_c0); if (errors == 0 && u_axil_mon.errors == 0 && u_s_mon.errors == 0 && u_m_mon.errors == 0 && n_echk > 0 && n_rchk > 0) $display("RESULT: PASS"); else $display("RESULT: FAIL"); $finish; end endmodule