Skip to content

Commit e9835ba

Browse files
committed
test: Add address-gen transpose regressions
TBs drive a transpose request through idma_transpose_midend (so its stride expansion is exercised) into idma_nd_midend + a stock rw_axi / rw_obi backend, checking out_T[c][r]==in[r][c] with no compute engine. Cover int8/fp16/fp32, square/rect/odd, DataWidth 32 and 64; OBI uses native obi_sim_mem. Wired into idma.mk (idma_sim_tb_idma_addrgen_transpose[_obi]).
1 parent 582b712 commit e9835ba

4 files changed

Lines changed: 495 additions & 0 deletions

File tree

Bender.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ sources:
135135
- target/rtl/tb_idma_generated.sv
136136
- test/tb_idma_transpose_nd.sv
137137
- test/tb_idma_transpose_b2b.sv
138+
- test/tb_idma_addrgen_transpose.sv
139+
- test/tb_idma_addrgen_transpose_obi.sv
138140

139141
# Multi-head directed backend testbenches
140142
- target: multihead

idma.mk

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,21 @@ idma_sim_tb_idma_transpose_b2b: $(IDMA_VSIM_DIR)/compile.tcl
387387
cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=32 tb_idma_transpose_b2b -do "run -all; quit"
388388
cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=64 tb_idma_transpose_b2b -do "run -all; quit"
389389

390+
# Address-gen transpose: idma_transpose_midend (AddrGen) -> idma_nd_midend ->
391+
# stock rw_axi / rw_obi backend. No compute engine. TB sweeps geometries; one
392+
# run per bus width.
393+
.PHONY: idma_sim_tb_idma_addrgen_transpose
394+
idma_sim_tb_idma_addrgen_transpose: $(IDMA_VSIM_DIR)/compile.tcl
395+
cd $(IDMA_VSIM_DIR); $(VSIM) -c -do "source compile.tcl; quit"
396+
cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=32 tb_idma_addrgen_transpose -do "run -all; quit"
397+
cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=64 tb_idma_addrgen_transpose -do "run -all; quit"
398+
399+
.PHONY: idma_sim_tb_idma_addrgen_transpose_obi
400+
idma_sim_tb_idma_addrgen_transpose_obi: $(IDMA_VSIM_DIR)/compile.tcl
401+
cd $(IDMA_VSIM_DIR); $(VSIM) -c -do "source compile.tcl; quit"
402+
cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=32 tb_idma_addrgen_transpose_obi -do "run -all; quit"
403+
cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=64 tb_idma_addrgen_transpose_obi -do "run -all; quit"
404+
390405
.PHONY: idma_sim_tb_idma_transpose_midend
391406
idma_sim_tb_idma_transpose_midend: $(IDMA_VSIM_DIR)/compile.tcl
392407
cd $(IDMA_VSIM_DIR); $(VSIM) -c -do "source compile.tcl; quit"

