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>
240 lines
8.3 KiB
Systemverilog
240 lines
8.3 KiB
Systemverilog
// retroDE_ps2 — tb_intc_stub
|
|
//
|
|
// Verifies intc_stub: register mask/set/clear behavior, synthetic interrupt
|
|
// injection, and trace on state transitions. Explicitly covers the
|
|
// ack+assert collision corner case — simultaneous W1C and irq_src on the
|
|
// same bit must keep the bit pending and have arg1/arg2 reflect the
|
|
// post-update state (regression guard for the fix flagged in Codex's
|
|
// audit on 2026-04-16).
|
|
//
|
|
// Plan ref: docs/stub_module_plan.md (Initial testbench plan).
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_intc_stub;
|
|
|
|
logic clk;
|
|
logic rst_n;
|
|
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
// ------------------------------------------------------------------
|
|
// DUT and trace sink
|
|
// ------------------------------------------------------------------
|
|
|
|
logic reg_wr_en, reg_rd_en;
|
|
logic [7:0] reg_addr;
|
|
logic [31:0] reg_wr_data;
|
|
logic [31:0] reg_rd_data;
|
|
logic reg_rd_valid;
|
|
|
|
logic [15:0] irq_src;
|
|
logic ee_irq;
|
|
|
|
logic ev_valid;
|
|
trace_pkg::subsys_e ev_subsys;
|
|
trace_pkg::event_e ev_event;
|
|
logic [63:0] ev_arg0, ev_arg1, ev_arg2, ev_arg3;
|
|
logic [31:0] ev_flags;
|
|
|
|
intc_stub u_intc (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.reg_wr_en(reg_wr_en), .reg_rd_en(reg_rd_en), .reg_addr(reg_addr),
|
|
.reg_wr_data(reg_wr_data), .reg_rd_data(reg_rd_data),
|
|
.reg_rd_valid(reg_rd_valid),
|
|
.irq_src(irq_src), .cpu_irq(ee_irq),
|
|
.ev_valid(ev_valid), .ev_subsys(ev_subsys), .ev_event(ev_event),
|
|
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
|
|
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
|
|
.ev_flags(ev_flags)
|
|
);
|
|
|
|
trace_sink_stub #(.FILENAME("intc.trace"), .SINK_LABEL("intc")) u_trace_intc (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.ev_valid(ev_valid), .ev_subsys(ev_subsys), .ev_event(ev_event),
|
|
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
|
|
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
|
|
.ev_flags(ev_flags)
|
|
);
|
|
|
|
// ------------------------------------------------------------------
|
|
// Helpers
|
|
// ------------------------------------------------------------------
|
|
|
|
// Event counters. Three distinct categories, distinguished using
|
|
// intc_stub's own semantics:
|
|
// source_assertion: source-driven (flags[0]==0, arg3==0)
|
|
// register_write: mask-write or STAT W1C without ack effect
|
|
// (flags[0]==1, arg3==0 — i.e., mask-write branch)
|
|
// ack: STAT W1C that actually cleared bits (arg3==1)
|
|
//
|
|
// The regression guard specifically needs source_assertion counted as
|
|
// source-driven, not contaminated by mask writes. Previously all non-
|
|
// ack events were lumped together (flagged by Codex audit 2026-04-16).
|
|
int errors;
|
|
int source_assertion_events;
|
|
int register_write_events;
|
|
int ack_events;
|
|
|
|
initial begin
|
|
errors = 0;
|
|
source_assertion_events = 0;
|
|
register_write_events = 0;
|
|
ack_events = 0;
|
|
end
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n && ev_valid && ev_event == trace_pkg::EV_IRQ) begin
|
|
if (ev_arg3 == 64'd1) begin
|
|
ack_events <= ack_events + 1;
|
|
end else if (ev_flags[0]) begin
|
|
register_write_events <= register_write_events + 1;
|
|
end else begin
|
|
source_assertion_events <= source_assertion_events + 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
task automatic write_reg(input logic [7:0] offset, input logic [31:0] data);
|
|
@(negedge clk);
|
|
reg_wr_en = 1'b1;
|
|
reg_addr = offset;
|
|
reg_wr_data = data;
|
|
@(negedge clk);
|
|
reg_wr_en = 1'b0;
|
|
reg_addr = 8'd0;
|
|
reg_wr_data = 32'd0;
|
|
endtask
|
|
|
|
task automatic expect_ee_irq(input logic want, input string label);
|
|
if (ee_irq !== want) begin
|
|
$error("[tb_intc_stub] %s: ee_irq=%0b expected=%0b", label, ee_irq, want);
|
|
errors = errors + 1;
|
|
end
|
|
endtask
|
|
|
|
// ------------------------------------------------------------------
|
|
// Stimulus
|
|
// ------------------------------------------------------------------
|
|
|
|
initial begin
|
|
rst_n = 1'b0;
|
|
reg_wr_en = 1'b0;
|
|
reg_rd_en = 1'b0;
|
|
reg_addr = 8'd0;
|
|
reg_wr_data = 32'd0;
|
|
irq_src = 16'd0;
|
|
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (2) @(posedge clk);
|
|
|
|
// Post-reset: no interrupts should be pending, no line asserted.
|
|
expect_ee_irq(1'b0, "post-reset quiet");
|
|
|
|
// Enable mask for bits [3:0].
|
|
write_reg(8'h10, 32'h0000_000F);
|
|
repeat (2) @(posedge clk);
|
|
|
|
// Inject a pulse on bit 1 and verify ee_irq asserts.
|
|
@(negedge clk);
|
|
irq_src = 16'h0002;
|
|
@(negedge clk);
|
|
irq_src = 16'h0000;
|
|
repeat (2) @(posedge clk);
|
|
expect_ee_irq(1'b1, "after bit1 assertion");
|
|
|
|
// W1C the bit and verify ee_irq drops.
|
|
write_reg(8'h00, 32'h0000_0002);
|
|
repeat (2) @(posedge clk);
|
|
expect_ee_irq(1'b0, "after bit1 ack");
|
|
|
|
// Mask-gated: inject bit 8 while mask has only [3:0] enabled.
|
|
// ee_irq should stay low even though intc_stat latches.
|
|
@(negedge clk);
|
|
irq_src = 16'h0100;
|
|
@(negedge clk);
|
|
irq_src = 16'h0000;
|
|
repeat (2) @(posedge clk);
|
|
expect_ee_irq(1'b0, "masked bit8 holds line low");
|
|
|
|
// Widen mask to include bit 8; line should now assert.
|
|
write_reg(8'h10, 32'h0000_010F);
|
|
repeat (2) @(posedge clk);
|
|
expect_ee_irq(1'b1, "after widening mask");
|
|
write_reg(8'h00, 32'h0000_0100);
|
|
repeat (2) @(posedge clk);
|
|
expect_ee_irq(1'b0, "after bit8 ack");
|
|
|
|
// ------------------------------------------------------------------
|
|
// Regression guard: simultaneous ack-and-inject on a *pre-asserted*
|
|
// bit. Real-hardware contract: the assertion wins, intc_stat bit
|
|
// stays pending, and the ack-path trace must show the bit still set
|
|
// in arg1/arg2 (post-update state). Covers the audit fix from
|
|
// 2026-04-16 where arg1 was dropping stat_inject.
|
|
// ------------------------------------------------------------------
|
|
|
|
// Pre-assert bit 2 via a pulse on irq_src.
|
|
@(negedge clk);
|
|
irq_src = 16'h0004;
|
|
@(negedge clk);
|
|
irq_src = 16'h0000;
|
|
repeat (2) @(posedge clk);
|
|
if ((u_intc.intc_stat[2]) !== 1'b1) begin
|
|
$error("[tb_intc_stub] setup for collision test failed: bit2 not latched");
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// Collision: W1C bit 2 AND re-inject bit 2 on the same cycle.
|
|
@(negedge clk);
|
|
reg_wr_en = 1'b1;
|
|
reg_addr = 8'h00;
|
|
reg_wr_data = 32'h0000_0004;
|
|
irq_src = 16'h0004;
|
|
@(posedge clk);
|
|
#1;
|
|
if ((u_intc.intc_stat[2]) !== 1'b1) begin
|
|
$error("[tb_intc_stub] collision: bit2 dropped when inject+ack coincided");
|
|
errors = errors + 1;
|
|
end
|
|
@(negedge clk);
|
|
reg_wr_en = 1'b0;
|
|
reg_wr_data = 32'd0;
|
|
irq_src = 16'h0000;
|
|
repeat (2) @(posedge clk);
|
|
|
|
// Clean up: a plain W1C now should finally clear bit 2.
|
|
write_reg(8'h00, 32'h0000_0004);
|
|
repeat (2) @(posedge clk);
|
|
if ((u_intc.intc_stat[2]) !== 1'b0) begin
|
|
$error("[tb_intc_stub] post-collision cleanup: bit2 still pending");
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// ------------------------------------------------------------------
|
|
$display("[tb_intc_stub] source_assertion=%0d register_write=%0d ack=%0d errors=%0d",
|
|
source_assertion_events, register_write_events, ack_events, errors);
|
|
|
|
if (source_assertion_events < 3)
|
|
$error("[tb_intc_stub] expected at least 3 source-driven assertion events, got %0d",
|
|
source_assertion_events);
|
|
if (ack_events < 2)
|
|
$error("[tb_intc_stub] expected at least 2 ack events, got %0d", ack_events);
|
|
|
|
if (errors == 0 && source_assertion_events >= 3 && ack_events >= 2)
|
|
$display("[tb_intc_stub] PASS");
|
|
else
|
|
$display("[tb_intc_stub] FAIL");
|
|
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#100000;
|
|
$error("[tb_intc_stub] timeout");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_intc_stub
|