`timescale 1ps/100fs // ============================================================ // EXP-0096 -- real FUNCTIONAL verification of the N=8 hybrid systolic // system (n8_system_ddr3_top.v), the real, definitive deployment // target -- directly adapted from tb_n16_system_ddr3.v's own real // methodology (EXP-0092/0094), scaled down to N_GROUPS=2 instead of 4. // Exists specifically because EXP-0095's own real P&R closure at N=8 // (WNS=0.000ns, 0 failing endpoints) was measured via a `-generic // N_GROUPS=2` override against n16_system_ddr3_top.v, WITHOUT a // dedicated functional test at that specific N -- this closes that // real, disclosed gap before trusting N=8 as a real, deployable // signoff. // // STRUCTURE: identical real harness to tb_n16_system_ddr3.v (real 2- // chip 32-bit DDR3 model via WireDelay, real mig_7series_0_mig with // SIM_BYPASS_INIT_CAL="FAST", the pre_active-muxed direct preload path, // weight_byte/input_byte golden functions, sdram_write_burst task) -- // only N_GROUPS/N_PES/M change (2 groups instead of 4, 16 positions // instead of 32, filling every one of the 2 groups x 4 PEs x 2 lanes // exactly once), plus neural_director_grouped.v + sdram_arbiter_hier.v // + 2x systolic_group.v instead of 4x (host slot tied off inactive, // same real precedent as tb_n2_system_ddr3.v never instantiating // spi_host_bridge_v3.v). // ============================================================ module tb; localparam CLKIN_PERIOD = 3225; // ps, this project's real, CLOSED MIG config (EXP-0086) localparam REFCLK_FREQ = 200.0; // MHz localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ)); localparam RESET_PERIOD = 200000; // ps localparam DATA_WIDTH = 8; localparam P_IN = 8; localparam ACC_WIDTH = 32; localparam ADDR_WIDTH = 26; // this project's byte-address convention (Director/systolic_group) localparam MIG_ADDR_WIDTH = 25; // word-address convention (BURST_LEN=8) at the arbiter/adapter localparam BURST_LEN = 8; localparam N_INPUTS = 128; localparam N_TILES = N_INPUTS/P_IN; localparam LAYER_BYTES = N_INPUTS; localparam WORDS_PER_LAYER = LAYER_BYTES/2; localparam N_GROUPS = 2; localparam N_PES = N_GROUPS*4; // 8 localparam QUEUE_DEPTH = 16; localparam L = 1; // one shared layer -- simplest real addressing that still localparam M = 16; // exercises every one of the 2 groups x 4 PEs x 2 lanes exactly once // ---- clock/reset (mirrors tb_n2_system_ddr3.v's own proven pattern) ---- reg sys_rst_n; wire sys_rst = sys_rst_n; reg sys_clk_i = 1'b0; always #(CLKIN_PERIOD/2.0) sys_clk_i = ~sys_clk_i; wire sys_clk_p = sys_clk_i; wire sys_clk_n = ~sys_clk_i; reg clk_ref_i = 1'b0; always #REFCLK_PERIOD clk_ref_i = ~clk_ref_i; wire clk_ref_p = clk_ref_i; wire clk_ref_n = ~clk_ref_i; initial begin sys_rst_n = 1'b0; #RESET_PERIOD sys_rst_n = 1'b1; end // ---- real DDR3 pins + model (identical to tb_n2_system_ddr3.v) ---- wire ddr3_reset_n; wire [31:0] ddr3_dq_fpga; wire [3:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga; wire [13:0] ddr3_addr_fpga; wire [2:0] ddr3_ba_fpga; wire ddr3_ras_n_fpga, ddr3_cas_n_fpga, ddr3_we_n_fpga; wire [0:0] ddr3_cke_fpga, ddr3_ck_p_fpga, ddr3_ck_n_fpga, ddr3_cs_n_fpga; wire [3:0] ddr3_dm_fpga; wire [0:0] ddr3_odt_fpga; wire [31:0] ddr3_dq_sdram; reg [13:0] ddr3_addr_sdram; reg [2:0] ddr3_ba_sdram; reg ddr3_ras_n_sdram, ddr3_cas_n_sdram, ddr3_we_n_sdram; wire [0:0] ddr3_cs_n_sdram; wire [0:0] ddr3_odt_sdram; reg [0:0] ddr3_cke_sdram; wire [3:0] ddr3_dm_sdram; wire [3:0] ddr3_dqs_p_sdram, ddr3_dqs_n_sdram; reg [0:0] ddr3_ck_p_sdram, ddr3_ck_n_sdram; reg [0:0] ddr3_cs_n_sdram_tmp; reg [3:0] ddr3_dm_sdram_tmp; reg [0:0] ddr3_odt_sdram_tmp; always @(*) begin ddr3_ck_p_sdram <= ddr3_ck_p_fpga; ddr3_ck_n_sdram <= ddr3_ck_n_fpga; ddr3_addr_sdram <= ddr3_addr_fpga; ddr3_ba_sdram <= ddr3_ba_fpga; ddr3_ras_n_sdram <= ddr3_ras_n_fpga; ddr3_cas_n_sdram <= ddr3_cas_n_fpga; ddr3_we_n_sdram <= ddr3_we_n_fpga; ddr3_cke_sdram <= ddr3_cke_fpga; end always @(*) ddr3_cs_n_sdram_tmp <= ddr3_cs_n_fpga; assign ddr3_cs_n_sdram = ddr3_cs_n_sdram_tmp; always @(*) ddr3_dm_sdram_tmp <= ddr3_dm_fpga; assign ddr3_dm_sdram = ddr3_dm_sdram_tmp; always @(*) ddr3_odt_sdram_tmp <= ddr3_odt_fpga; assign ddr3_odt_sdram = ddr3_odt_sdram_tmp; genvar dqwd; generate for (dqwd = 0; dqwd < 32; dqwd = dqwd + 1) begin : dq_delay WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dq ( .A(ddr3_dq_fpga[dqwd]), .B(ddr3_dq_sdram[dqwd]), .reset(sys_rst_n), .phy_init_done(init_calib_complete) ); end endgenerate genvar dqswd; generate for (dqswd = 0; dqswd < 4; dqswd = dqswd + 1) begin : dqs_delay WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dqs_p ( .A(ddr3_dqs_p_fpga[dqswd]), .B(ddr3_dqs_p_sdram[dqswd]), .reset(sys_rst_n), .phy_init_done(init_calib_complete) ); WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dqs_n ( .A(ddr3_dqs_n_fpga[dqswd]), .B(ddr3_dqs_n_sdram[dqswd]), .reset(sys_rst_n), .phy_init_done(init_calib_complete) ); end endgenerate genvar ci; generate for (ci = 0; ci < 2; ci = ci + 1) begin : gen_mem ddr3_model u_comp_ddr3 ( .rst_n(ddr3_reset_n), .ck(ddr3_ck_p_sdram), .ck_n(ddr3_ck_n_sdram), .cke(ddr3_cke_sdram[0]), .cs_n(ddr3_cs_n_sdram[0]), .ras_n(ddr3_ras_n_sdram), .cas_n(ddr3_cas_n_sdram), .we_n(ddr3_we_n_sdram), .dm_tdqs(ddr3_dm_sdram[2*ci +: 2]), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram), .dq(ddr3_dq_sdram[16*ci +: 16]), .dqs(ddr3_dqs_p_sdram[2*ci +: 2]), .dqs_n(ddr3_dqs_n_sdram[2*ci +: 2]), .tdqs_n(), .odt(ddr3_odt_sdram[0]) ); end endgenerate wire [27:0] app_addr; wire [2:0] app_cmd; wire app_en, app_rdy; wire [127:0] app_wdf_data; wire app_wdf_end; wire [15:0] app_wdf_mask; wire app_wdf_wren, app_wdf_rdy; wire [127:0] app_rd_data; wire app_rd_data_end, app_rd_data_valid; wire ui_clk, ui_clk_sync_rst, init_calib_complete; mig_7series_0_mig #( .SIM_BYPASS_INIT_CAL("FAST") ) u_mig ( .ddr3_dq(ddr3_dq_fpga), .ddr3_dqs_n(ddr3_dqs_n_fpga), .ddr3_dqs_p(ddr3_dqs_p_fpga), .ddr3_addr(ddr3_addr_fpga), .ddr3_ba(ddr3_ba_fpga), .ddr3_ras_n(ddr3_ras_n_fpga), .ddr3_cas_n(ddr3_cas_n_fpga), .ddr3_we_n(ddr3_we_n_fpga), .ddr3_reset_n(ddr3_reset_n), .ddr3_ck_p(ddr3_ck_p_fpga), .ddr3_ck_n(ddr3_ck_n_fpga), .ddr3_cke(ddr3_cke_fpga), .ddr3_cs_n(ddr3_cs_n_fpga), .ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga), .sys_clk_p(sys_clk_p), .sys_clk_n(sys_clk_n), .clk_ref_p(clk_ref_p), .clk_ref_n(clk_ref_n), .app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en), .app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end), .app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren), .app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid), .app_rdy(app_rdy), .app_wdf_rdy(app_wdf_rdy), .app_sr_req(1'b0), .app_ref_req(1'b0), .app_zq_req(1'b0), .app_sr_active(), .app_ref_ack(), .app_zq_ack(), .ui_clk(ui_clk), .ui_clk_sync_rst(ui_clk_sync_rst), .init_calib_complete(init_calib_complete), .device_temp(), .sys_rst(sys_rst) ); // ---- preload path: direct access to mig_native_adapter.v, // bypassing the arbiter, exactly like tb_n2_system_ddr3.v's own // "pre_active" mux -- used only before job submission begins. ---- reg pre_active; reg pre_req, pre_wr; reg [MIG_ADDR_WIDTH-1:0] pre_addr; reg [32*BURST_LEN-1:0] pre_wdata; wire adp_req, adp_wr; wire [MIG_ADDR_WIDTH-1:0] adp_addr; wire [32*BURST_LEN-1:0] adp_wdata; wire [4*BURST_LEN-1:0] adp_wmask; wire [32*BURST_LEN-1:0] adp_rdata; wire adp_ready, adp_busy; wire arb_ctrl_req_o, arb_ctrl_wr_o; wire [MIG_ADDR_WIDTH-1:0] arb_ctrl_addr_o; wire [32*BURST_LEN-1:0] arb_ctrl_wdata_o; wire [4*BURST_LEN-1:0] arb_ctrl_wmask_o; assign adp_req = pre_active ? pre_req : arb_ctrl_req_o; assign adp_wr = pre_active ? pre_wr : arb_ctrl_wr_o; assign adp_addr = pre_active ? pre_addr : arb_ctrl_addr_o; assign adp_wdata = pre_active ? pre_wdata : arb_ctrl_wdata_o; assign adp_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : arb_ctrl_wmask_o; mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MIG_ADDR_WIDTH)) u_adapter ( .clk(ui_clk), .rst(ui_clk_sync_rst), .req(adp_req), .wr(adp_wr), .addr(adp_addr), .wdata(adp_wdata), .wmask(adp_wmask), .rdata(adp_rdata), .ready(adp_ready), .busy(adp_busy), .app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en), .app_rdy(app_rdy), .app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end), .app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren), .app_wdf_rdy(app_wdf_rdy), .app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid) ); // EXP-0086 fix (see tb_n2_system_ddr3.v): plain intermediate 8-bit // reg instead of SV-only `8'(expr)` sized-cast syntax. function automatic signed [7:0] weight_byte(input integer li, input integer t); reg [7:0] tmp; begin tmp = (li*17 + t*29 + 13) & 8'hFF; weight_byte = $signed(tmp); end endfunction function automatic signed [7:0] input_byte(input integer li, input integer pos, input integer t); reg [7:0] tmp; begin tmp = (li*11 + pos*41 + t*7 + 3) & 8'hFF; input_byte = $signed(tmp); end endfunction task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data); begin @(posedge ui_clk); while (adp_busy) @(posedge ui_clk); pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data; @(posedge ui_clk); pre_req = 1'b0; while (!adp_ready) @(posedge ui_clk); end endtask task automatic preload_sdram_layers; integer li, bi, wb, tt; reg [32*BURST_LEN-1:0] burst_data; begin for (li = 0; li < L; li = li + 1) begin for (bi = 0; bi < (LAYER_BYTES/(4*BURST_LEN)); bi = bi + 1) begin for (wb = 0; wb < BURST_LEN; wb = wb + 1) begin tt = bi*(4*BURST_LEN) + wb*4; burst_data[wb*32 +: 32] = {weight_byte(li, tt+3), weight_byte(li, tt+2), weight_byte(li, tt+1), weight_byte(li, tt)}; end sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data); end end end endtask localparam [MIG_ADDR_WIDTH-1:0] ACT_MEM_BASE = 25'h10000; function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos); act_x_base = {{(ADDR_WIDTH-MIG_ADDR_WIDTH){1'b0}}, ACT_MEM_BASE} + (li*M + pos) * ((N_TILES/4)*BURST_LEN); endfunction task automatic preload_ddr3_activations; integer li, pos, tq, qi; reg [32*BURST_LEN-1:0] burst_data; reg [ADDR_WIDTH-1:0] base; begin for (li = 0; li < L; li = li + 1) begin for (pos = 0; pos < M; pos = pos + 1) begin base = act_x_base(li, pos); for (tq = 0; tq < N_TILES/4; tq = tq + 1) begin burst_data = {(32*BURST_LEN){1'b0}}; for (qi = 0; qi < 4; qi = qi + 1) burst_data[qi*64 +: 64] = {input_byte(li, pos, (4*tq+qi)*P_IN + 7), input_byte(li, pos, (4*tq+qi)*P_IN + 6), input_byte(li, pos, (4*tq+qi)*P_IN + 5), input_byte(li, pos, (4*tq+qi)*P_IN + 4), input_byte(li, pos, (4*tq+qi)*P_IN + 3), input_byte(li, pos, (4*tq+qi)*P_IN + 2), input_byte(li, pos, (4*tq+qi)*P_IN + 1), input_byte(li, pos, (4*tq+qi)*P_IN + 0)}; sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + tq*BURST_LEN, burst_data); end end end end endtask // ---- neural_director_grouped.v ---- reg job_in_valid; wire job_in_ready; reg [ADDR_WIDTH-1:0] job_in_x_base, job_in_w_base, job_in_result_addr; reg [15:0] job_in_n_tiles, job_in_node_id; wire [N_GROUPS-1:0] group_job_start; wire [ADDR_WIDTH*N_GROUPS-1:0] group_w_base; wire [16*N_GROUPS-1:0] group_n_tiles; wire [4*ADDR_WIDTH*N_GROUPS-1:0] group_pe_x_base_a, group_pe_x_base_b; wire [4*ADDR_WIDTH*N_GROUPS-1:0] group_pe_result_addr_a, group_pe_result_addr_b; wire [4*16*N_GROUPS-1:0] group_pe_node_id_a, group_pe_node_id_b; wire [N_GROUPS-1:0] group_job_done; wire job_out_done; wire [$clog2(N_GROUPS)-1:0] job_out_group; wire [3:0] dir_state; wire dir_error; wire queue_empty; neural_director_grouped #( .ADDR_WIDTH(ADDR_WIDTH), .N_GROUPS(N_GROUPS), .QUEUE_DEPTH(QUEUE_DEPTH) ) u_dir ( .clk(ui_clk), .rst(ui_clk_sync_rst), .job_in_valid(job_in_valid), .job_in_ready(job_in_ready), .job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base), .job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr), .job_in_node_id(job_in_node_id), .group_job_start(group_job_start), .group_w_base(group_w_base), .group_n_tiles(group_n_tiles), .group_pe_x_base_a(group_pe_x_base_a), .group_pe_x_base_b(group_pe_x_base_b), .group_pe_result_addr_a(group_pe_result_addr_a), .group_pe_result_addr_b(group_pe_result_addr_b), .group_pe_node_id_a(group_pe_node_id_a), .group_pe_node_id_b(group_pe_node_id_b), .group_job_done(group_job_done), .job_out_done(job_out_done), .job_out_group(job_out_group), .dir_state(dir_state), .dir_error(dir_error), .queue_empty(queue_empty) ); // ---- real, hierarchical 2-level arbiter (EXP-0094): 2 groups' own // weight-fetch + 8 PEs' own activation-fetch+writeback + 1 host // slot, matching n8_system_ddr3_top.v's own real, fixed topology // exactly -- sdram_arbiter_hier.v always includes a host slot, so // this testbench ties it off inactive (doesn't instantiate // host_mem_bridge.v at all, same real precedent as // tb_n2_system_ddr3.v never instantiating spi_host_bridge_v3.v). ---- localparam NUM_REQ = N_GROUPS + N_PES + 1; // 2 + 8 + 1 = 11 localparam HOST_SLOT = NUM_REQ - 1; // 10, tied off inactive below wire [NUM_REQ-1:0] req_active, req_grant, req_req, req_wr; wire [NUM_REQ-1:0] req_ready, req_busy; wire [NUM_REQ*MIG_ADDR_WIDTH-1:0] req_addr; wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata; wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask; wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata; assign req_active[HOST_SLOT] = 1'b0; assign req_req[HOST_SLOT] = 1'b0; assign req_wr[HOST_SLOT] = 1'b0; assign req_addr[HOST_SLOT*MIG_ADDR_WIDTH +: MIG_ADDR_WIDTH] = {MIG_ADDR_WIDTH{1'b0}}; assign req_wdata[HOST_SLOT*32*BURST_LEN +: 32*BURST_LEN] = {(32*BURST_LEN){1'b0}}; assign req_wmask[HOST_SLOT*4*BURST_LEN +: 4*BURST_LEN] = {(4*BURST_LEN){1'b0}}; sdram_arbiter_hier #( .N_GROUPS(N_GROUPS), .PES_PER_GROUP(4), .ADDR_WIDTH(MIG_ADDR_WIDTH), .BURST_LEN(BURST_LEN) ) u_arb ( .clk(ui_clk), .rst(ui_clk_sync_rst), .req_active(req_active), .req_grant(req_grant), .req_req(req_req), .req_wr(req_wr), .req_addr(req_addr), .req_wdata(req_wdata), .req_wmask(req_wmask), .req_rdata(req_rdata), .req_ready(req_ready), .req_busy(req_busy), .ctrl_req(arb_ctrl_req_o), .ctrl_wr(arb_ctrl_wr_o), .ctrl_addr(arb_ctrl_addr_o), .ctrl_wdata(arb_ctrl_wdata_o), .ctrl_wmask(arb_ctrl_wmask_o), .ctrl_rdata(adp_rdata), .ctrl_ready(adp_ready), .ctrl_busy(adp_busy) ); wire [4*DATA_WIDTH*N_GROUPS-1:0] all_pe_result_data_a, all_pe_result_data_b; wire [4*16*N_GROUPS-1:0] all_pe_result_node_id_a, all_pe_result_node_id_b; genvar gg; generate for (gg = 0; gg < N_GROUPS; gg = gg + 1) begin : GEN_GROUP localparam PE_BASE = N_GROUPS + gg*4; systolic_group #( .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH), .LAYER_BYTES(LAYER_BYTES) ) u_group ( .clk(ui_clk), .rst(ui_clk_sync_rst), .job_start(group_job_start[gg]), .w_base(group_w_base[gg*ADDR_WIDTH +: ADDR_WIDTH]), .n_tiles(group_n_tiles[gg*16 +: 16]), .pe_x_base_a(group_pe_x_base_a[gg*4*ADDR_WIDTH +: 4*ADDR_WIDTH]), .pe_x_base_b(group_pe_x_base_b[gg*4*ADDR_WIDTH +: 4*ADDR_WIDTH]), .pe_result_addr_a(group_pe_result_addr_a[gg*4*ADDR_WIDTH +: 4*ADDR_WIDTH]), .pe_result_addr_b(group_pe_result_addr_b[gg*4*ADDR_WIDTH +: 4*ADDR_WIDTH]), .pe_node_id_a(group_pe_node_id_a[gg*4*16 +: 4*16]), .pe_node_id_b(group_pe_node_id_b[gg*4*16 +: 4*16]), .job_done(group_job_done[gg]), .pe_result_data_a(all_pe_result_data_a[gg*4*DATA_WIDTH +: 4*DATA_WIDTH]), .pe_result_data_b(all_pe_result_data_b[gg*4*DATA_WIDTH +: 4*DATA_WIDTH]), .pe_result_node_id_a(all_pe_result_node_id_a[gg*4*16 +: 4*16]), .pe_result_node_id_b(all_pe_result_node_id_b[gg*4*16 +: 4*16]), .pe_result_addr_a_out(), .pe_result_addr_b_out(), .mem_active(req_active[gg]), .mem_grant(req_grant[gg]), .ctrl_req(req_req[gg]), .ctrl_wr(req_wr[gg]), .ctrl_addr(req_addr[gg*MIG_ADDR_WIDTH +: MIG_ADDR_WIDTH]), .ctrl_wdata(req_wdata[gg*32*BURST_LEN +: 32*BURST_LEN]), .ctrl_wmask(req_wmask[gg*4*BURST_LEN +: 4*BURST_LEN]), .ctrl_rdata(req_rdata[gg*32*BURST_LEN +: 32*BURST_LEN]), .ctrl_ready(req_ready[gg]), .ctrl_busy(req_busy[gg]), .pe_mem_active(req_active[PE_BASE +: 4]), .pe_mem_grant(req_grant[PE_BASE +: 4]), .pe_ctrl_req(req_req[PE_BASE +: 4]), .pe_ctrl_wr(req_wr[PE_BASE +: 4]), .pe_ctrl_addr(req_addr[PE_BASE*MIG_ADDR_WIDTH +: 4*MIG_ADDR_WIDTH]), .pe_ctrl_wdata(req_wdata[PE_BASE*32*BURST_LEN +: 4*32*BURST_LEN]), .pe_ctrl_wmask(req_wmask[PE_BASE*4*BURST_LEN +: 4*4*BURST_LEN]), .pe_ctrl_rdata(req_rdata[PE_BASE*32*BURST_LEN +: 4*32*BURST_LEN]), .pe_ctrl_ready(req_ready[PE_BASE +: 4]), .pe_ctrl_busy(req_busy[PE_BASE +: 4]) ); end endgenerate integer errors, tests, completions, n_expected; reg [15:0] expect_node [0:15]; reg signed [7:0] expect_val [0:15]; function automatic signed [7:0] golden_result(input integer li, input integer pos); integer t, acc; reg signed [7:0] r; begin acc = 0; for (t = 0; t < N_INPUTS; t = t + 1) acc = acc + (input_byte(li, pos, t) * weight_byte(li, t)); if (acc <= 0) r = 0; else if (acc > 127) r = 8'sd127; else r = acc[7:0]; golden_result = r; end endfunction task automatic check_completion(input integer idx, input [15:0] nid, input signed [7:0] val); integer ei, found; begin found = 0; for (ei = 0; ei < n_expected; ei = ei + 1) begin if (expect_node[ei] === nid && !found) begin found = 1; tests = tests + 1; if (expect_val[ei] !== val) begin $display("FAIL idx=%0d node_id=%0d: got=%0d expected=%0d", idx, nid, $signed(val), $signed(expect_val[ei])); errors = errors + 1; end else begin $display("PASS idx=%0d node_id=%0d: result=%0d", idx, nid, $signed(val)); end end end end endtask integer gg2, pp2; always @(posedge ui_clk) begin if (!ui_clk_sync_rst) begin for (gg2 = 0; gg2 < N_GROUPS; gg2 = gg2 + 1) begin if (group_job_done[gg2]) begin completions = completions + 8; for (pp2 = 0; pp2 < 4; pp2 = pp2 + 1) begin check_completion(gg2*4+pp2, all_pe_result_node_id_a[(gg2*4+pp2)*16 +: 16], all_pe_result_data_a[(gg2*4+pp2)*DATA_WIDTH +: DATA_WIDTH]); check_completion(gg2*4+pp2, all_pe_result_node_id_b[(gg2*4+pp2)*16 +: 16], all_pe_result_data_b[(gg2*4+pp2)*DATA_WIDTH +: DATA_WIDTH]); end end end end end // real, root-caused fix (EXP-0090, CLAUDE.md): driving stimulus on // @(posedge clk) races the DUT's own posedge-sampling always block // when called back-to-back with zero real simulated gap -- drive on // @(negedge ui_clk) instead. task automatic submit_job( input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb, input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr, input [15:0] nid ); begin @(negedge ui_clk); job_in_valid = 1'b1; job_in_x_base = xb; job_in_w_base = wb; job_in_n_tiles = nt; job_in_result_addr = resaddr; job_in_node_id = nid; @(negedge ui_clk); job_in_valid = 1'b0; end endtask integer pp_i, wd; initial begin errors = 0; tests = 0; completions = 0; n_expected = 0; pre_active = 1'b1; pre_req = 0; pre_wr = 0; pre_addr = 0; pre_wdata = 0; job_in_valid = 0; job_in_x_base = 0; job_in_w_base = 0; job_in_n_tiles = 0; job_in_result_addr = 0; job_in_node_id = 0; $display("=== waiting for real DDR3 init_calib_complete ==="); wait (init_calib_complete); $display("=== calibration done at time %0t ===", $time); repeat (10) @(posedge ui_clk); $display("=== preload SDRAM with %0d resident-filter weight set(s) ===", L); preload_sdram_layers; $display("=== preload SDRAM with real activation data (%0d positions) ===", M); preload_ddr3_activations; @(posedge ui_clk); pre_active = 1'b0; repeat (5) @(posedge ui_clk); $display("=== N=8 hybrid systolic system on REAL DDR3: submitting %0d positions (2 groups x 4 PEs x 2 lanes) ===", M); for (pp_i = 0; pp_i < M; pp_i = pp_i + 1) begin submit_job(act_x_base(0, pp_i), {ADDR_WIDTH{1'b0}}, N_TILES[15:0], 26'h9000 + pp_i, pp_i[15:0]); expect_node[n_expected] = pp_i[15:0]; expect_val[n_expected] = golden_result(0, pp_i); n_expected = n_expected + 1; end wd = 0; while (completions < n_expected && wd < 400000) begin @(posedge ui_clk); wd = wd + 1; end if (completions < n_expected) begin $display("FAIL: only %0d/%0d position-results completed within watchdog", completions, n_expected); errors = errors + 1; end $display("=== %0d/%0d tests, %0d errors, %0d/%0d positions completed ===", tests-errors, tests, errors, completions, n_expected); if (errors == 0 && completions == n_expected) $display("ALL TESTS PASSED (tb_n8_system_ddr3, REAL DDR3)"); $finish; end endmodule