test/tb_idma_addrgen_transpose.sv

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2026 ETH Zurich and University of Bologna.
2+
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
3+
// SPDX-License-Identifier: SHL-0.51
4+
5+
// Authors:
6+
// - Daniel Keller <dankeller@iis.ee.ethz.ch>
7+
8+
// Address-gen transpose proof: NO compute engine. A plain rw_axi backend
9+
// transposes an M x N matrix purely via a 2D ND program with swapped strides
10+
// and a one-element (EB-byte) inner burst. out_T[c][r] = in[r][c], dst is a
11+
// contiguous N x M transpose (no padding). Validates that iDMA's read->FIFO->
12+
// write datapath transposes by addressing alone, the basis for the OBI/TCDM
13+
// multi-write transpose path.
14+
15+
`include "axi/typedef.svh"
16+
`include "idma/typedef.svh"
17+
18+
module tb_idma_addrgen_transpose
19+
import idma_pkg::*;
20+
#(
21+
parameter int unsigned DataWidth = 32,
22+
parameter int unsigned AddrWidth = 32,
23+
parameter int unsigned UserWidth = 1,
24+
parameter int unsigned AxiIdWidth = 12,
25+
parameter int unsigned TFLenWidth = 32,
26+
parameter int unsigned BufferDepth = 3
27+
);
28+
29+
localparam time TA = 1ns;
30+
localparam time TT = 9ns;
31+
localparam time TCK = 10ns;
32+
33+
localparam int unsigned StrbWidth = DataWidth / 8;
34+
localparam int unsigned AxIF = 8;
35+
// base burst = 1 element (dim 0); two repetition dims (col, row) => NumDim=3
36+
localparam int unsigned NumDim = 3;
37+
localparam logic [NumDim-1:0][31:0] RepWidths = '{default: 32'd16};
38+
39+
// Geometry cases (M, N, EB): square, rectangular, odd, int8/fp16/fp32.
40+
localparam int unsigned NCases = 8;
41+
localparam int unsigned Cases [NCases][3] = '{
42+
'{ 4, 4, 1}, '{ 8, 8, 1}, '{ 8, 4, 1}, '{ 4, 8, 1},
43+
'{ 6, 5, 1}, '{ 5, 7, 2}, '{ 3, 9, 4}, '{16, 16, 1}
44+
};
45+
46+
// ── Types ──
47+
typedef logic [AddrWidth-1:0] addr_t;
48+
typedef logic [DataWidth-1:0] data_t;
49+
typedef logic [StrbWidth-1:0] strb_t;
50+
typedef logic [AxiIdWidth-1:0] id_t;
51+
typedef logic [UserWidth-1:0] user_t;
52+
typedef logic [TFLenWidth-1:0] tf_len_t;
53+
typedef logic [31:0] reps_t;
54+
55+
`AXI_TYPEDEF_AW_CHAN_T(axi_aw_chan_t, addr_t, id_t, user_t)
56+
`AXI_TYPEDEF_W_CHAN_T(axi_w_chan_t, data_t, strb_t, user_t)
57+
`AXI_TYPEDEF_B_CHAN_T(axi_b_chan_t, id_t, user_t)
58+
`AXI_TYPEDEF_AR_CHAN_T(axi_ar_chan_t, addr_t, id_t, user_t)
59+
`AXI_TYPEDEF_R_CHAN_T(axi_r_chan_t, data_t, id_t, user_t)
60+
`AXI_TYPEDEF_REQ_T(axi_req_t, axi_aw_chan_t, axi_w_chan_t, axi_ar_chan_t)
61+
`AXI_TYPEDEF_RESP_T(axi_rsp_t, axi_b_chan_t, axi_r_chan_t)
62+
63+
`IDMA_TYPEDEF_FULL_REQ_T(idma_req_t, id_t, addr_t, tf_len_t)
64+
`IDMA_TYPEDEF_FULL_RSP_T(idma_rsp_t, addr_t)
65+
`IDMA_TYPEDEF_FULL_ND_REQ_T(idma_nd_req_t, idma_req_t, reps_t, addr_t)
66+
67+
typedef struct packed { axi_ar_chan_t ar_chan; } axi_read_meta_channel_t;
68+
typedef struct packed { axi_read_meta_channel_t axi; } read_meta_channel_t;
69+
typedef struct packed { axi_aw_chan_t aw_chan; } axi_write_meta_channel_t;
70+
typedef struct packed { axi_write_meta_channel_t axi; } write_meta_channel_t;
71+
72+
// ── Signals ──
73+
logic clk, rst_n;
74+
idma_req_t idma_req; logic req_valid, req_ready;
75+
idma_rsp_t idma_rsp; logic rsp_valid, rsp_ready;
76+
idma_eh_req_t idma_eh_req; logic eh_req_valid, eh_req_ready;
77+
idma_nd_req_t tp_req; logic tp_valid, tp_ready; // TB -> transpose midend
78+
idma_nd_req_t nd_req; logic nd_req_valid, nd_req_ready; // transpose midend -> nd midend
79+
idma_rsp_t nd_rsp; logic nd_rsp_valid, nd_rsp_ready;
80+
axi_req_t axi_read_req, axi_write_req, axi_req, axi_req_mem;
81+
axi_rsp_t axi_read_rsp, axi_write_rsp, axi_rsp, axi_rsp_mem;
82+
idma_busy_t busy; logic nd_busy;
83+
84+
assign idma_eh_req = '0;
85+
assign eh_req_valid = 1'b0;
86+
87+
// ── Clock / reset ──
88+
clk_rst_gen #(.ClkPeriod(TCK), .RstClkCycles(1)) i_clk_rst_gen (.clk_o(clk), .rst_no(rst_n));
89+
90+
// ── AXI sim memory (read+write joined) ──
91+
axi_rw_join #(.axi_req_t(axi_req_t), .axi_resp_t(axi_rsp_t)) i_axi_rw_join (
92+
.clk_i(clk), .rst_ni(rst_n),
93+
.slv_read_req_i(axi_read_req), .slv_read_resp_o(axi_read_rsp),
94+
.slv_write_req_i(axi_write_req), .slv_write_resp_o(axi_write_rsp),
95+
.mst_req_o(axi_req), .mst_resp_i(axi_rsp)
96+
);
97+
assign axi_req_mem = axi_req;
98+
assign axi_rsp = axi_rsp_mem;
99+
100+
axi_sim_mem #(
101+
.AddrWidth(AddrWidth), .DataWidth(DataWidth), .IdWidth(AxiIdWidth), .UserWidth(UserWidth),
102+
.axi_req_t(axi_req_t), .axi_rsp_t(axi_rsp_t),
103+
.WarnUninitialized(1'b0), .ClearErrOnAccess(1'b1), .ApplDelay(TA), .AcqDelay(TT)
104+
) i_axi_sim_mem (
105+
.clk_i(clk), .rst_ni(rst_n), .axi_req_i(axi_req_mem), .axi_rsp_o(axi_rsp_mem),
106+
.mon_r_last_o(), .mon_r_beat_count_o(), .mon_r_user_o(), .mon_r_id_o(),
107+
.mon_r_data_o(), .mon_r_addr_o(), .mon_r_valid_o(),
108+
.mon_w_last_o(), .mon_w_beat_count_o(), .mon_w_user_o(), .mon_w_id_o(),
109+
.mon_w_data_o(), .mon_w_addr_o(), .mon_w_valid_o()
110+
);
111+
112+
// ── Transpose midend (RTL UNDER TEST): expands the transpose request into the
113+
// swapped-stride ND program; the nd_midend + backend just execute it ──
114+
idma_transpose_midend #(
115+
.NumDim(NumDim), .AddrGenTranspose(1'b1), .StrbWidth(StrbWidth),
116+
.addr_t(addr_t), .idma_nd_req_t(idma_nd_req_t)
117+
) i_xpose_midend (
118+
.nd_req_i(tp_req), .valid_i(tp_valid), .ready_o(tp_ready),
119+
.nd_req_o(nd_req), .valid_o(nd_req_valid), .ready_i(nd_req_ready)
120+
);
121+
122+
// ── ND midend: walks the swapped-stride program into 1-element bursts ──
123+
idma_nd_midend #(
124+
.NumDim(NumDim), .addr_t(addr_t), .idma_req_t(idma_req_t),
125+
.idma_rsp_t(idma_rsp_t), .idma_nd_req_t(idma_nd_req_t), .RepWidths(RepWidths)
126+
) i_nd_midend (
127+
.clk_i(clk), .rst_ni(rst_n),
128+
.nd_req_i(nd_req), .nd_req_valid_i(nd_req_valid), .nd_req_ready_o(nd_req_ready),
129+
.nd_rsp_o(nd_rsp), .nd_rsp_valid_o(nd_rsp_valid), .nd_rsp_ready_i(nd_rsp_ready),
130+
.burst_req_o(idma_req), .burst_req_valid_o(req_valid), .burst_req_ready_i(req_ready),
131+
.burst_rsp_i(idma_rsp), .burst_rsp_valid_i(rsp_valid), .burst_rsp_ready_o(rsp_ready),
132+
.busy_o(nd_busy)
133+
);
134+
135+
// ── Backend (rw_axi), plain copy — NO compute engine ──
136+
idma_backend_rw_axi #(
137+
.CombinedShifter(1'b0), .DataWidth(DataWidth), .AddrWidth(AddrWidth), .AxiIdWidth(AxiIdWidth),
138+
.UserWidth(UserWidth), .TFLenWidth(TFLenWidth), .MaskInvalidData(1'b1), .BufferDepth(BufferDepth),
139+
.RAWCouplingAvail(1'b1), .HardwareLegalizer(1'b1), .RejectZeroTransfers(1'b1),
140+
.ErrorCap(idma_pkg::NO_ERROR_HANDLING), .PrintFifoInfo(1'b0), .NumAxInFlight(AxIF), .MemSysDepth(0),
141+
.idma_req_t(idma_req_t), .idma_rsp_t(idma_rsp_t), .idma_eh_req_t(idma_eh_req_t),
142+
.idma_busy_t(idma_busy_t), .axi_req_t(axi_req_t), .axi_rsp_t(axi_rsp_t),
143+
.write_meta_channel_t(write_meta_channel_t), .read_meta_channel_t(read_meta_channel_t)
144+
) i_idma_backend (
145+
.clk_i(clk), .rst_ni(rst_n), .testmode_i(1'b0),
146+
.idma_req_i(idma_req), .req_valid_i(req_valid), .req_ready_o(req_ready),
147+
.idma_rsp_o(idma_rsp), .rsp_valid_o(rsp_valid), .rsp_ready_i(rsp_ready),
148+
.idma_eh_req_i(idma_eh_req), .eh_req_valid_i(eh_req_valid), .eh_req_ready_o(eh_req_ready),
149+
.axi_read_req_o(axi_read_req), .axi_read_rsp_i(axi_read_rsp),
150+
.axi_write_req_o(axi_write_req), .axi_write_rsp_i(axi_write_rsp), .busy_o(busy)
151+
);
152+
153+
stream_watchdog #(.NumCycles(4000)) i_r_wd (.clk_i(clk), .rst_ni(rst_n), .valid_i(axi_rsp.r_valid), .ready_i(axi_req.r_ready));
154+
stream_watchdog #(.NumCycles(4000)) i_w_wd (.clk_i(clk), .rst_ni(rst_n), .valid_i(axi_req.w_valid), .ready_i(axi_rsp.w_ready));
155+
156+
// ── Stimulus + check via sim-memory backdoor ──
157+
addr_t sb = 'h0000_1000;
158+
addr_t db = 'h0000_8000;
159+
160+
task automatic wr_mem(input addr_t a, input logic [7:0] d); i_axi_sim_mem.mem[a] = d; endtask
161+
function automatic logic [7:0] rd_mem(input addr_t a);
162+
return i_axi_sim_mem.mem.exists(a) ? i_axi_sim_mem.mem[a] : 8'hxx;
163+
endfunction
164+
165+
// Run one M x N transpose of EB-byte elements; returns the mismatch count.
166+
task automatic run_case(input int unsigned m, input int unsigned n, input int unsigned eb,
167+
output int unsigned errs);
168+
errs = 0;
169+
170+
// init source matrix (row-major, m x n elements of eb bytes), unique fingerprint
171+
for (int unsigned r = 0; r < m; r++)
172+
for (int unsigned c = 0; c < n; c++)
173+
for (int unsigned b = 0; b < eb; b++)
174+
wr_mem(sb + (r*n + c)*eb + b, 8'((( (r*n+c)*eb + b )*7 + 3) & 8'hFF));
175+
// sentinel-fill the contiguous N x M dst
176+
for (int unsigned k = 0; k < n*m*eb; k++) wr_mem(db + k, 8'hCC);
177+
178+
// Drive a transpose request THROUGH idma_transpose_midend (it computes the
179+
// swapped-stride program); the TB sets only the compute fields + addresses,
180+
// NOT the strides — so the midend's expansion is what gets exercised.
181+
tp_req = '0;
182+
tp_req.burst_req.src_addr = sb;
183+
tp_req.burst_req.dst_addr = db;
184+
tp_req.burst_req.opt.src_protocol = idma_pkg::AXI;
185+
tp_req.burst_req.opt.dst_protocol = idma_pkg::AXI;
186+
tp_req.burst_req.opt.src.burst = axi_pkg::BURST_INCR;
187+
tp_req.burst_req.opt.dst.burst = axi_pkg::BURST_INCR;
188+
tp_req.burst_req.opt.beo.decouple_rw = 1'b1;
189+
tp_req.burst_req.opt.beo.decouple_aw = 1'b1;
190+
tp_req.burst_req.opt.last = 1'b1;
191+
tp_req.burst_req.opt.compute.enable = 1'b1;
192+
tp_req.burst_req.opt.compute.op = idma_pkg::COMPUTE_TRANSPOSE;
193+
tp_req.burst_req.opt.compute.params.transpose.mode = 2'(eb == 4 ? 2 : eb == 2 ? 1 : 0);
194+
tp_req.burst_req.opt.compute.params.transpose.tensor_m = 12'(m);
195+
tp_req.burst_req.opt.compute.params.transpose.tensor_n = 12'(n);
196+
197+
$display("[AG] case %0dx%0d EB=%0d (via transpose_midend, %0d elements)", m, n, eb, m*n);
198+
tp_valid = 1'b1;
199+
do @(posedge clk); while (!tp_ready);
200+
tp_valid = 1'b0;
201+
tp_req = '0;
202+
203+
while (!(nd_rsp_valid && nd_rsp_ready)) @(posedge clk);
204+
repeat (20) @(posedge clk);
205+
206+
// check: out_T[c][r] == in[r][c], dst contiguous N x M (pitch M)
207+
for (int unsigned c = 0; c < n; c++)
208+
for (int unsigned r = 0; r < m; r++)
209+
for (int unsigned b = 0; b < eb; b++) begin
210+
automatic logic [7:0] got = rd_mem(db + (c*m + r)*eb + b);
211+
automatic logic [7:0] exp = rd_mem(sb + (r*n + c)*eb + b);
212+
if (got !== exp) begin
213+
errs++;
214+
if (errs <= 12) $display("[AG] MISMATCH out_T[%0d][%0d].b%0d=%02h exp %02h", c, r, b, got, exp);
215+
end
216+
end
217+
endtask
218+
219+
initial begin
220+
automatic int unsigned total = 0;
221+
automatic int unsigned ce;
222+
tp_valid = 1'b0; nd_rsp_ready = 1'b1; tp_req = '0;
223+
@(posedge rst_n);
224+
repeat (5) @(posedge clk);
225+
226+
for (int unsigned k = 0; k < NCases; k++) begin
227+
if (Cases[k][2] > StrbWidth) continue;
228+
run_case(Cases[k][0], Cases[k][1], Cases[k][2], ce);
229+
if (ce == 0) $display("[AG] PASS: %0dx%0d EB=%0d", Cases[k][0], Cases[k][1], Cases[k][2]);
230+
else $display("[AG] FAIL: %0dx%0d EB=%0d (%0d mismatches)", Cases[k][0], Cases[k][1], Cases[k][2], ce);
231+
total += ce;
232+
end
233+
234+
if (total == 0) $display("[AG] ALL PASS (%0d cases, StrbWidth=%0d)", NCases, StrbWidth);
235+
else $fatal(1, "[AG] FAIL: %0d total mismatches", total);
236+
repeat (5) @(posedge clk);
237+
$finish();
238+
end
239+
240+
initial begin #100_000_000; $fatal(1, "[AG] timeout"); end
241+
242+
endmodule

0 commit comments

Comments
 (0)