1616
1717module tb_idma_reg_frontend import idma_pkg :: * ; # (
1818 // number of streams the elaborated DUT exposes (checked at instantiation)
19- parameter int unsigned NumStreams = 32'd1
19+ parameter int unsigned NumStreams = 32'd1 ,
20+ // number of config-bus ports (arbitrated by the reg frontend's rr_arb_tree)
21+ parameter int unsigned NumRegs = 32'd1
2022);
2123
2224 // --------------------------------------------------------------------------
@@ -27,7 +29,6 @@ module tb_idma_reg_frontend import idma_pkg::*; #(
2729 localparam int unsigned CfgDataWidth = 32'd32 ;
2830 localparam int unsigned CfgStrbWidth = CfgDataWidth / 32'd8 ;
2931 localparam int unsigned IdCounterWidth = 32'd32 ;
30- localparam int unsigned NumRegs = 32'd1 ;
3132 // idma data-path (reg32_3d: 32-bit data, 3 ND dims)
3233 localparam int unsigned AddrWidth = 32'd32 ;
3334 localparam int unsigned DataWidth = 32'd32 ;
@@ -262,17 +263,25 @@ module tb_idma_reg_frontend import idma_pkg::*; #(
262263 else if (issue) launch_accept_count <= launch_accept_count + 1 ;
263264 end
264265
266+ // Test 5 (multi-port arbitration) scoreboard state. Each config port programs a
267+ // unique src_addr that encodes its target stream, so the arbiter's presented
268+ // winner (dma_req_o) can be mapped back to a stream and compared to stream_idx_o.
269+ logic [31 : 0 ] sb_addr_stream0; // src_addr programmed for stream 0's port
270+ logic [31 : 0 ] sb_addr_stream1; // src_addr programmed for stream 1's port
271+ int unsigned sb_mismatch; // times stream_idx != the winner's stream
272+ initial sb_mismatch = 0 ;
273+
265274 // --------------------------------------------------------------------------
266275 // APB driver
267276 // --------------------------------------------------------------------------
268- task automatic apb_idle ();
269- apb_req[0 ].psel = 1'b0 ;
270- apb_req[0 ].penable = 1'b0 ;
271- apb_req[0 ].pwrite = 1'b0 ;
272- apb_req[0 ].paddr = '0 ;
273- apb_req[0 ].pwdata = '0 ;
274- apb_req[0 ].pstrb = '0 ;
275- apb_req[0 ].pprot = '0 ;
277+ task automatic apb_idle (input int unsigned port = 0 );
278+ apb_req[port ].psel = 1'b0 ;
279+ apb_req[port ].penable = 1'b0 ;
280+ apb_req[port ].pwrite = 1'b0 ;
281+ apb_req[port ].paddr = '0 ;
282+ apb_req[port ].pwdata = '0 ;
283+ apb_req[port ].pstrb = '0 ;
284+ apb_req[port ].pprot = '0 ;
276285 endtask
277286
278287 // Strict APB4 master. `access_cycles` (out) counts the ACCESS-phase config cycles
@@ -283,73 +292,77 @@ module tb_idma_reg_frontend import idma_pkg::*; #(
283292 input logic [31 : 0 ] wdata,
284293 output logic [31 : 0 ] rdata,
285294 output int unsigned access_cycles,
286- input bit is_next_id = 1'b0 );
295+ input bit is_next_id = 1'b0 ,
296+ input int unsigned port = 0 );
287297 access_cycles = 0 ;
288298 // SETUP phase: psel high, penable low, for one cycle
289299 @ (negedge clk);
290- apb_req[0 ].psel = 1'b1 ;
291- apb_req[0 ].penable = 1'b0 ;
292- apb_req[0 ].pwrite = write;
293- apb_req[0 ].paddr = addr;
294- apb_req[0 ].pwdata = wdata;
295- apb_req[0 ].pstrb = write ? '1 : '0 ;
296- apb_req[0 ].pprot = '0 ;
300+ apb_req[port ].psel = 1'b1 ;
301+ apb_req[port ].penable = 1'b0 ;
302+ apb_req[port ].pwrite = write;
303+ apb_req[port ].paddr = addr;
304+ apb_req[port ].pwdata = wdata;
305+ apb_req[port ].pstrb = write ? '1 : '0 ;
306+ apb_req[port ].pprot = '0 ;
297307 if (is_next_id) nxt_read_active = 1'b1 ;
298308
299309 // ACCESS phase: raise penable, then wait for pready at the posedge. Count the
300310 // ACCESS-phase cycles until pready — for a next_id read this is the read latency
301311 // the non-blocking contract bounds (must not depend on req_ready_i).
302312 @ (negedge clk);
303- apb_req[0 ].penable = 1'b1 ;
313+ apb_req[port ].penable = 1'b1 ;
304314 forever begin
305315 @ (posedge clk);
306316 access_cycles = access_cycles + 1 ;
307- if (apb_rsp[0 ].pready) begin
308- rdata = apb_rsp[0 ].prdata; // sample in the completing cycle
317+ if (apb_rsp[port ].pready) begin
318+ rdata = apb_rsp[port ].prdata; // sample in the completing cycle
309319 break ;
310320 end
311321 end
312322 // retire the transaction: return to IDLE immediately (no extra held posedge)
313- apb_idle ();
323+ apb_idle (port );
314324 if (is_next_id) nxt_read_active = 1'b0 ;
315325 // mandatory idle cycle so the FSM's is_active fully drops with psel low
316326 @ (negedge clk);
317327 endtask
318328
319- task automatic apb_write (input logic [31 : 0 ] addr, input logic [31 : 0 ] data);
329+ task automatic apb_write (input logic [31 : 0 ] addr, input logic [31 : 0 ] data,
330+ input int unsigned port = 0 );
320331 logic [31 : 0 ] dummy;
321332 int unsigned cyc;
322- apb_xact (1'b1 , addr, data, dummy, cyc);
333+ apb_xact (1'b1 , addr, data, dummy, cyc, 1'b0 , port );
323334 endtask
324335
325336 // APB4 read that respects pready; `cyc` returns the ACCESS-phase cycle count.
326337 task automatic apb_read (input logic [31 : 0 ] addr,
327338 output logic [31 : 0 ] data,
328339 output int unsigned cyc,
329- input bit is_next_id = 1'b0 );
330- apb_xact (1'b0 , addr, 32'h0 , data, cyc, is_next_id);
340+ input bit is_next_id = 1'b0 ,
341+ input int unsigned port = 0 );
342+ apb_xact (1'b0 , addr, 32'h0 , data, cyc, is_next_id, port);
331343 endtask
332344
333345 // --------------------------------------------------------------------------
334346 // High-level helpers
335347 // --------------------------------------------------------------------------
336348 task automatic program_transfer (input logic [31 : 0 ] src,
337349 input logic [31 : 0 ] dst,
338- input logic [31 : 0 ] len);
350+ input logic [31 : 0 ] len,
351+ input int unsigned port = 0 );
339352 // conf: plain 1D incremental copy, ND disabled
340- apb_write (REG_CONF , 32'h0 );
341- apb_write (REG_SRC_ADDR , src);
342- apb_write (REG_DST_ADDR , dst);
343- apb_write (REG_LENGTH , len);
353+ apb_write (REG_CONF , 32'h0 , port );
354+ apb_write (REG_SRC_ADDR , src, port );
355+ apb_write (REG_DST_ADDR , dst, port );
356+ apb_write (REG_LENGTH , len, port );
344357 endtask
345358
346359 // launch: read next_id (the transfer trigger, non-blocking); returns id and the
347360 // ACCESS-phase latency in `cyc`. Snapshots the accept count before the read so
348361 // an accept coinciding with the (non-blocking) read is still observed.
349362 task automatic launch (output logic [31 : 0 ] id, output int unsigned cyc,
350- input int unsigned s = 0 );
363+ input int unsigned s = 0 , input int unsigned port = 0 );
351364 launch_acc_base = launch_accept_count;
352- apb_read (reg_next_id (s), id, cyc, .is_next_id (1'b1 ));
365+ apb_read (reg_next_id (s), id, cyc, .is_next_id (1'b1 ), . port (port) );
353366 endtask
354367
355368 // wait until the launch read since the last launch() has been accepted (the
@@ -411,7 +424,7 @@ module tb_idma_reg_frontend import idma_pkg::*; #(
411424 initial begin : test
412425 errors = 0 ;
413426 checks = 0 ;
414- apb_idle ();
427+ for ( int unsigned p = 0 ; p < NumRegs; p ++ ) apb_idle (p );
415428 req_ready = 1'b1 ;
416429
417430 // reset
@@ -604,6 +617,90 @@ module tb_idma_reg_frontend import idma_pkg::*; #(
604617 $display (" [ ok ] Test4 done_id advanced in order to %0d " , ids[3 ]);
605618 end
606619
620+ // ------------------------------------------------------------------
621+ // Test 5 — Concurrent multi-port launch: stream_idx must ride the
622+ // arbitration (only meaningful when NumRegs>1 and NumStreams>1).
623+ // Two config ports launch on *different* streams in the same window; on
624+ // every grant stream_idx must match the ARBITRATED port's stream, not the
625+ // last-pending port. This FAILS on the pre-fix RTL and PASSES after it.
626+ // ------------------------------------------------------------------
627+ if (NumRegs > 1 && NumStreams > 1 ) begin
628+ logic [31 : 0 ] id_p0, id_p1;
629+ int unsigned cyc0, cyc1;
630+ $display (" \n --- Test 5: concurrent multi-port arbitration (stream_idx) ---" );
631+ backend_auto_retire = 1'b0 ;
632+ captured_q.delete ();
633+ req_ready = 1'b0 ;
634+ // port 0 -> stream 0, port 1 -> stream 1, each with a unique src_addr
635+ sb_addr_stream0 = 32'hAAAA_0000 ;
636+ sb_addr_stream1 = 32'hBBBB_0000 ;
637+ program_transfer (sb_addr_stream0, 32'hCCCC_0000 , 32'h0000_0040 , 0 );
638+ program_transfer (sb_addr_stream1, 32'hDDDD_0000 , 32'h0000_0080 , 1 );
639+ sb_mismatch = 0 ;
640+ // launch both ports concurrently on different streams while the grant is held off,
641+ // so both launch_pending latches are set at once (the arbiter must pick one).
642+ fork
643+ launch (id_p0, cyc0, 0 , 0 ); // port 0, stream 0
644+ launch (id_p1, cyc1, 1 , 1 ); // port 1, stream 1
645+ join
646+ // Both launches are now pending with req_ready still low. rr_arb_tree (AxiVldRdy=1)
647+ // *presents* its chosen winner on dma_req_o / idx_o even while the grant is withheld,
648+ // so stream_idx_o must equal the winner's stream. On the pre-fix RTL stream_idx_o is
649+ // the last-pending port's stream (held_stream[NumRegs-1]) regardless of the winner,
650+ // so it disagrees with dma_req_o whenever the winner is not the last port. Check the
651+ // presented (winner, stream_idx) pair across the whole held window.
652+ begin
653+ int unsigned held_checks;
654+ held_checks = 0 ;
655+ repeat (12 ) begin
656+ @ (negedge clk);
657+ # (TCK / 10 ); // let combinational DUT outputs settle
658+ if (req_valid && ! req_ready) begin
659+ automatic int unsigned won_stream = 32'hFFFF_FFFF ;
660+ if (dma_req.burst_req.src_addr == sb_addr_stream0) won_stream = 0 ;
661+ else if (dma_req.burst_req.src_addr == sb_addr_stream1) won_stream = 1 ;
662+ if (won_stream != 32'hFFFF_FFFF ) begin
663+ held_checks++ ;
664+ if (stream_idx != won_stream[$bits (stream_idx)- 1 : 0 ]) begin
665+ sb_mismatch++ ;
666+ $display (" [Test5] MISMATCH: dma_req_o=port for stream %0d but stream_idx=%0d " ,
667+ won_stream, stream_idx);
668+ end
669+ end
670+ end
671+ end
672+ check_eq (held_checks > 0 , 1'b1 , " Test5 winner presented while grant withheld" );
673+ check_eq (sb_mismatch, 32'd0 , " Test5 stream_idx matches arbitrated port (winner)" );
674+ end
675+ // now let both launches drain and confirm both transfers are captured correctly
676+ req_ready = 1'b1 ;
677+ backend_auto_retire = 1'b1 ;
678+ begin
679+ int unsigned tries;
680+ tries = 0 ;
681+ while (captured_q.size () < 2 ) begin
682+ @ (posedge clk);
683+ tries++ ;
684+ if (tries > 1000 ) $fatal (1 , " Test5: both launches never drained (got %0d )" ,
685+ captured_q.size ());
686+ end
687+ end
688+ check_eq (captured_q.size (), 32'd2 , " Test5 both launches captured" );
689+ begin
690+ logic saw_a, saw_b;
691+ saw_a = 1'b0 ; saw_b = 1'b0 ;
692+ foreach (captured_q[k]) begin
693+ if (captured_q[k].burst_req.src_addr == sb_addr_stream0) saw_a = 1'b1 ;
694+ if (captured_q[k].burst_req.src_addr == sb_addr_stream1) saw_b = 1'b1 ;
695+ end
696+ check_eq (saw_a, 1'b1 , " Test5 port0/stream0 transfer captured" );
697+ check_eq (saw_b, 1'b1 , " Test5 port1/stream1 transfer captured" );
698+ end
699+ $display (" [ ok ] Test5 concurrent arbitration: %0d mismatches" , sb_mismatch);
700+ end else begin
701+ $display (" \n --- Test 5: skipped (needs NumRegs>1 and NumStreams>1) ---" );
702+ end
703+
607704 // ------------------------------------------------------------------
608705 // Summary
609706 // ------------------------------------------------------------------
0 commit comments