// retroDE_ps2 — tb_iop_autonomous_two_xfers // // First autonomous multi-transaction flow. Extends tb_iop_self_driven // one architectural level higher: the RAM-resident control program // now contains a BNE back-edge that loops the DMA+IRQ+ack body until // DMAC.DONE_COUNT reaches 2. A single `go_i` pulse produces TWO full // DMA transactions, two interrupt assertions, and two acks — all // fabric-driven, no TB steering between transactions. // // Chain identical to tb_iop_self_driven (same map, DMAC, INTC, exec // stub, ep_ready-on-the-floor endpoint). The only difference is the // script: a BNE at the loop tail wires two transactions together. // // Script (9 ops, laid out in IOP RAM at SCRIPT_BASE): // pc=0 WRITE INTC_MASK = 1 (enable bit 0) // pc=1 WRITE DMAC_MADR = payload (loop entry) // pc=2 WRITE DMAC_BCR = 4 // pc=3 WRITE DMAC_CHCR = 1 (start transfer) // pc=4 WAIT_IRQ (block until completion) // pc=5 WRITE INTC_STAT = 1 (W1C ack through real map) // pc=6 READ DMAC_DONE_COUNT (latches into last_read_data) // pc=7 BNE target=1, expected=2 (branch back if DONE_COUNT != 2) // pc=8 HALT // // Acceptance: // - Exactly one go_i pulse from the TB; no orchestration after. // - Two DMA_DONE events, two INTC assertions, two INTC acks. // - Exactly two WAIT_IRQ exits and exactly one BNE taken (the first // time, when DONE_COUNT == 1). On the second iteration DONE_COUNT // reads 2, so BNE falls through to HALT. // - halt_o asserts exactly once. // - Full causal chain reconstructible from the trace. `timescale 1ns/1ps module tb_iop_autonomous_two_xfers; localparam int IOP_RAM_BYTES = 4 * 1024; localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES); localparam logic [31:0] SCRIPT_BASE = 32'h0000_0400; localparam logic [31:0] PAYLOAD_BASE = 32'h0000_0200; localparam logic [31:0] DMAC_MADR_ADDR = 32'h1F80_1520; localparam logic [31:0] DMAC_BCR_ADDR = 32'h1F80_1524; localparam logic [31:0] DMAC_CHCR_ADDR = 32'h1F80_1528; localparam logic [31:0] DMAC_DONECOUNT_ADDR = 32'h1F80_152C; localparam logic [31:0] INTC_STAT_ADDR = 32'h1F80_1070; localparam logic [31:0] INTC_MASK_ADDR = 32'h1F80_1074; localparam logic [31:0] OP_HALT = 32'h0000_0000; localparam logic [31:0] OP_WRITE = 32'h0000_0001; localparam logic [31:0] OP_READ = 32'h0000_0002; localparam logic [31:0] OP_WAIT_IRQ = 32'h0000_0003; localparam logic [31:0] OP_BNE = 32'h0000_0004; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // ------------------------------------------------------------------ // IOP exec stub // ------------------------------------------------------------------ logic exec_go; logic exec_halt; logic [7:0] exec_pc; logic map_cpu_rd_en; logic [31:0] map_cpu_rd_addr; logic [31:0] map_cpu_rd_data; logic map_cpu_rd_valid; logic map_cpu_wr_en; logic [31:0] map_cpu_wr_addr; logic [31:0] map_cpu_wr_data; logic [3:0] map_cpu_wr_be; logic iop_cpu_irq; logic exec_ev_valid; trace_pkg::subsys_e exec_ev_subsys; trace_pkg::event_e exec_ev_event; logic [63:0] exec_ev_arg0, exec_ev_arg1, exec_ev_arg2, exec_ev_arg3; logic [31:0] exec_ev_flags; iop_exec_stub #(.SCRIPT_BASE(SCRIPT_BASE)) u_exec ( .clk(clk), .rst_n(rst_n), .go_i(exec_go), .map_rd_en(map_cpu_rd_en), .map_rd_addr(map_cpu_rd_addr), .map_rd_data(map_cpu_rd_data), .map_rd_valid(map_cpu_rd_valid), .map_wr_en(map_cpu_wr_en), .map_wr_addr(map_cpu_wr_addr), .map_wr_data(map_cpu_wr_data), .map_wr_be(map_cpu_wr_be), .cpu_irq(iop_cpu_irq), .halt_o(exec_halt), .pc_o(exec_pc), .ev_valid(exec_ev_valid), .ev_subsys(exec_ev_subsys), .ev_event(exec_ev_event), .ev_arg0(exec_ev_arg0), .ev_arg1(exec_ev_arg1), .ev_arg2(exec_ev_arg2), .ev_arg3(exec_ev_arg3), .ev_flags(exec_ev_flags) ); // ------------------------------------------------------------------ // IOP map + IOP RAM (TB drives bridge_wr_* for preload) // ------------------------------------------------------------------ logic iop_ram_rd_en; logic [20:0] iop_ram_rd_addr; logic [31:0] iop_ram_rd_data; logic iop_ram_rd_valid; logic iop_ram_wr_en; logic [20:0] iop_ram_wr_addr; logic [31:0] iop_ram_wr_data; logic [3:0] iop_ram_wr_be; logic [7:0] iop_ram_master_id; logic map_dmac_rd_en; logic [3:0] map_dmac_rd_addr; logic [31:0] map_dmac_rd_data; logic map_dmac_rd_valid; logic map_dmac_wr_en; logic [3:0] map_dmac_wr_addr; logic [31:0] map_dmac_wr_data; logic map_intc_rd_en; logic [7:0] map_intc_rd_addr; logic [31:0] map_intc_rd_data; logic map_intc_rd_valid; logic map_intc_wr_en; logic [7:0] map_intc_wr_addr; logic [31:0] map_intc_wr_data; logic dma_rd_en; logic [31:0] dma_rd_addr; logic [7:0] dma_master_id; logic [31:0] dma_rd_data; logic dma_rd_valid; logic tb_br_wr_en; logic [31:0] tb_br_wr_addr; logic [31:0] tb_br_wr_data; logic [3:0] tb_br_wr_be; logic iop_map_ev_valid; trace_pkg::subsys_e iop_map_ev_subsys; trace_pkg::event_e iop_map_ev_event; logic [63:0] iop_map_ev_arg0, iop_map_ev_arg1, iop_map_ev_arg2, iop_map_ev_arg3; logic [31:0] iop_map_ev_flags; iop_memory_map_stub u_iop_map ( .clk(clk), .rst_n(rst_n), .iop_rd_en(map_cpu_rd_en), .iop_rd_addr(map_cpu_rd_addr), .iop_rd_data(map_cpu_rd_data), .iop_rd_valid(map_cpu_rd_valid), .iop_wr_en(map_cpu_wr_en), .iop_wr_addr(map_cpu_wr_addr), .iop_wr_data(map_cpu_wr_data), .iop_wr_be(map_cpu_wr_be), .master_id(8'd2), .bridge_wr_en(tb_br_wr_en), .bridge_wr_addr(tb_br_wr_addr), .bridge_wr_data(tb_br_wr_data), .bridge_wr_be(tb_br_wr_be), .bridge_master_id(8'd0), .dma_rd_en(dma_rd_en), .dma_rd_addr(dma_rd_addr), .dma_master_id(dma_master_id), .dma_rd_data(dma_rd_data), .dma_rd_valid(dma_rd_valid), .sif_rd_en(), .sif_rd_addr(), .sif_rd_data(32'd0), .sif_rd_valid(1'b0), .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(), .iop_dmac_rd_en(map_dmac_rd_en), .iop_dmac_rd_addr(map_dmac_rd_addr), .iop_dmac_rd_data(map_dmac_rd_data), .iop_dmac_rd_valid(map_dmac_rd_valid), .iop_dmac_wr_en(map_dmac_wr_en), .iop_dmac_wr_addr(map_dmac_wr_addr), .iop_dmac_wr_data(map_dmac_wr_data), .iop_intc_rd_en(map_intc_rd_en), .iop_intc_rd_addr(map_intc_rd_addr), .iop_intc_rd_data(map_intc_rd_data), .iop_intc_rd_valid(map_intc_rd_valid), .iop_intc_wr_en(map_intc_wr_en), .iop_intc_wr_addr(map_intc_wr_addr), .iop_intc_wr_data(map_intc_wr_data), .input_p1(32'd0), .input_p2(32'd0), // BIOS ROM port — unused by this TB .bios_rd_en(), .bios_rd_addr(), .bios_rd_data(32'd0), .bios_rd_valid(1'b0), .ram_rd_en(iop_ram_rd_en), .ram_rd_addr(iop_ram_rd_addr), .ram_rd_data(iop_ram_rd_data), .ram_rd_valid(iop_ram_rd_valid), .ram_wr_en(iop_ram_wr_en), .ram_wr_addr(iop_ram_wr_addr), .ram_wr_data(iop_ram_wr_data), .ram_wr_be(iop_ram_wr_be), .ram_master_id(iop_ram_master_id), .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys), .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags) ); iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_iop_ram ( .clk(clk), .rst_n(rst_n), .rd_en(iop_ram_rd_en), .rd_addr(iop_ram_rd_addr[IOP_RAM_ADDR_W-1:0]), .rd_data(iop_ram_rd_data), .rd_valid(iop_ram_rd_valid), .wr_en(iop_ram_wr_en), .wr_addr(iop_ram_wr_addr[IOP_RAM_ADDR_W-1:0]), .wr_data(iop_ram_wr_data), .wr_be(iop_ram_wr_be), .master_id(iop_ram_master_id), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // ------------------------------------------------------------------ // IOP DMAC ch9 — endpoint drained (no stall in this TB) // ------------------------------------------------------------------ logic ep_valid; logic [31:0] ep_data; logic ep_last; logic ep_ready; assign ep_ready = 1'b1; logic dmac_irq_completion; logic dmac_busy; logic [31:0] dmac_done_count; logic dmac_ev_valid; trace_pkg::subsys_e dmac_ev_subsys; trace_pkg::event_e dmac_ev_event; logic [63:0] dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3; logic [31:0] dmac_ev_flags; iop_dmac_reg_stub u_dmac ( .clk(clk), .rst_n(rst_n), .reg_wr_en(map_dmac_wr_en), .reg_rd_en(map_dmac_rd_en), .reg_offset(map_dmac_wr_en ? map_dmac_wr_addr : map_dmac_rd_addr), .reg_wr_data(map_dmac_wr_data), .reg_rd_data(map_dmac_rd_data), .reg_rd_valid(map_dmac_rd_valid), .mem_rd_en(dma_rd_en), .mem_rd_addr(dma_rd_addr), .mem_master_id(dma_master_id), .mem_rd_data(dma_rd_data), .mem_rd_valid(dma_rd_valid), .ep_valid(ep_valid), .ep_data(ep_data), .ep_last(ep_last), .ep_ready(ep_ready), .irq_completion_o(dmac_irq_completion), .busy_o(dmac_busy), .done_count_o(dmac_done_count), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags) ); // ------------------------------------------------------------------ // IOP INTC // ------------------------------------------------------------------ logic [15:0] iop_irq_src; assign iop_irq_src = {15'd0, dmac_irq_completion}; logic intc_ev_valid; trace_pkg::subsys_e intc_ev_subsys; trace_pkg::event_e intc_ev_event; logic [63:0] intc_ev_arg0, intc_ev_arg1, intc_ev_arg2, intc_ev_arg3; logic [31:0] intc_ev_flags; intc_stub #( .INTC_STAT_OFFSET(8'h70), .INTC_MASK_OFFSET(8'h74) ) u_intc ( .clk(clk), .rst_n(rst_n), .reg_wr_en(map_intc_wr_en), .reg_rd_en(map_intc_rd_en), .reg_addr(map_intc_wr_en ? map_intc_wr_addr : map_intc_rd_addr), .reg_wr_data(map_intc_wr_data), .reg_rd_data(map_intc_rd_data), .reg_rd_valid(map_intc_rd_valid), .irq_src(iop_irq_src), .cpu_irq(iop_cpu_irq), .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys), .ev_event(intc_ev_event), .ev_arg0(intc_ev_arg0), .ev_arg1(intc_ev_arg1), .ev_arg2(intc_ev_arg2), .ev_arg3(intc_ev_arg3), .ev_flags(intc_ev_flags) ); // ------------------------------------------------------------------ // Trace sinks // ------------------------------------------------------------------ trace_sink_stub #(.FILENAME("iop_two_xfers_exec.trace"), .SINK_LABEL("iop_exec")) u_trace_exec (.clk(clk), .rst_n(rst_n), .ev_valid(exec_ev_valid), .ev_subsys(exec_ev_subsys), .ev_event(exec_ev_event), .ev_arg0(exec_ev_arg0), .ev_arg1(exec_ev_arg1), .ev_arg2(exec_ev_arg2), .ev_arg3(exec_ev_arg3), .ev_flags(exec_ev_flags)); trace_sink_stub #(.FILENAME("iop_two_xfers_map.trace"), .SINK_LABEL("iop_map")) u_trace_map (.clk(clk), .rst_n(rst_n), .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys), .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags)); trace_sink_stub #(.FILENAME("iop_two_xfers_dmac.trace"), .SINK_LABEL("iop_dmac")) u_trace_dmac (.clk(clk), .rst_n(rst_n), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags)); trace_sink_stub #(.FILENAME("iop_two_xfers_intc.trace"), .SINK_LABEL("iop_intc")) u_trace_intc (.clk(clk), .rst_n(rst_n), .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys), .ev_event(intc_ev_event), .ev_arg0(intc_ev_arg0), .ev_arg1(intc_ev_arg1), .ev_arg2(intc_ev_arg2), .ev_arg3(intc_ev_arg3), .ev_flags(intc_ev_flags)); // ------------------------------------------------------------------ // Observers // ------------------------------------------------------------------ int dmac_done_events; int intc_assert_events; int intc_ack_events; int exec_op_events; int wait_irq_exit_events; int bne_taken_events; int halt_events; int errors; initial begin dmac_done_events = 0; intc_assert_events = 0; intc_ack_events = 0; exec_op_events = 0; wait_irq_exit_events = 0; bne_taken_events = 0; halt_events = 0; errors = 0; end always_ff @(posedge clk) begin if (rst_n && dmac_ev_valid && dmac_ev_subsys == trace_pkg::SUBSYS_DMAC && dmac_ev_event == trace_pkg::EV_DMA_DONE) dmac_done_events <= dmac_done_events + 1; if (rst_n && intc_ev_valid && intc_ev_subsys == trace_pkg::SUBSYS_INTC && intc_ev_event == trace_pkg::EV_IRQ) begin if (intc_ev_arg3 == 64'd0 && (intc_ev_flags & 32'h1) == 32'd0) intc_assert_events <= intc_assert_events + 1; else if (intc_ev_arg3 == 64'd1) intc_ack_events <= intc_ack_events + 1; end if (rst_n && exec_ev_valid) begin exec_op_events <= exec_op_events + 1; if ((exec_ev_flags & 32'h2) != 32'd0) wait_irq_exit_events <= wait_irq_exit_events + 1; if ((exec_ev_flags & 32'h4) != 32'd0) halt_events <= halt_events + 1; if ((exec_ev_flags & 32'h8) != 32'd0) bne_taken_events <= bne_taken_events + 1; end end // ------------------------------------------------------------------ // Preload helpers // ------------------------------------------------------------------ task automatic bridge_write(input logic [31:0] addr, input logic [31:0] data); @(negedge clk); tb_br_wr_en = 1'b1; tb_br_wr_addr = addr; tb_br_wr_data = data; tb_br_wr_be = 4'b1111; @(negedge clk); tb_br_wr_en = 1'b0; tb_br_wr_addr = 32'd0; tb_br_wr_data = 32'd0; tb_br_wr_be = 4'd0; endtask task automatic emit_op(input int slot, input logic [31:0] opcode, input logic [31:0] addr_or_tgt, input logic [31:0] data_or_exp); logic [31:0] base; base = SCRIPT_BASE + (slot << 4); bridge_write(base + 32'd0, opcode); bridge_write(base + 32'd4, addr_or_tgt); bridge_write(base + 32'd8, data_or_exp); bridge_write(base + 32'd12, 32'd0); endtask // ------------------------------------------------------------------ // Stimulus — single go_i pulse; loop-tail BNE drives two transactions // ------------------------------------------------------------------ initial begin rst_n = 1'b0; exec_go = 1'b0; tb_br_wr_en = 1'b0; tb_br_wr_addr = 32'd0; tb_br_wr_data = 32'd0; tb_br_wr_be = 4'd0; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // Payload (reused by both transactions — exec stub doesn't // rewrite between iterations) bridge_write(PAYLOAD_BASE + 32'h00, 32'hCAFE_0000); bridge_write(PAYLOAD_BASE + 32'h04, 32'hCAFE_0001); bridge_write(PAYLOAD_BASE + 32'h08, 32'hCAFE_0002); bridge_write(PAYLOAD_BASE + 32'h0C, 32'hCAFE_0003); // Control program emit_op( 0, OP_WRITE, INTC_MASK_ADDR, 32'h0000_0001); // loop_start @ pc=1 emit_op( 1, OP_WRITE, DMAC_MADR_ADDR, PAYLOAD_BASE); emit_op( 2, OP_WRITE, DMAC_BCR_ADDR, 32'h0000_0004); emit_op( 3, OP_WRITE, DMAC_CHCR_ADDR, 32'h0000_0001); emit_op( 4, OP_WAIT_IRQ, 32'd0, 32'd0); emit_op( 5, OP_WRITE, INTC_STAT_ADDR, 32'h0000_0001); emit_op( 6, OP_READ, DMAC_DONECOUNT_ADDR, 32'd0); // BNE: branch back to loop_start (pc=1) if DONE_COUNT != 2 emit_op( 7, OP_BNE, 32'h0000_0001, 32'h0000_0002); emit_op( 8, OP_HALT, 32'd0, 32'd0); // Single go_i pulse @(negedge clk); exec_go = 1'b1; @(negedge clk); exec_go = 1'b0; while (!exec_halt) @(posedge clk); repeat (4) @(posedge clk); // Post-halt checks if (dmac_done_events != 2) begin $error("[tb_iop_autonomous_two_xfers] expected 2 DMA_DONE, got %0d", dmac_done_events); errors = errors + 1; end if (intc_assert_events != 2) begin $error("[tb_iop_autonomous_two_xfers] expected 2 INTC asserts, got %0d", intc_assert_events); errors = errors + 1; end if (intc_ack_events != 2) begin $error("[tb_iop_autonomous_two_xfers] expected 2 INTC acks, got %0d", intc_ack_events); errors = errors + 1; end if (wait_irq_exit_events != 2) begin $error("[tb_iop_autonomous_two_xfers] expected 2 WAIT_IRQ exits, got %0d", wait_irq_exit_events); errors = errors + 1; end if (bne_taken_events != 1) begin $error("[tb_iop_autonomous_two_xfers] expected 1 BNE taken, got %0d", bne_taken_events); errors = errors + 1; end if (halt_events != 1) begin $error("[tb_iop_autonomous_two_xfers] expected 1 halt event, got %0d", halt_events); errors = errors + 1; end $display("[tb_iop_autonomous_two_xfers] ops=%0d wait_irq_exit=%0d bne_taken=%0d halt=%0d done=%0d asserts=%0d acks=%0d errors=%0d", exec_op_events, wait_irq_exit_events, bne_taken_events, halt_events, dmac_done_events, intc_assert_events, intc_ack_events, errors); if (errors == 0 && dmac_done_events == 2 && intc_assert_events == 2 && intc_ack_events == 2 && wait_irq_exit_events == 2 && bne_taken_events == 1 && halt_events == 1) $display("[tb_iop_autonomous_two_xfers] PASS"); else $display("[tb_iop_autonomous_two_xfers] FAIL"); $finish; end initial begin #2000000; $error("[tb_iop_autonomous_two_xfers] timeout"); $finish; end endmodule : tb_iop_autonomous_two_xfers