diff --git a/Bender.yml b/Bender.yml index 2049f69e..72eeef9e 100644 --- a/Bender.yml +++ b/Bender.yml @@ -135,6 +135,8 @@ sources: - target/rtl/tb_idma_generated.sv - test/tb_idma_transpose_nd.sv - test/tb_idma_transpose_b2b.sv + - test/tb_idma_addrgen_transpose.sv + - test/tb_idma_addrgen_transpose_obi.sv # Multi-head directed backend testbenches - target: multihead diff --git a/idma.mk b/idma.mk index 879be491..8779c872 100644 --- a/idma.mk +++ b/idma.mk @@ -387,6 +387,21 @@ idma_sim_tb_idma_transpose_b2b: $(IDMA_VSIM_DIR)/compile.tcl cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=32 tb_idma_transpose_b2b -do "run -all; quit" cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=64 tb_idma_transpose_b2b -do "run -all; quit" +# Address-gen transpose: idma_transpose_midend (AddrGen) -> idma_nd_midend -> +# stock rw_axi / rw_obi backend. No compute engine. TB sweeps geometries; one +# run per bus width. +.PHONY: idma_sim_tb_idma_addrgen_transpose +idma_sim_tb_idma_addrgen_transpose: $(IDMA_VSIM_DIR)/compile.tcl + cd $(IDMA_VSIM_DIR); $(VSIM) -c -do "source compile.tcl; quit" + cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=32 tb_idma_addrgen_transpose -do "run -all; quit" + cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=64 tb_idma_addrgen_transpose -do "run -all; quit" + +.PHONY: idma_sim_tb_idma_addrgen_transpose_obi +idma_sim_tb_idma_addrgen_transpose_obi: $(IDMA_VSIM_DIR)/compile.tcl + cd $(IDMA_VSIM_DIR); $(VSIM) -c -do "source compile.tcl; quit" + cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=32 tb_idma_addrgen_transpose_obi -do "run -all; quit" + cd $(IDMA_VSIM_DIR); $(VSIM) -c -t 1ps -voptargs=+acc -gDataWidth=64 tb_idma_addrgen_transpose_obi -do "run -all; quit" + .PHONY: idma_sim_tb_idma_transpose_midend idma_sim_tb_idma_transpose_midend: $(IDMA_VSIM_DIR)/compile.tcl cd $(IDMA_VSIM_DIR); $(VSIM) -c -do "source compile.tcl; quit" diff --git a/src/frontend/inst64/idma_inst64_top.sv b/src/frontend/inst64/idma_inst64_top.sv index 4f93b55f..ed19f65a 100644 --- a/src/frontend/inst64/idma_inst64_top.sv +++ b/src/frontend/inst64/idma_inst64_top.sv @@ -22,6 +22,10 @@ module idma_inst64_top #( parameter int unsigned NumChannels = 32'd1, parameter bit TCDMAliasEnable = 1'b0, parameter int unsigned DMATracing = 32'd0, + /// Compile-time on-the-fly compute feature enables (e.g. transpose). This + /// multi-write backend cannot host the FF transpose engine, so transpose is + /// always executed by address generation (idma_transpose_midend AddrGen). + parameter idma_pkg::compute_enable_t ComputeEnable = '0, parameter type axi_ar_chan_t = logic, parameter type axi_aw_chan_t = logic, parameter type axi_req_t = logic, @@ -70,7 +74,7 @@ module idma_inst64_top #( localparam int unsigned TfIdWidth = 32'd32; localparam int unsigned TFLenWidth = AxiAddrWidth; localparam int unsigned RepWidth = 32'd32; - localparam int unsigned NumDim = 32'd2; + localparam int unsigned NumDim = ComputeEnable.transpose ? 32'd4 : 32'd2; localparam int unsigned BufferDepth = 32'd3; localparam int unsigned NumRules = 32'd5; @@ -84,7 +88,8 @@ module idma_inst64_top #( localparam type id_t = logic[AxiIdWidth-1:0]; localparam type tf_len_t = logic[TFLenWidth-1:0]; localparam type offset_t = logic[OffsetWidth-1:0]; - localparam type strides_t = logic[RepWidth-1:0]; + // strides must match addr_t: signed transpose deltas would not sign-extend if narrower + localparam type strides_t = addr_t; localparam type reps_t = logic[RepWidth-1:0]; localparam type tf_id_t = logic[TfIdWidth-1:0]; @@ -178,6 +183,7 @@ module idma_inst64_top #( logic [1:0] idma_fe_status; logic [2:0] idma_fe_sel_chan; logic idma_fe_twod; + logic idma_fe_tp_reject; // busy signals idma_pkg::idma_busy_t [NumChannels-1:0] idma_busy; @@ -348,7 +354,7 @@ module idma_inst64_top #( .idma_req_t ( idma_req_t ), .idma_rsp_t ( idma_rsp_t ), .idma_nd_req_t ( idma_nd_req_t ), - .RepWidths ( RepWidth ) + .RepWidths ( {NumDim{RepWidth}} ) ) i_idma_nd_midend ( .clk_i, .rst_ni, @@ -367,6 +373,33 @@ module idma_inst64_top #( .busy_o ( idma_nd_busy [c] ) ); + // FIFO output, before transpose expansion + idma_nd_req_t fifo_nd_req; + logic fifo_nd_valid, fifo_nd_ready; + + // expand transpose requests into the address-gen ND walk (this backend + // has no FF engine, so transpose is always address generation) + if (ComputeEnable.transpose) begin : gen_transpose + idma_transpose_midend #( + .NumDim ( NumDim ), + .AddrGenTranspose ( 1'b1 ), + .StrbWidth ( StrbWidth ), + .addr_t ( addr_t ), + .idma_nd_req_t ( idma_nd_req_t ) + ) i_idma_transpose_midend ( + .nd_req_i ( fifo_nd_req ), + .valid_i ( fifo_nd_valid ), + .ready_o ( fifo_nd_ready ), + .nd_req_o ( idma_nd_req [c] ), + .valid_o ( idma_nd_req_valid [c] ), + .ready_i ( idma_nd_req_ready [c] ) + ); + end else begin : gen_no_transpose + assign idma_nd_req [c] = fifo_nd_req; + assign idma_nd_req_valid [c] = fifo_nd_valid; + assign fifo_nd_ready = idma_nd_req_ready [c]; + end + stream_fifo_optimal_wrap #( .Depth ( DMAReqFifoDepth ), .type_t ( idma_nd_req_t ), @@ -380,9 +413,9 @@ module idma_inst64_top #( .data_i ( idma_fe_req ), .valid_i ( idma_fe_req_valid [c] ), .ready_o ( idma_fe_req_ready [c] ), - .data_o ( idma_nd_req [c] ), - .valid_o ( idma_nd_req_valid [c] ), - .ready_i ( idma_nd_req_ready [c] ) + .data_o ( fifo_nd_req ), + .valid_o ( fifo_nd_valid ), + .ready_i ( fifo_nd_ready ) ); end @@ -519,10 +552,12 @@ module idma_inst64_top #( idma_fe_req_d.burst_req.opt.beo.src_reduce_len = 1'b0; idma_fe_req_d.burst_req.opt.beo.dst_reduce_len = 1'b0; idma_fe_req_d.burst_req.opt.last = 1'b0; + idma_fe_req_d.burst_req.opt.compute = '0; // frontend config idma_fe_cfg = '0; idma_fe_status = '0; + idma_fe_tp_reject = 1'b0; idma_fe_sel_chan = '0; // default handshaking @@ -573,6 +608,32 @@ module idma_inst64_top #( idma_inst64_snitch_pkg::DMCPY : begin idma_fe_cfg = acc_req_i.data_argb[1:0]; idma_fe_sel_chan = acc_req_i.data_argb[4:2]; + // transpose request (register form only): argb spare bits + // carry {enable, mode, tensor_m, tensor_n} + if (ComputeEnable.transpose && acc_req_i.data_argb[5]) begin + idma_fe_req_d.burst_req.opt.compute.enable = 1'b1; + idma_fe_req_d.burst_req.opt.compute.op = + idma_pkg::COMPUTE_TRANSPOSE; + idma_fe_req_d.burst_req.opt.compute.params.transpose.mode = + acc_req_i.data_argb[7:6]; + idma_fe_req_d.burst_req.opt.compute.params.transpose.tensor_m = + acc_req_i.data_argb[19:8]; + idma_fe_req_d.burst_req.opt.compute.params.transpose.tensor_n = + acc_req_i.data_argb[31:20]; + end + // reject malformed transpose: feature off, reserved + // mode, zero dim, twod (cfg[1]), or src/dst not E-aligned + if (acc_req_i.data_argb[5]) begin + idma_fe_tp_reject = !ComputeEnable.transpose + | (acc_req_i.data_argb[7:6] == 2'd3) + | (acc_req_i.data_argb[19:8] == '0) + | (acc_req_i.data_argb[31:20] == '0) + | acc_req_i.data_argb[1] + | (|(idma_fe_req_d.burst_req.src_addr + & addr_t'((32'd1 << acc_req_i.data_argb[7:6]) - 32'd1))) + | (|(idma_fe_req_d.burst_req.dst_addr + & addr_t'((32'd1 << acc_req_i.data_argb[7:6]) - 32'd1))); + end end default:; endcase @@ -588,7 +649,15 @@ module idma_inst64_top #( // 3. wait for twod transfer to be accepted (ready) // 4. send acc response (pvalid) // 5. acknowledge acc request (qready) - if (acc_res_ready) begin + // DMCPY launch; transpose requests reject malformed configs + if (idma_fe_tp_reject) begin + // error response; the transfer is not launched + if (acc_res_ready) begin + acc_res.id = acc_req_i.id; + acc_res_valid = 1'b1; + acc_req_ready_o = 1'b1; + end + end else if (acc_res_ready) begin idma_fe_req_valid[idma_fe_sel_chan] = 1'b1; if (idma_fe_req_ready[idma_fe_sel_chan]) begin acc_res.id = acc_req_i.id; @@ -750,6 +819,12 @@ module idma_inst64_top #( if (!idma_fe_twod) begin idma_fe_req.d_req[0].reps = 'd1; end + // keep higher dims inert for plain requests (the transpose expander overwrites them) + for (int d = 1; d <= NumDim-2; d++) begin + idma_fe_req.d_req[d].reps = 'd1; + idma_fe_req.d_req[d].src_strides = '0; + idma_fe_req.d_req[d].dst_strides = '0; + end end //-------------------------------------- @@ -763,6 +838,8 @@ module idma_inst64_top #( //-------------------------------------- // only activate tracer if requested `ifndef SYNTHESIS + initial assert (idma_pkg::TransposeDimWidth == 32'd12) else + $fatal(1, "DMCPY argb transpose packing requires TransposeDimWidth == 12"); if (DMATracing) begin : gen_tracer for (genvar c = 0; c < NumChannels; c++) begin : gen_channels // derive the name of the trace file from the hart and channel IDs diff --git a/src/midend/idma_transpose_midend.sv b/src/midend/idma_transpose_midend.sv index b6f66325..fdea64e3 100644 --- a/src/midend/idma_transpose_midend.sv +++ b/src/midend/idma_transpose_midend.sv @@ -5,12 +5,12 @@ // Authors: // - Daniel Keller -/// Transpose geometry expander: expands an opt.compute=TRANSPOSE request into a -/// NumDim=4 tiled ND walk for the generic idma_nd_midend. Non-transpose passes -/// through. Combinational, quasi-static per request. +/// Expand a TRANSPOSE request into an idma_nd_midend walk: engine (tiled) or address-gen. module idma_transpose_midend #( - /// Number of ND dimensions (must be >= 4 to express the tiled walk) + /// Number of ND dimensions (engine walk needs >= 4; address-gen needs >= 3) parameter int unsigned NumDim = 32'd4, + /// Address-gen mode: element-granular swapped-stride transpose, no engine + parameter bit AddrGenTranspose = 1'b0, /// Write data-path width in bytes (tile side NE = StrbWidth / element bytes) parameter int unsigned StrbWidth = 32'd64, /// Address type @@ -51,6 +51,7 @@ module idma_transpose_midend #( logic [TensorW-1:0] tm, tn; logic signed [WorkW-1:0] m, n, log2ne, ne, yt, nt, nxe, mpe; logic signed [WorkW-1:0] strb_c; // NE*E == StrbWidth (mode cancels) + logic signed [WorkW-1:0] e, me; // address-gen: E (=1<= 4) else - $fatal(1, "idma_transpose_midend requires NumDim >= 4 (got %0d)", NumDim); + initial assert (NumDim >= (AddrGenTranspose ? 32'd3 : 32'd4)) else + $fatal(1, "idma_transpose_midend: NumDim too small (got %0d)", NumDim); // mode 0..2 needs NE >= 1, i.e. log2(StrbWidth) >= 2 initial assert (Log2Strb >= 2) else $fatal(1, "idma_transpose_midend requires StrbWidth >= 4 (got %0d)", StrbWidth); diff --git a/test/tb_idma_addrgen_transpose.sv b/test/tb_idma_addrgen_transpose.sv new file mode 100644 index 00000000..87b9f386 --- /dev/null +++ b/test/tb_idma_addrgen_transpose.sv @@ -0,0 +1,247 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +// Authors: +// - Daniel Keller + +// Address-gen transpose (no engine): rw_axi transposes M x N via a swapped-stride ND program. + +`include "axi/typedef.svh" +`include "idma/typedef.svh" + +module tb_idma_addrgen_transpose + import idma_pkg::*; +#( + parameter int unsigned DataWidth = 32, + parameter int unsigned AddrWidth = 32, + parameter int unsigned UserWidth = 1, + parameter int unsigned AxiIdWidth = 12, + parameter int unsigned TFLenWidth = 32, + parameter int unsigned BufferDepth = 3 +); + + localparam time TA = 1ns; + localparam time TT = 9ns; + localparam time TCK = 10ns; + + localparam int unsigned StrbWidth = DataWidth / 8; + localparam int unsigned AxIF = 8; + // base burst = 1 element (dim 0); two repetition dims (col, row) => NumDim=3 + localparam int unsigned NumDim = 3; + localparam logic [NumDim-1:0][31:0] RepWidths = '{default: 32'd16}; + + // Geometry cases (M, N, EB): square, rectangular, odd; int8/fp16/fp32 each + // with square + rectangular coverage. + localparam int unsigned NCases = 12; + localparam int unsigned Cases [NCases][3] = '{ + '{ 4, 4, 1}, '{ 8, 8, 1}, '{ 8, 4, 1}, '{ 4, 8, 1}, + '{ 6, 5, 1}, '{ 5, 7, 2}, '{ 3, 9, 4}, '{16, 16, 1}, + '{ 8, 8, 2}, '{ 8, 8, 4}, '{10, 6, 2}, '{12, 8, 4} + }; + + // ── Types ── + typedef logic [AddrWidth-1:0] addr_t; + typedef logic [DataWidth-1:0] data_t; + typedef logic [StrbWidth-1:0] strb_t; + typedef logic [AxiIdWidth-1:0] id_t; + typedef logic [UserWidth-1:0] user_t; + typedef logic [TFLenWidth-1:0] tf_len_t; + typedef logic [31:0] reps_t; + + `AXI_TYPEDEF_AW_CHAN_T(axi_aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(axi_w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(axi_b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(axi_ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(axi_r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, axi_aw_chan_t, axi_w_chan_t, axi_ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_rsp_t, axi_b_chan_t, axi_r_chan_t) + + `IDMA_TYPEDEF_FULL_REQ_T(idma_req_t, id_t, addr_t, tf_len_t) + `IDMA_TYPEDEF_FULL_RSP_T(idma_rsp_t, addr_t) + `IDMA_TYPEDEF_FULL_ND_REQ_T(idma_nd_req_t, idma_req_t, reps_t, addr_t) + + typedef struct packed { axi_ar_chan_t ar_chan; } axi_read_meta_channel_t; + typedef struct packed { axi_read_meta_channel_t axi; } read_meta_channel_t; + typedef struct packed { axi_aw_chan_t aw_chan; } axi_write_meta_channel_t; + typedef struct packed { axi_write_meta_channel_t axi; } write_meta_channel_t; + + // ── Signals ── + logic clk, rst_n; + idma_req_t idma_req; logic req_valid, req_ready; + idma_rsp_t idma_rsp; logic rsp_valid, rsp_ready; + idma_eh_req_t idma_eh_req; logic eh_req_valid, eh_req_ready; + idma_nd_req_t tp_req; logic tp_valid, tp_ready; // TB -> transpose midend + idma_nd_req_t nd_req; logic nd_req_valid, nd_req_ready; // transpose midend -> nd midend + idma_rsp_t nd_rsp; logic nd_rsp_valid, nd_rsp_ready; + axi_req_t axi_read_req, axi_write_req, axi_req, axi_req_mem; + axi_rsp_t axi_read_rsp, axi_write_rsp, axi_rsp, axi_rsp_mem; + idma_busy_t busy; logic nd_busy; + + assign idma_eh_req = '0; + assign eh_req_valid = 1'b0; + + // ── Clock / reset ── + clk_rst_gen #(.ClkPeriod(TCK), .RstClkCycles(1)) i_clk_rst_gen (.clk_o(clk), .rst_no(rst_n)); + + // ── AXI sim memory (read+write joined) ── + axi_rw_join #(.axi_req_t(axi_req_t), .axi_resp_t(axi_rsp_t)) i_axi_rw_join ( + .clk_i(clk), .rst_ni(rst_n), + .slv_read_req_i(axi_read_req), .slv_read_resp_o(axi_read_rsp), + .slv_write_req_i(axi_write_req), .slv_write_resp_o(axi_write_rsp), + .mst_req_o(axi_req), .mst_resp_i(axi_rsp) + ); + assign axi_req_mem = axi_req; + assign axi_rsp = axi_rsp_mem; + + axi_sim_mem #( + .AddrWidth(AddrWidth), .DataWidth(DataWidth), .IdWidth(AxiIdWidth), .UserWidth(UserWidth), + .axi_req_t(axi_req_t), .axi_rsp_t(axi_rsp_t), + .WarnUninitialized(1'b0), .ClearErrOnAccess(1'b1), .ApplDelay(TA), .AcqDelay(TT) + ) i_axi_sim_mem ( + .clk_i(clk), .rst_ni(rst_n), .axi_req_i(axi_req_mem), .axi_rsp_o(axi_rsp_mem), + .mon_r_last_o(), .mon_r_beat_count_o(), .mon_r_user_o(), .mon_r_id_o(), + .mon_r_data_o(), .mon_r_addr_o(), .mon_r_valid_o(), + .mon_w_last_o(), .mon_w_beat_count_o(), .mon_w_user_o(), .mon_w_id_o(), + .mon_w_data_o(), .mon_w_addr_o(), .mon_w_valid_o() + ); + + // ── Transpose midend (RTL UNDER TEST): expands the transpose request into the + // swapped-stride ND program; the nd_midend + backend just execute it ── + idma_transpose_midend #( + .NumDim(NumDim), .AddrGenTranspose(1'b1), .StrbWidth(StrbWidth), + .addr_t(addr_t), .idma_nd_req_t(idma_nd_req_t) + ) i_xpose_midend ( + .nd_req_i(tp_req), .valid_i(tp_valid), .ready_o(tp_ready), + .nd_req_o(nd_req), .valid_o(nd_req_valid), .ready_i(nd_req_ready) + ); + + // ── ND midend: walks the swapped-stride program into 1-element bursts ── + idma_nd_midend #( + .NumDim(NumDim), .addr_t(addr_t), .idma_req_t(idma_req_t), + .idma_rsp_t(idma_rsp_t), .idma_nd_req_t(idma_nd_req_t), .RepWidths(RepWidths) + ) i_nd_midend ( + .clk_i(clk), .rst_ni(rst_n), + .nd_req_i(nd_req), .nd_req_valid_i(nd_req_valid), .nd_req_ready_o(nd_req_ready), + .nd_rsp_o(nd_rsp), .nd_rsp_valid_o(nd_rsp_valid), .nd_rsp_ready_i(nd_rsp_ready), + .burst_req_o(idma_req), .burst_req_valid_o(req_valid), .burst_req_ready_i(req_ready), + .burst_rsp_i(idma_rsp), .burst_rsp_valid_i(rsp_valid), .burst_rsp_ready_o(rsp_ready), + .busy_o(nd_busy) + ); + + // ── Backend (rw_axi), plain copy — NO compute engine ── + idma_backend_rw_axi #( + .CombinedShifter(1'b0), .DataWidth(DataWidth), .AddrWidth(AddrWidth), .AxiIdWidth(AxiIdWidth), + .UserWidth(UserWidth), .TFLenWidth(TFLenWidth), .MaskInvalidData(1'b1), .BufferDepth(BufferDepth), + .RAWCouplingAvail(1'b1), .HardwareLegalizer(1'b1), .RejectZeroTransfers(1'b1), + .ErrorCap(idma_pkg::NO_ERROR_HANDLING), .PrintFifoInfo(1'b0), .NumAxInFlight(AxIF), .MemSysDepth(0), + .idma_req_t(idma_req_t), .idma_rsp_t(idma_rsp_t), .idma_eh_req_t(idma_eh_req_t), + .idma_busy_t(idma_busy_t), .axi_req_t(axi_req_t), .axi_rsp_t(axi_rsp_t), + .write_meta_channel_t(write_meta_channel_t), .read_meta_channel_t(read_meta_channel_t) + ) i_idma_backend ( + .clk_i(clk), .rst_ni(rst_n), .testmode_i(1'b0), + .idma_req_i(idma_req), .req_valid_i(req_valid), .req_ready_o(req_ready), + .idma_rsp_o(idma_rsp), .rsp_valid_o(rsp_valid), .rsp_ready_i(rsp_ready), + .idma_eh_req_i(idma_eh_req), .eh_req_valid_i(eh_req_valid), .eh_req_ready_o(eh_req_ready), + .axi_read_req_o(axi_read_req), .axi_read_rsp_i(axi_read_rsp), + .axi_write_req_o(axi_write_req), .axi_write_rsp_i(axi_write_rsp), .busy_o(busy) + ); + + 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)); + 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)); + + // ── Stimulus + check via sim-memory backdoor ── + addr_t sb = 'h0000_1000; + addr_t db = 'h0000_8000; + + task automatic wr_mem(input addr_t a, input logic [7:0] d); i_axi_sim_mem.mem[a] = d; endtask + function automatic logic [7:0] rd_mem(input addr_t a); + return i_axi_sim_mem.mem.exists(a) ? i_axi_sim_mem.mem[a] : 8'hxx; + endfunction + + // Run one M x N transpose of EB-byte elements; returns the mismatch count. + task automatic run_case(input int unsigned m, input int unsigned n, input int unsigned eb, + input bit corrupt, output int unsigned errs); + errs = 0; + + // init source matrix (row-major, m x n elements of eb bytes), unique fingerprint + for (int unsigned r = 0; r < m; r++) + for (int unsigned c = 0; c < n; c++) + for (int unsigned b = 0; b < eb; b++) + wr_mem(sb + (r*n + c)*eb + b, 8'((( (r*n+c)*eb + b )*7 + 3) & 8'hFF)); + // sentinel-fill the contiguous N x M dst + for (int unsigned k = 0; k < n*m*eb; k++) wr_mem(db + k, 8'hCC); + + // Drive a transpose request THROUGH idma_transpose_midend (it computes the + // swapped-stride program); the TB sets only the compute fields + addresses, + // NOT the strides — so the midend's expansion is what gets exercised. + tp_req = '0; + tp_req.burst_req.src_addr = sb; + tp_req.burst_req.dst_addr = db; + tp_req.burst_req.opt.src_protocol = idma_pkg::AXI; + tp_req.burst_req.opt.dst_protocol = idma_pkg::AXI; + tp_req.burst_req.opt.src.burst = axi_pkg::BURST_INCR; + tp_req.burst_req.opt.dst.burst = axi_pkg::BURST_INCR; + tp_req.burst_req.opt.beo.decouple_rw = 1'b1; + tp_req.burst_req.opt.beo.decouple_aw = 1'b1; + tp_req.burst_req.opt.last = 1'b1; + tp_req.burst_req.opt.compute.enable = 1'b1; + tp_req.burst_req.opt.compute.op = idma_pkg::COMPUTE_TRANSPOSE; + tp_req.burst_req.opt.compute.params.transpose.mode = 2'(eb == 4 ? 2 : eb == 2 ? 1 : 0); + tp_req.burst_req.opt.compute.params.transpose.tensor_m = 12'(m); + tp_req.burst_req.opt.compute.params.transpose.tensor_n = 12'(n); + + $display("[AG] case %0dx%0d EB=%0d (via transpose_midend, %0d elements)", m, n, eb, m*n); + tp_valid = 1'b1; + do @(posedge clk); while (!tp_ready); + tp_valid = 1'b0; + tp_req = '0; + + while (!(nd_rsp_valid && nd_rsp_ready)) @(posedge clk); + repeat (20) @(posedge clk); + + // negative control: flip one dst byte so the checker MUST report a mismatch + if (corrupt) wr_mem(db, ~rd_mem(db)); + + // check: out_T[c][r] == in[r][c], dst contiguous N x M (pitch M) + for (int unsigned c = 0; c < n; c++) + for (int unsigned r = 0; r < m; r++) + for (int unsigned b = 0; b < eb; b++) begin + automatic logic [7:0] got = rd_mem(db + (c*m + r)*eb + b); + automatic logic [7:0] exp = rd_mem(sb + (r*n + c)*eb + b); + if (got !== exp) begin + errs++; + if (errs <= 12) $display("[AG] MISMATCH out_T[%0d][%0d].b%0d=%02h exp %02h", c, r, b, got, exp); + end + end + endtask + + initial begin + automatic int unsigned total = 0; + automatic int unsigned ce; + tp_valid = 1'b0; nd_rsp_ready = 1'b1; tp_req = '0; + @(posedge rst_n); + repeat (5) @(posedge clk); + + for (int unsigned k = 0; k < NCases; k++) begin + if (Cases[k][2] > StrbWidth) continue; + run_case(Cases[k][0], Cases[k][1], Cases[k][2], 1'b0, ce); + if (ce == 0) $display("[AG] PASS: %0dx%0d EB=%0d", Cases[k][0], Cases[k][1], Cases[k][2]); + else $display("[AG] FAIL: %0dx%0d EB=%0d (%0d mismatches)", Cases[k][0], Cases[k][1], Cases[k][2], ce); + total += ce; + end + + // negative control: a corrupted dst MUST be caught, else the checker is vacuous + run_case(8, 8, 1, 1'b1, ce); + if (ce == 0) $fatal(1, "[AG] negative control FAILED: checker is vacuous"); + $display("[AG] negative control OK: corrupted dst caught (%0d mismatches)", ce); + + if (total == 0) $display("[AG] ALL PASS (%0d cases + neg-control, StrbWidth=%0d)", NCases, StrbWidth); + else $fatal(1, "[AG] FAIL: %0d total mismatches", total); + repeat (5) @(posedge clk); + $finish(); + end + + initial begin #100_000_000; $fatal(1, "[AG] timeout"); end + +endmodule diff --git a/test/tb_idma_addrgen_transpose_obi.sv b/test/tb_idma_addrgen_transpose_obi.sv new file mode 100644 index 00000000..f8b1687f --- /dev/null +++ b/test/tb_idma_addrgen_transpose_obi.sv @@ -0,0 +1,239 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +// Authors: +// - Daniel Keller + +// Address-gen transpose (no engine), OBI->OBI TCDM: rw_obi transposes M x N via swapped strides. + +`include "idma/typedef.svh" +`include "obi/typedef.svh" + +module tb_idma_addrgen_transpose_obi + import idma_pkg::*; +#( + parameter int unsigned DataWidth = 32, + parameter int unsigned AddrWidth = 32, + parameter int unsigned UserWidth = 1, + parameter int unsigned AxiIdWidth = 1, + parameter int unsigned TFLenWidth = 32, + parameter int unsigned BufferDepth = 3 +); + + localparam time TA = 1ns; + localparam time TT = 9ns; + localparam time TCK = 10ns; + + localparam int unsigned StrbWidth = DataWidth / 8; + localparam int unsigned AxIF = 8; + // base burst = 1 element (dim 0); two repetition dims (col, row) => NumDim=3 + localparam int unsigned NumDim = 3; + localparam logic [NumDim-1:0][31:0] RepWidths = '{default: 32'd16}; + + localparam int unsigned NCases = 12; + localparam int unsigned Cases [NCases][3] = '{ + '{ 4, 4, 1}, '{ 8, 8, 1}, '{ 8, 4, 1}, '{ 4, 8, 1}, + '{ 6, 5, 1}, '{ 5, 7, 2}, '{ 3, 9, 4}, '{16, 16, 1}, + '{ 8, 8, 2}, '{ 8, 8, 4}, '{10, 6, 2}, '{12, 8, 4} + }; + + // ── Types ── + typedef logic [AddrWidth-1:0] addr_t; + typedef logic [DataWidth-1:0] data_t; + typedef logic [StrbWidth-1:0] strb_t; + typedef logic [AxiIdWidth-1:0] id_t; + typedef logic [UserWidth-1:0] user_t; + typedef logic [TFLenWidth-1:0] tf_len_t; + typedef logic [31:0] reps_t; + + `OBI_TYPEDEF_MINIMAL_A_OPTIONAL(a_optional_t) + `OBI_TYPEDEF_MINIMAL_R_OPTIONAL(r_optional_t) + `OBI_TYPEDEF_TYPE_A_CHAN_T(obi_a_chan_t, addr_t, data_t, strb_t, id_t, a_optional_t) + `OBI_TYPEDEF_TYPE_R_CHAN_T(obi_r_chan_t, data_t, id_t, r_optional_t) + `OBI_TYPEDEF_REQ_T(obi_req_t, obi_a_chan_t) + `OBI_TYPEDEF_RSP_T(obi_rsp_t, obi_r_chan_t) + + function automatic obi_pkg::obi_cfg_t tb_obi_cfg(); + tb_obi_cfg = obi_pkg::obi_default_cfg(AddrWidth, DataWidth, AxiIdWidth, + obi_pkg::ObiMinimalOptionalConfig); + tb_obi_cfg.UseRReady = 1'b1; + endfunction + localparam obi_pkg::obi_cfg_t ObiCfg = tb_obi_cfg(); + + `IDMA_TYPEDEF_FULL_REQ_T(idma_req_t, id_t, addr_t, tf_len_t) + `IDMA_TYPEDEF_FULL_RSP_T(idma_rsp_t, addr_t) + `IDMA_TYPEDEF_FULL_ND_REQ_T(idma_nd_req_t, idma_req_t, reps_t, addr_t) + + typedef struct packed { obi_a_chan_t a_chan; } obi_read_meta_channel_t; + typedef struct packed { obi_read_meta_channel_t obi; } read_meta_channel_t; + typedef struct packed { obi_a_chan_t a_chan; } obi_write_meta_channel_t; + typedef struct packed { obi_write_meta_channel_t obi; } write_meta_channel_t; + + // ── Signals ── + logic clk, rst_n; + idma_req_t idma_req; logic req_valid, req_ready; + idma_rsp_t idma_rsp; logic rsp_valid, rsp_ready; + idma_eh_req_t idma_eh_req; logic eh_req_valid, eh_req_ready; + idma_nd_req_t tp_req; logic tp_valid, tp_ready; // TB -> transpose midend + idma_nd_req_t nd_req; logic nd_req_valid, nd_req_ready; // transpose midend -> nd midend + idma_rsp_t nd_rsp; logic nd_rsp_valid, nd_rsp_ready; + obi_req_t obi_read_req, obi_write_req; + obi_rsp_t obi_read_rsp, obi_write_rsp; + idma_busy_t busy; logic nd_busy; + + assign idma_eh_req = '0; + assign eh_req_valid = 1'b0; + + clk_rst_gen #(.ClkPeriod(TCK), .RstClkCycles(1)) i_clk_rst_gen (.clk_o(clk), .rst_no(rst_n)); + + // ── Native OBI memories: read (src) + write (dst) ── + obi_sim_mem #( + .ObiCfg(ObiCfg), .obi_req_t(obi_req_t), .obi_rsp_t(obi_rsp_t), .obi_r_chan_t(obi_r_chan_t), + .WarnUninitialized(1'b0), .ClearErrOnAccess(1'b1), .ApplDelay(TA), .AcqDelay(TT) + ) i_obi_read_sim_mem ( + .clk_i(clk), .rst_ni(rst_n), .obi_req_i(obi_read_req), .obi_rsp_o(obi_read_rsp), + .mon_valid_o(), .mon_we_o(), .mon_addr_o(), .mon_wdata_o(), .mon_be_o(), .mon_id_o() + ); + obi_sim_mem #( + .ObiCfg(ObiCfg), .obi_req_t(obi_req_t), .obi_rsp_t(obi_rsp_t), .obi_r_chan_t(obi_r_chan_t), + .WarnUninitialized(1'b0), .ClearErrOnAccess(1'b1), .ApplDelay(TA), .AcqDelay(TT) + ) i_obi_write_sim_mem ( + .clk_i(clk), .rst_ni(rst_n), .obi_req_i(obi_write_req), .obi_rsp_o(obi_write_rsp), + .mon_valid_o(), .mon_we_o(), .mon_addr_o(), .mon_wdata_o(), .mon_be_o(), .mon_id_o() + ); + + // ── Transpose midend (RTL UNDER TEST): expands the transpose request into the + // swapped-stride ND program; the nd_midend + backend just execute it ── + idma_transpose_midend #( + .NumDim(NumDim), .AddrGenTranspose(1'b1), .StrbWidth(StrbWidth), + .addr_t(addr_t), .idma_nd_req_t(idma_nd_req_t) + ) i_xpose_midend ( + .nd_req_i(tp_req), .valid_i(tp_valid), .ready_o(tp_ready), + .nd_req_o(nd_req), .valid_o(nd_req_valid), .ready_i(nd_req_ready) + ); + + // ── ND midend ── + idma_nd_midend #( + .NumDim(NumDim), .addr_t(addr_t), .idma_req_t(idma_req_t), + .idma_rsp_t(idma_rsp_t), .idma_nd_req_t(idma_nd_req_t), .RepWidths(RepWidths) + ) i_nd_midend ( + .clk_i(clk), .rst_ni(rst_n), + .nd_req_i(nd_req), .nd_req_valid_i(nd_req_valid), .nd_req_ready_o(nd_req_ready), + .nd_rsp_o(nd_rsp), .nd_rsp_valid_o(nd_rsp_valid), .nd_rsp_ready_i(nd_rsp_ready), + .burst_req_o(idma_req), .burst_req_valid_o(req_valid), .burst_req_ready_i(req_ready), + .burst_rsp_i(idma_rsp), .burst_rsp_valid_i(rsp_valid), .burst_rsp_ready_o(rsp_ready), + .busy_o(nd_busy) + ); + + // ── Backend (rw_obi): OBI read + OBI write, plain copy — NO compute ── + idma_backend_rw_obi #( + .CombinedShifter(1'b0), .DataWidth(DataWidth), .AddrWidth(AddrWidth), .AxiIdWidth(AxiIdWidth), + .UserWidth(UserWidth), .TFLenWidth(TFLenWidth), .MaskInvalidData(1'b1), .BufferDepth(BufferDepth), + .RAWCouplingAvail(1'b0), .HardwareLegalizer(1'b1), .RejectZeroTransfers(1'b1), + .ErrorCap(idma_pkg::NO_ERROR_HANDLING), .PrintFifoInfo(1'b0), .NumAxInFlight(AxIF), .MemSysDepth(0), + .idma_req_t(idma_req_t), .idma_rsp_t(idma_rsp_t), .idma_eh_req_t(idma_eh_req_t), + .idma_busy_t(idma_busy_t), .obi_req_t(obi_req_t), .obi_rsp_t(obi_rsp_t), + .write_meta_channel_t(write_meta_channel_t), .read_meta_channel_t(read_meta_channel_t) + ) i_idma_backend ( + .clk_i(clk), .rst_ni(rst_n), .testmode_i(1'b0), + .idma_req_i(idma_req), .req_valid_i(req_valid), .req_ready_o(req_ready), + .idma_rsp_o(idma_rsp), .rsp_valid_o(rsp_valid), .rsp_ready_i(rsp_ready), + .idma_eh_req_i(idma_eh_req), .eh_req_valid_i(eh_req_valid), .eh_req_ready_o(eh_req_ready), + .obi_read_req_o(obi_read_req), .obi_read_rsp_i(obi_read_rsp), + .obi_write_req_o(obi_write_req), .obi_write_rsp_i(obi_write_rsp), .busy_o(busy) + ); + + // ── Stimulus + check via sim-memory backdoor (src in read mem, dst in write mem) ── + addr_t sb = 'h0000_1000; + addr_t db = 'h0000_8000; + + task automatic wr_src(input addr_t a, input logic [7:0] d); i_obi_read_sim_mem.mem[a] = d; endtask + function automatic logic [7:0] rd_src(input addr_t a); + return i_obi_read_sim_mem.mem.exists(a) ? i_obi_read_sim_mem.mem[a] : 8'hxx; + endfunction + task automatic wr_dst(input addr_t a, input logic [7:0] d); i_obi_write_sim_mem.mem[a] = d; endtask + function automatic logic [7:0] rd_dst(input addr_t a); + return i_obi_write_sim_mem.mem.exists(a) ? i_obi_write_sim_mem.mem[a] : 8'hxx; + endfunction + + task automatic run_case(input int unsigned m, input int unsigned n, input int unsigned eb, + input bit corrupt, output int unsigned errs); + errs = 0; + for (int unsigned r = 0; r < m; r++) + for (int unsigned c = 0; c < n; c++) + for (int unsigned b = 0; b < eb; b++) + wr_src(sb + (r*n + c)*eb + b, 8'((( (r*n+c)*eb + b )*7 + 3) & 8'hFF)); + for (int unsigned k = 0; k < n*m*eb; k++) wr_dst(db + k, 8'hCC); + + // Drive a transpose request THROUGH idma_transpose_midend (it computes the + // swapped-stride program); set only compute fields + addresses, not strides. + tp_req = '0; + tp_req.burst_req.src_addr = sb; + tp_req.burst_req.dst_addr = db; + tp_req.burst_req.opt.src_protocol = idma_pkg::OBI; + tp_req.burst_req.opt.dst_protocol = idma_pkg::OBI; + tp_req.burst_req.opt.src.burst = axi_pkg::BURST_INCR; + tp_req.burst_req.opt.dst.burst = axi_pkg::BURST_INCR; + tp_req.burst_req.opt.beo.decouple_rw = 1'b1; + tp_req.burst_req.opt.beo.decouple_aw = 1'b1; + tp_req.burst_req.opt.last = 1'b1; + tp_req.burst_req.opt.compute.enable = 1'b1; + tp_req.burst_req.opt.compute.op = idma_pkg::COMPUTE_TRANSPOSE; + tp_req.burst_req.opt.compute.params.transpose.mode = 2'(eb == 4 ? 2 : eb == 2 ? 1 : 0); + tp_req.burst_req.opt.compute.params.transpose.tensor_m = 12'(m); + tp_req.burst_req.opt.compute.params.transpose.tensor_n = 12'(n); + + $display("[AGO] case %0dx%0d EB=%0d (via transpose_midend, %0d OBI elements)", m, n, eb, m*n); + tp_valid = 1'b1; + do @(posedge clk); while (!tp_ready); + tp_valid = 1'b0; + tp_req = '0; + + while (!(nd_rsp_valid && nd_rsp_ready)) @(posedge clk); + repeat (20) @(posedge clk); + + // negative control: flip one dst byte so the checker MUST report a mismatch + if (corrupt) wr_dst(db, ~rd_dst(db)); + + for (int unsigned c = 0; c < n; c++) + for (int unsigned r = 0; r < m; r++) + for (int unsigned b = 0; b < eb; b++) begin + automatic logic [7:0] got = rd_dst(db + (c*m + r)*eb + b); + automatic logic [7:0] exp = rd_src(sb + (r*n + c)*eb + b); + if (got !== exp) begin + errs++; + if (errs <= 12) $display("[AGO] MISMATCH out_T[%0d][%0d].b%0d=%02h exp %02h", c, r, b, got, exp); + end + end + endtask + + initial begin + automatic int unsigned total = 0; + automatic int unsigned ce; + tp_valid = 1'b0; nd_rsp_ready = 1'b1; tp_req = '0; + @(posedge rst_n); + repeat (5) @(posedge clk); + + for (int unsigned k = 0; k < NCases; k++) begin + if (Cases[k][2] > StrbWidth) continue; + run_case(Cases[k][0], Cases[k][1], Cases[k][2], 1'b0, ce); + if (ce == 0) $display("[AGO] PASS: %0dx%0d EB=%0d", Cases[k][0], Cases[k][1], Cases[k][2]); + else $display("[AGO] FAIL: %0dx%0d EB=%0d (%0d mismatches)", Cases[k][0], Cases[k][1], Cases[k][2], ce); + total += ce; + end + + // negative control: a corrupted dst MUST be caught, else the checker is vacuous + run_case(8, 8, 1, 1'b1, ce); + if (ce == 0) $fatal(1, "[AGO] negative control FAILED: checker is vacuous"); + $display("[AGO] negative control OK: corrupted dst caught (%0d mismatches)", ce); + + if (total == 0) $display("[AGO] ALL PASS (%0d cases + neg-control, StrbWidth=%0d)", NCases, StrbWidth); + else $fatal(1, "[AGO] FAIL: %0d total mismatches", total); + repeat (5) @(posedge clk); + $finish(); + end + + initial begin #100_000_000; $fatal(1, "[AGO] timeout"); end + +endmodule