|
| 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