ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
535 lines
21 KiB
Systemverilog
535 lines
21 KiB
Systemverilog
// 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
|