// retroDE_ps2 — tb_iop_self_driven // // First RAM-backed self-driven IOP transaction. After a single go_i // pulse, the TB stops orchestrating. The exec stub fetches its control // program from IOP RAM — same 32-bit map path a real IOP CPU would use // — and runs it to completion. The TB's only responsibility after // reset is to lay the script and DMA payload into RAM, then observe. // // Chain (all architectural addresses routed by the real IOP map): // iop_exec_stub // ├── map reads 0x0000_0400..0x0000_04FF → IOP RAM (ifetch) // ├── map write 0x1F80_1074 → IOP INTC I_MASK // ├── map writes 0x1F80_1520/4/8 → IOP DMAC ch9 MADR/BCR/CHCR // ├── map reads 0x1F80_1070 → IOP INTC I_STAT // └── map writes 0x1F80_1070 → IOP INTC I_STAT (W1C) // // iop_dmac_reg_stub (ch9) pulls source words through map.dma_rd_*, // drives an endpoint whose ep_ready the TB uses for the stall gate. // On S_DONE, irq_completion_o pulses; INTC latches and raises cpu_irq. // // RAM layout preloaded by the TB via bridge_wr_*: // 0x0000_0200..0x0000_020F DMA payload (4 × 32-bit words) // 0x0000_0400..0x0000_049F 10-op exec script (16 bytes / op) // // Script (one transaction): // pc=0 WRITE INTC_MASK = 1 // pc=1 WRITE DMAC_MADR = 0x0000_0200 // pc=2 WRITE DMAC_BCR = 4 // pc=3 WRITE DMAC_CHCR = 1 (start) // pc=4 WAIT_IRQ // pc=5 READ INTC_STAT (verify pending) // pc=6 WRITE INTC_STAT = 1 (W1C ack) // pc=7 READ INTC_STAT (verify cleared) // pc=8 READ DMAC_CHCR (verify start cleared) // pc=9 HALT // // Pre-completion stall window: // The TB holds ep_ready=0 for STALL_WINDOW cycles after reset. During // that period the DMAC starts, tries to ship beat 0, and stalls. The // exec stub must stay in WAIT_IRQ with pc at the wait slot. No // cpu_irq must assert. Halt must NOT happen. Once ep_ready rises, // the DMAC completes, cpu_irq comes up, the exec stub walks the // remaining ops and halts. `timescale 1ns/1ps module tb_iop_self_driven; localparam int IOP_RAM_BYTES = 4 * 1024; localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES); // Script location (must match exec stub SCRIPT_BASE parameter) localparam logic [31:0] SCRIPT_BASE = 32'h0000_0400; localparam logic [31:0] PAYLOAD_BASE = 32'h0000_0200; // Architectural addresses reached by the script 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] INTC_STAT_ADDR = 32'h1F80_1070; localparam logic [31:0] INTC_MASK_ADDR = 32'h1F80_1074; // Opcodes (keep in sync with iop_exec_stub) 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 [7:0] EXEC_PC_WAIT_IRQ = 8'd4; localparam int STALL_WINDOW = 30; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // ------------------------------------------------------------------ // IOP exec stub (drives the IOP map CPU-side port) // ------------------------------------------------------------------ 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_* to preload payload + script. // ------------------------------------------------------------------ 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; // TB-driven bridge_wr_* for preload 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), // TB direct .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 into the TB (data on floor) // ------------------------------------------------------------------ logic ep_valid; logic [31:0] ep_data; logic ep_last; logic ep_ready; logic tb_stall; assign ep_ready = !tb_stall; 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_self_driven_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_self_driven_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_self_driven_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_self_driven_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 (no orchestration after go) // ------------------------------------------------------------------ int dmac_done_events; int intc_assert_events; int intc_ack_events; int exec_op_events; int wait_irq_exit_events; int halt_events; logic [7:0] stall_pc_sample; logic stall_irq_sample; logic stall_halt_sample; 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; halt_events = 0; stall_pc_sample = 8'd0; stall_irq_sample = 1'b0; stall_halt_sample = 1'b0; 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; end end // ------------------------------------------------------------------ // Preload helpers (bridge_wr_* → iop_ram_stub, 32-bit words) // ------------------------------------------------------------------ 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 // ------------------------------------------------------------------ initial begin rst_n = 1'b0; exec_go = 1'b0; tb_stall = 1'b1; 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); // Preload DMA payload (4 distinctive words into IOP RAM) bridge_write(PAYLOAD_BASE + 32'h00, 32'hBEEF_0000); bridge_write(PAYLOAD_BASE + 32'h04, 32'hBEEF_0001); bridge_write(PAYLOAD_BASE + 32'h08, 32'hBEEF_0002); bridge_write(PAYLOAD_BASE + 32'h0C, 32'hBEEF_0003); // Preload control program at SCRIPT_BASE emit_op( 0, OP_WRITE, INTC_MASK_ADDR, 32'h0000_0001); 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_READ, INTC_STAT_ADDR, 32'd0); emit_op( 6, OP_WRITE, INTC_STAT_ADDR, 32'h0000_0001); emit_op( 7, OP_READ, INTC_STAT_ADDR, 32'd0); emit_op( 8, OP_READ, DMAC_CHCR_ADDR, 32'd0); emit_op( 9, OP_HALT, 32'd0, 32'd0); // Single-shot start @(negedge clk); exec_go = 1'b1; @(negedge clk); exec_go = 1'b0; // Let ops 0..3 execute and reach WAIT_IRQ. Each op = 3 ifetch // reads (6 cycles) + op execution (~2 cycles). Slack is ample. repeat (80) @(posedge clk); // Stall-window invariants stall_pc_sample = exec_pc; stall_irq_sample = iop_cpu_irq; stall_halt_sample = exec_halt; if (stall_pc_sample !== EXEC_PC_WAIT_IRQ) begin $error("[tb_iop_self_driven] exec pc in stall window: got %0d, expected %0d (WAIT_IRQ)", stall_pc_sample, EXEC_PC_WAIT_IRQ); errors = errors + 1; end if (stall_irq_sample !== 1'b0) begin $error("[tb_iop_self_driven] cpu_irq asserted during stall window"); errors = errors + 1; end if (stall_halt_sample !== 1'b0) begin $error("[tb_iop_self_driven] exec halted during stall window"); errors = errors + 1; end repeat (STALL_WINDOW) @(posedge clk); if (exec_pc !== EXEC_PC_WAIT_IRQ) begin $error("[tb_iop_self_driven] exec advanced past WAIT_IRQ without IRQ: pc=%0d", exec_pc); errors = errors + 1; end if (iop_cpu_irq !== 1'b0) begin $error("[tb_iop_self_driven] cpu_irq rose during extended stall"); errors = errors + 1; end // Release the stall — chain completes, exec stub walks remaining ops @(negedge clk); tb_stall = 1'b0; while (!exec_halt) @(posedge clk); repeat (4) @(posedge clk); if (dmac_done_events != 1) begin $error("[tb_iop_self_driven] expected 1 DMA_DONE, got %0d", dmac_done_events); errors = errors + 1; end if (intc_assert_events != 1) begin $error("[tb_iop_self_driven] expected 1 INTC assert, got %0d", intc_assert_events); errors = errors + 1; end if (intc_ack_events != 1) begin $error("[tb_iop_self_driven] expected 1 INTC ack, got %0d", intc_ack_events); errors = errors + 1; end if (wait_irq_exit_events != 1) begin $error("[tb_iop_self_driven] expected 1 WAIT_IRQ exit, got %0d", wait_irq_exit_events); errors = errors + 1; end if (halt_events != 1) begin $error("[tb_iop_self_driven] expected 1 halt event, got %0d", halt_events); errors = errors + 1; end $display("[tb_iop_self_driven] stall_pc=%0d ops=%0d wait_irq_exit=%0d halt=%0d done=%0d asserts=%0d acks=%0d errors=%0d", stall_pc_sample, exec_op_events, wait_irq_exit_events, halt_events, dmac_done_events, intc_assert_events, intc_ack_events, errors); if (errors == 0 && dmac_done_events == 1 && intc_assert_events == 1 && intc_ack_events == 1 && wait_irq_exit_events == 1 && halt_events == 1) $display("[tb_iop_self_driven] PASS"); else $display("[tb_iop_self_driven] FAIL"); $finish; end initial begin #2000000; $error("[tb_iop_self_driven] timeout"); $finish; end endmodule : tb_iop_self_driven