Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)

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>
This commit is contained in:
2026-06-29 20:10:50 -04:00
commit ec82764bef
2462 changed files with 2174303 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
// retroDE_ps2 — de25_nano_pll_stub (Ch151)
//
// Sim-friendly stub matching the Quartus IOPLL "pll" module signature
// used by sibling cores (retroDE_nes/ip/pll/pll_bb.v and
// retroDE_splash/ip/sys_pll/sys_pll_bb.v). Real synthesis swaps this
// stub for Terasic-supplied IP via Quartus's IP catalog and a
// `\`ifdef USE_PLL_IP` gate in the board top.
//
// Behavior:
// - `outclk_0` is a direct pass-through of `refclk` (no PLL
// multiplication; sim doesn't need a different frequency, and a
// pass-through still exercises the PLL-gated reset bridge in the
// Ch149 board top).
// - `locked` rises after a small post-reset delay (~32 cycles),
// mimicking real-IP behavior where lock acquires after rst goes
// low. Held LOW while `rst` is HIGH.
//
// The signature matches Quartus's IOPLL exactly so swapping in the
// real IP is a single `\`ifdef` at instantiation; the rest of the
// board top is unchanged.
`timescale 1ns/1ps
module de25_nano_pll_stub (
input wire refclk, // reference clock from CLOCK2_50
input wire rst, // active-HIGH async reset (Quartus convention)
output wire outclk_0, // pass-through of refclk
output wire locked // high once "lock" is acquired
);
assign outclk_0 = refclk;
// Lock counter — tick up while rst is low; saturate at 32 and hold
// `locked` high. While rst is high, hold counter at 0 and locked low.
logic [5:0] lock_cnt;
always_ff @(posedge refclk or posedge rst) begin
if (rst)
lock_cnt <= 6'd0;
else if (lock_cnt < 6'd32)
lock_cnt <= lock_cnt + 6'd1;
end
assign locked = (lock_cnt == 6'd32);
endmodule : de25_nano_pll_stub
File diff suppressed because it is too large Load Diff
+666
View File
@@ -0,0 +1,666 @@
// retroDE_ps2 — top_psmct32_raster_demo (Ch146)
//
// First hardware-targeted top wrapper, structured around the Ch123 PSMCT32
// raster end-to-end demo (the simplest direct-color path; see the Ch144
// hardware-readiness report in docs/contracts/gif_gs.md for rationale and
// dep-tree audit). This module is the one a board-level synthesis project
// would target — board-level concerns (HDMI/VGA PHY, pin constraints,
// .mem bake tooling, clock-domain crossings) are deliberately deferred to
// later chapters. Ch146's job is to prove the design can be expressed as
// a single SystemVerilog module with a sensible top-level shape.
//
// Topology mirrors the Ch123 TB exactly — the 11 modules in the Ch144
// dep tree, all instantiated here, with hardware-friendly tweaks:
//
// bios_rom_stub#(.IMAGE_FILE(BIOS_IMAGE_FILE)) — EE bootlet at 0xBFC0_0000
// ee_ram_stub#(.IMAGE_FILE(PAYLOAD_IMAGE_FILE)) — GIF payload at phys 0x100
// ee_memory_map_stub#(.USEG_SHADOW_WORDS_PARAM(1024)) — Ch145 BRAM shrink
// ee_core_stub#(.PC_RESET(0xBFC00000)) — MIPS R5900 core
// ee_gs_priv_bridge_stub — 32-bit MMIO → 64-bit GS-priv
// dmac_reg_stub — DMAC ch2
// gif_packed_stub#(.REAL_AD_REG_MAP(1'b1)) — GIFtag + PACKED A+D parser
// gs_stub#(.PSMCT32_SWIZZLE(1'b1)) — GS register file + raster
// gif_image_xfer_stub#(.PSMCT32_SWIZZLE(1'b1)) — TRXDIR/IMAGE engine (idle in Ch123)
// vram_stub#(.BYTES(8192)) — 8 KiB VRAM (one PSMCT32 page)
// gs_pcrtc_stub#(.PSMCT32_SWIZZLE(1'b1)) — PCRTC scanout
//
// Differences from the Ch123 TB:
// - No procedural ee_prog_word() / preload_qword() drives. The BIOS
// bootlet and GIF payload are preloaded by `$readmemh` from the
// IMAGE_FILE parameters (default empty = synthetic NOP-sled fallback
// in bios_rom_stub for a "won't crash on power-up" smoke baseline,
// and an all-zeros ee_ram_stub which yields no DMAC payload but a
// stable PCRTC frame).
// - useg_shadow_mem trimmed to 1024 words (4 KiB) via Ch145
// parameter — no useg traffic in the Ch123 data plane.
// - All trace event outputs left open. Status is exposed as a
// debug bundle (core_halt, dma_done_seen, frame_seen) that a
// board can wire to LEDs.
// - The Ch123 TB's collision-check `$error` and observer counters
// are TB-only and do not appear here. (Their checks land in the
// focused Ch146 TB tb_top_psmct32_raster_demo.sv instead.)
//
// Top-level ports:
// clk, rst_n — single clock domain, active-low synchronous reset
// core_go — pulsed high for one cycle to start the EE bootlet
// (a board reset-release sequencer can tie it high
// after rst_n deasserts)
// r/g/b, hsync, vsync, de — 8-bit RGB scanout (PCRTC active region)
// core_halt — high once SYSCALL halts the EE
// dma_done_seen — sticky: high once DMAC channel-2 fires its DONE event
// frame_seen — sticky: high once one full PCRTC frame end-of-frame fires
//
// Parameters:
// H_ACTIVE / V_ACTIVE — PCRTC active region (defaults to the Ch123 16×8)
// BIOS_SIZE_BYTES — bios_rom_stub size (default 4 KiB)
// RAM_SIZE_BYTES — ee_ram_stub size (default 4 KiB)
// VRAM_BYTES — vram_stub size (default 8 KiB)
// USEG_SHADOW_WORDS_PARAM — Ch145 useg-shadow size (default 1024 = 4 KiB)
//
// Macros (NOT parameters — iverilog-12 string-parameter forwarding
// limitation forced them to be macros; see the `\`define` block
// below the `timescale directive):
// TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE — path to bios.mem
// (one 32-bit hex word/line)
// TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE — path to payload.mem
// (one 128-bit hex qword/line)
// Both default to "" so the wrapper is still elaborable without
// fixtures (synthetic NOP-sled in bios_rom_stub + zero-init
// ee_ram_stub, which produces no DMAC payload but a stable PCRTC
// frame). On synthesis these become FPGA-tool defines.
//
// PASS for the Ch146 focused TB matches Ch123 exactly:
// dma=(1,24,1) ee_dmac_wr=3 giftags=4 ad_writes=20 xfer_writes=0
// ee_priv_wr=4 bridge_fires=4 core_halt=1 emits=128 frame=16x8
`timescale 1ns/1ps
// BIOS / payload image paths are passed via macros (iverilog-12
// limitation: string parameter forwarding through hierarchy
// elaborates inconsistently). On synthesis the same macros become
// FPGA-tool defines pointing at .mem fixtures or board-specific
// files. The macros default to empty strings (synthetic NOP-sled +
// zero-RAM fallback in bios_rom_stub / ee_ram_stub) so the wrapper
// is still elaborable without bake artifacts present.
`ifndef TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE
`define TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE ""
`endif
`ifndef TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE
`define TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE ""
`endif
module top_psmct32_raster_demo
import trace_pkg::*;
#(
parameter int H_ACTIVE = 16,
parameter int V_ACTIVE = 8,
parameter int BIOS_SIZE_BYTES = 4 * 1024,
parameter int RAM_SIZE_BYTES = 4 * 1024,
parameter int VRAM_BYTES = 8 * 1024,
parameter int unsigned USEG_SHADOW_WORDS_PARAM = 1024,
// Brick 1 — PSMCT32 page/block swizzle gate. Default 1 preserves
// the Ch123/Ch251 swizzled raster+scanout behavior (and every
// existing TB that drives this top). A TEXTURED-sprite demo
// fixture sets this to 0 so the linear gs_texel_addr fetch and the
// BITBLT upload land in the SAME (linear) VRAM layout — avoiding
// the swizzle reconciliation the gs_stub TODO flags. The gate is
// forwarded to gs_stub / gif_image_xfer_stub / gs_pcrtc_stub
// together so all three VRAM views stay consistent.
parameter bit PSMCT32_SWIZZLE = 1'b1
) (
input logic clk,
input logic rst_n,
input logic core_go,
output logic [7:0] r,
output logic [7:0] g,
output logic [7:0] b,
output logic hsync,
output logic vsync,
output logic de,
output logic core_halt,
output logic dma_done_seen,
output logic frame_seen,
output logic raster_overflow,
// Ch174 — event toggles for HPS-visible counters. See the
// mirror block in top_psmct32_raster_demo_bram.sv for the full
// pulse-CDC contract. Toggle, not pulse — by design.
output logic frame_toggle,
output logic dma_done_toggle
);
localparam int RAM_ADDR_W = $clog2(RAM_SIZE_BYTES);
localparam int BIOS_ADDR_W = $clog2(BIOS_SIZE_BYTES);
// ---------------------------------------------------------------------
// ee_ram_stub — DMAC-side GIF payload
// ---------------------------------------------------------------------
logic ram_rd_en;
logic [RAM_ADDR_W-1:0] ram_rd_addr;
logic [127:0] ram_rd_data;
logic ram_rd_valid;
// Top has no TB-direct write path; the wr_* ports are tied off.
logic [7:0] ram_master_id;
assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0;
ee_ram_stub #(
.SIZE_BYTES(RAM_SIZE_BYTES),
.IMAGE_FILE(`TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE)
) u_ram (
.clk(clk), .rst_n(rst_n),
.rd_en(ram_rd_en), .rd_addr(ram_rd_addr),
.rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
.wr_en(1'b0), .wr_addr('0), .wr_data(128'd0), .wr_be(16'd0),
.master_id(ram_master_id),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// ---------------------------------------------------------------------
// bios_rom_stub — EE bootlet at 0xBFC0_0000
// ---------------------------------------------------------------------
logic bios_rd_en;
logic [21:0] bios_rd_addr_full;
logic [BIOS_ADDR_W-1:0] bios_rd_addr;
logic bios_rd_valid;
logic [31:0] bios_rd_data;
assign bios_rd_addr = bios_rd_addr_full[BIOS_ADDR_W-1:0];
bios_rom_stub #(
.SIZE_BYTES(BIOS_SIZE_BYTES),
.IMAGE_FILE(`TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE)
) u_bios (
.clk(clk), .rst_n(rst_n),
.rd_en(bios_rd_en),
.rd_addr(bios_rd_addr),
.rd_data(bios_rd_data),
.rd_valid(bios_rd_valid),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// ---------------------------------------------------------------------
// dmac_reg_stub — channel-2 NORMAL transfer
// ---------------------------------------------------------------------
logic dmac_reg_wr_en;
logic [7:0] dmac_reg_offset;
logic [31:0] dmac_reg_wr_data;
logic dmac_mem_rd_en;
logic [31:0] dmac_mem_rd_addr;
logic dmac_gif_valid;
logic [127:0] dmac_gif_data;
logic dmac_gif_last;
logic dmac_gif_ready;
logic dmac_ev_valid;
subsys_e dmac_ev_subsys;
event_e dmac_ev_event;
logic [127:0] map_to_dmac_rd_data;
logic map_to_dmac_rd_valid;
dmac_reg_stub u_dmac (
.clk(clk), .rst_n(rst_n),
.reg_wr_en(dmac_reg_wr_en), .reg_offset(dmac_reg_offset),
.reg_wr_data(dmac_reg_wr_data),
.reg_rd_en(1'b0), .reg_rd_data(), .reg_rd_valid(),
.mem_rd_en(dmac_mem_rd_en), .mem_rd_addr(dmac_mem_rd_addr),
.mem_rd_data(map_to_dmac_rd_data), .mem_rd_valid(map_to_dmac_rd_valid),
.ep_valid(dmac_gif_valid), .ep_data(dmac_gif_data),
.ep_last(dmac_gif_last), .ep_ready(dmac_gif_ready),
.irq_completion_o(),
.ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
.ev_event(dmac_ev_event),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// ---------------------------------------------------------------------
// ee_memory_map_stub — bus arbiter (USEG_SHADOW shrunk per Ch145)
// ---------------------------------------------------------------------
logic ee_cpu_rd_en;
logic [31:0] ee_cpu_rd_addr;
logic [31:0] ee_cpu_rd_data;
logic ee_cpu_rd_valid;
logic ee_cpu_wr_en;
logic [31:0] ee_cpu_wr_addr;
logic [31:0] ee_cpu_wr_data;
logic [3:0] ee_cpu_wr_be;
logic map_gs_priv_wr_en;
logic [15:0] map_gs_priv_wr_addr;
logic [31:0] map_gs_priv_wr_data;
logic [3:0] map_gs_priv_wr_be;
logic map_ram_rd_en;
logic [24:0] map_ram_rd_addr;
ee_memory_map_stub #(
.USEG_SHADOW_WORDS_PARAM(USEG_SHADOW_WORDS_PARAM)
) u_map (
.clk(clk), .rst_n(rst_n),
.ee_rd_en (ee_cpu_rd_en),
.ee_rd_addr(ee_cpu_rd_addr),
.ee_rd_data(ee_cpu_rd_data),
.ee_rd_valid(ee_cpu_rd_valid),
.ee_wr_en (ee_cpu_wr_en),
.ee_wr_addr(ee_cpu_wr_addr),
.ee_wr_data(ee_cpu_wr_data),
.ee_wr_be (ee_cpu_wr_be),
.dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr),
.dmac_rd_data(map_to_dmac_rd_data),
.dmac_rd_valid(map_to_dmac_rd_valid),
.bios_rd_en (bios_rd_en),
.bios_rd_addr(bios_rd_addr_full),
.bios_rd_data(bios_rd_data),
.bios_rd_valid(bios_rd_valid),
.ram_rd_en(map_ram_rd_en), .ram_rd_addr(map_ram_rd_addr),
.ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
.bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
.bridge_wr_data(128'd0), .bridge_wr_be(16'd0),
.bridge_master_id(8'd0),
.ram_wr_en(), .ram_wr_addr(), .ram_wr_data(),
.ram_wr_be(), .ram_master_id(),
.ee_dmac_ch2_wr_en (dmac_reg_wr_en),
.ee_dmac_ch2_wr_addr(dmac_reg_offset),
.ee_dmac_ch2_wr_data(dmac_reg_wr_data),
.ee_dmac_ch2_rd_en(), .ee_dmac_ch2_rd_addr(),
.ee_dmac_ch2_rd_data(32'd0), .ee_dmac_ch2_rd_valid(1'b0),
.ee_intc_wr_en(), .ee_intc_wr_addr(), .ee_intc_wr_data(),
.ee_intc_rd_en(), .ee_intc_rd_addr(),
.ee_intc_rd_data(32'd0), .ee_intc_rd_valid(1'b0),
.ee_misc_mmio_wr_en(), .ee_misc_mmio_wr_addr(), .ee_misc_mmio_wr_data(), .ee_misc_mmio_wr_be(),
.ee_misc_mmio_rd_en(), .ee_misc_mmio_rd_addr(),
.ee_misc_mmio_rd_data(32'd0), .ee_misc_mmio_rd_valid(1'b0),
.ee_biu_wr_en(), .ee_biu_wr_addr(), .ee_biu_wr_data(), .ee_biu_wr_be(),
.ee_biu_rd_en(), .ee_biu_rd_addr(),
.ee_biu_rd_data(32'd0), .ee_biu_rd_valid(1'b0),
.ee_gs_priv_wr_en (map_gs_priv_wr_en),
.ee_gs_priv_wr_addr(map_gs_priv_wr_addr),
.ee_gs_priv_wr_data(map_gs_priv_wr_data),
.ee_gs_priv_wr_be (map_gs_priv_wr_be),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
assign ram_rd_en = map_ram_rd_en;
assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0];
// ---------------------------------------------------------------------
// ee_core_stub
// ---------------------------------------------------------------------
logic [31:0] core_pc;
logic core_trap;
ee_core_stub #(
.PC_RESET(32'hBFC0_0000),
.STRICT_UNSUPPORTED(1'b0)
) u_core (
.clk(clk), .rst_n(rst_n),
.go_i(core_go),
.map_rd_en (ee_cpu_rd_en),
.map_rd_addr(ee_cpu_rd_addr),
.map_rd_data(ee_cpu_rd_data),
.map_rd_valid(ee_cpu_rd_valid),
.map_wr_en (ee_cpu_wr_en),
.map_wr_addr(ee_cpu_wr_addr),
.map_wr_data(ee_cpu_wr_data),
.map_wr_be (ee_cpu_wr_be),
.cpu_irq(1'b0),
.halt_o(core_halt),
.pc_o (core_pc),
.trap_o(core_trap),
.trap_pc_o(),
.trap_instr_o(),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// ---------------------------------------------------------------------
// gif_packed_stub
// ---------------------------------------------------------------------
logic gif_in_ready;
logic [7:0] gif_gif_reg_num;
logic gif_gif_reg_wr_en;
logic [63:0] gif_gif_reg_data;
logic gif_image_data_valid;
logic [127:0] gif_image_data;
logic gif_image_data_last;
logic xfer_data_ready;
// Ch172 — raster FIFO full from gs_stub, fed back into gif_packed_stub.
logic gs_raster_fifo_full;
gif_packed_stub #(.REAL_AD_REG_MAP(1'b1)) u_gif (
.clk(clk), .rst_n(rst_n),
.in_valid(dmac_gif_valid), .in_data(dmac_gif_data),
.in_last(dmac_gif_last), .in_ready(gif_in_ready),
.image_data_valid(gif_image_data_valid),
.image_data(gif_image_data),
.image_data_last(gif_image_data_last),
.image_data_ready(xfer_data_ready),
.raster_fifo_full(gs_raster_fifo_full),
.gs_wr_en(), .gs_wr_addr(), .gs_wr_data(),
.gif_reg_wr_en(gif_gif_reg_wr_en),
.gif_reg_num(gif_gif_reg_num),
.gif_reg_data(gif_gif_reg_data),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// DMAC ready follows gif_packed_stub's in_ready directly (Ch110
// image-xfer backpressure propagates through gif_packed_stub).
assign dmac_gif_ready = gif_in_ready;
// ---------------------------------------------------------------------
// gs_stub — PSMCT32 raster, swizzled
// ---------------------------------------------------------------------
logic priv_reg_wr_en;
logic [15:0] priv_reg_wr_addr;
logic [63:0] priv_reg_wr_data;
logic [63:0] pmode_q, dispfb1_q, display1_q;
logic [63:0] bitbltbuf_q, trxpos_q, trxreg_q, trxdir_q;
logic trxdir_wr_q;
logic raster_pixel_emit;
logic [63:0] raster_pixel_color_q;
logic [31:0] raster_pixel_fb_addr_q;
logic [3:0] raster_pixel_be_q;
logic [31:0] raster_pixel_mask_q;
// Brick 1 — texture-sampler read port out of gs_stub. Wired to
// vram_stub's SECOND read port (read2) below. In this top there is
// no clut_loader_stub instantiated (clut_enable=0 at the PCRTC), so
// read2 is dedicated to the texel fetch; the mux contract (CLUT load
// at TEX0 commit vs texel fetch during scan) is documented at the
// read2 wiring site.
logic gs_tex_rd_en;
logic [31:0] gs_tex_rd_addr;
logic [31:0] gs_tex_rd_data;
// Brick 2a — dest-framebuffer read port for alpha blending. Wired
// to vram_stub.read2 below, arbitrated with the texel-fetch port.
// vram_stub.read2 is COMBINATIONAL, so FB_RD_REGISTERED defaults to
// 0 (dest data valid the same cycle the S2 address is presented).
logic gs_fb_rd_en;
logic [31:0] gs_fb_rd_addr;
logic [31:0] gs_fb_rd_data;
// Brick 2b — Z-buffer stored-Z read port. Wired to vram_stub.read2
// below, arbitrated with the texel-fetch + alpha dest-fb ports.
// vram_stub.read2 is COMBINATIONAL, so Z_RD_REGISTERED defaults to 0.
logic gs_z_rd_en;
logic [31:0] gs_z_rd_addr;
logic [31:0] gs_z_rd_data;
gs_stub #(
.PSMCT32_SWIZZLE(PSMCT32_SWIZZLE)
) u_gs (
.clk(clk), .rst_n(rst_n),
.reg_wr_en (priv_reg_wr_en),
.reg_wr_addr(priv_reg_wr_addr),
.reg_wr_data(priv_reg_wr_data),
.gif_reg_wr_en(gif_gif_reg_wr_en),
.gif_reg_num (gif_gif_reg_num),
.gif_reg_data (gif_gif_reg_data),
.bg_r(), .bg_g(), .bg_b(),
.pmode_q(pmode_q), .dispfb1_q(dispfb1_q), .display1_q(display1_q),
.prim_q(), .rgbaq_q(),
.xyz2_q(), .xyzf2_q(),
.frame_1_q(), .zbuf_1_q(),
.tex0_1_q(), .tex0_1_cbp_q(), .tex0_1_cpsm_q(),
.tex0_1_csm_q(), .tex0_1_csa_q(), .tex0_1_cld_q(), .tex0_1_wr_q(),
.bitbltbuf_q(bitbltbuf_q),
.trxpos_q(trxpos_q),
.trxreg_q(trxreg_q),
.trxdir_q(trxdir_q),
.trxdir_wr_q(trxdir_wr_q),
.prim_complete(), .prim_complete_count(),
.prim_v0_q(), .prim_v1_q(), .prim_v2_q(),
.prim_color_q(),
.prim_color_v0_q(), .prim_color_v1_q(), .prim_color_v2_q(),
.prim_v0_decoded_q(), .prim_v1_decoded_q(), .prim_v2_decoded_q(),
.prim_v0_color_decoded_q(), .prim_v1_color_decoded_q(), .prim_v2_color_decoded_q(),
.pixel_emit(), .pixel_emit_count(),
.pixel_x_q(), .pixel_y_q(),
.pixel_color_q(),
.pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_q(), .pixel_fb_addr_q(),
.raster_pixel_emit(raster_pixel_emit),
.raster_pixel_emit_count(),
.raster_pixel_x_q(), .raster_pixel_y_q(),
.raster_pixel_color_q(raster_pixel_color_q),
.raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
.raster_pixel_be_q(raster_pixel_be_q),
.raster_pixel_mask_q(raster_pixel_mask_q),
.raster_pixel_psm_q(),
.raster_active(),
.raster_overflow(raster_overflow),
.raster_fifo_full(gs_raster_fifo_full),
.raster_degenerate(),
.tex_rd_en (gs_tex_rd_en),
.tex_rd_addr(gs_tex_rd_addr),
.tex_rd_data(gs_tex_rd_data),
// Ch296 — PSMCT32-only top: no CLUT instantiated, the PSMT8 index
// path is never selected (s1_tex_active gates it on PSM==0x13).
// Tie the lookup data to 0; leave the index output open.
.clut_rd_idx (),
.clut_rd_data(32'd0),
.clut_load_busy(1'b0),
.fb_rd_en (gs_fb_rd_en),
.fb_rd_addr(gs_fb_rd_addr),
.fb_rd_data(gs_fb_rd_data),
.z_rd_en (gs_z_rd_en),
.z_rd_addr(gs_z_rd_addr),
.z_rd_data(gs_z_rd_data),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// ---------------------------------------------------------------------
// ee_gs_priv_bridge_stub
// ---------------------------------------------------------------------
ee_gs_priv_bridge_stub u_priv_bridge (
.clk(clk), .rst_n(rst_n),
.ee_wr_en (map_gs_priv_wr_en),
.ee_wr_addr(map_gs_priv_wr_addr),
.ee_wr_data(map_gs_priv_wr_data),
.ee_wr_be (map_gs_priv_wr_be),
.gs_reg_wr_en (priv_reg_wr_en),
.gs_reg_wr_addr(priv_reg_wr_addr),
.gs_reg_wr_data(priv_reg_wr_data)
);
// ---------------------------------------------------------------------
// gif_image_xfer_stub — idle in Ch123 (no TRXDIR/IMAGE), but
// instantiated for symmetry. The TRXDIR-driven Ch124 demo would
// turn it load-bearing.
// ---------------------------------------------------------------------
logic xfer_we;
logic [31:0] xfer_waddr;
logic [31:0] xfer_wdata;
logic [3:0] xfer_wbe;
logic [31:0] xfer_wmask;
logic xfer_busy;
gif_image_xfer_stub #(
.PSMCT32_SWIZZLE(PSMCT32_SWIZZLE)
) u_xfer (
.clk(clk), .rst_n(rst_n),
.trxdir_wr_pulse(trxdir_wr_q),
.trxdir(trxdir_q),
.bitbltbuf(bitbltbuf_q),
.trxpos(trxpos_q),
.trxreg(trxreg_q),
.data_valid(gif_image_data_valid),
.data_qword(gif_image_data),
.data_last (gif_image_data_last),
.data_ready(xfer_data_ready),
.vram_we (xfer_we),
.vram_waddr(xfer_waddr),
.vram_wdata(xfer_wdata),
.vram_wbe (xfer_wbe),
.vram_wmask(xfer_wmask),
.busy (xfer_busy)
);
// ---------------------------------------------------------------------
// VRAM mux: xfer-OWNED when xfer.busy, raster-OWNED otherwise.
// (Sequenced: in Ch123 raster fills exclusively; xfer never fires.
// In a future TRXDIR variant the mux still works — payload upload
// finishes before raster starts.)
// ---------------------------------------------------------------------
logic vram_we_mux;
logic [31:0] vram_waddr_mux;
logic [31:0] vram_wdata_mux;
logic [3:0] vram_wbe_mux;
logic [31:0] vram_wmask_mux;
assign vram_we_mux = xfer_busy ? xfer_we : raster_pixel_emit;
assign vram_waddr_mux = xfer_busy ? xfer_waddr : raster_pixel_fb_addr_q;
assign vram_wdata_mux = xfer_busy ? xfer_wdata : raster_pixel_color_q[31:0];
assign vram_wbe_mux = xfer_busy ? xfer_wbe : raster_pixel_be_q;
assign vram_wmask_mux = xfer_busy ? xfer_wmask : raster_pixel_mask_q;
logic [31:0] vram_raddr;
logic [31:0] vram_rdata;
// ---------------------------------------------------------------------
// Brick 1 — read2 (second VRAM read port) MUX.
//
// read2 is shared between two consumers that are sequenced in time:
// - clut_loader_stub : VRAM→CLUT copy at TEX0 commit (BEFORE the
// raster scan). NOT instantiated in this top
// (PCRTC clut_enable=0), so it never drives
// read2 here.
// - gs_stub texel fetch : during the raster SCAN, one read per
// inside pixel of a textured SPRITE.
// Because CLUT load completes before scanout begins, a simple
// gs_tex_rd_en select is collision-free. When a future variant adds
// clut_loader, extend this select: read2_addr = clut_active ?
// clut_rd_addr : gs_tex_rd_addr.
// vram_stub's read2 is COMBINATIONAL; gs_stub presents the address
// from a registered S1 stage and consumes tex_rd_data one cycle
// later, so the effective latency matches TEX_RD_LATENCY=1.
// ---------------------------------------------------------------------
//
// Brick 2a — THIRD potential read2 consumer: the alpha-blend
// dest-fb read (gs_fb_rd_en/gs_fb_rd_addr). A flat alpha-blended
// SPRITE never textures, so gs_tex_rd_en and gs_fb_rd_en are
// mutually exclusive (gs_stub.new_abe_active requires
// !close_tme_effective). The combinational read2_data is fanned out
// to both consumers; only the active one's address selects the mux.
//
// Brick 2b — FOURTH potential read2 consumer: the Z-buffer stored-Z
// read (gs_z_rd_en/gs_z_rd_addr). A flat Z-tested SPRITE never
// textures and never alpha-blends (gs_stub.new_zte_active requires
// !close_tme_effective && !new_abe_active), so the four read2
// consumers are mutually exclusive by feature.
logic [31:0] vram_read2_addr;
logic [31:0] vram_read2_data;
assign vram_read2_addr = gs_tex_rd_en ? gs_tex_rd_addr
: gs_fb_rd_en ? gs_fb_rd_addr
: gs_z_rd_en ? gs_z_rd_addr
: 32'd0;
assign gs_tex_rd_data = vram_read2_data;
assign gs_fb_rd_data = vram_read2_data;
assign gs_z_rd_data = vram_read2_data;
// synthesis translate_off
always_ff @(posedge clk) begin
if (rst_n && gs_tex_rd_en && gs_fb_rd_en)
$error("Brick2a: read2 overlap @%0t — texel fetch and alpha dest-fb read both active; one read is being dropped (must be mutually exclusive by texturing).",
$time);
if (rst_n && gs_z_rd_en && (gs_tex_rd_en || gs_fb_rd_en))
$error("Brick2b: read2 overlap @%0t — Z-buffer read collides with another consumer; one read is being dropped (Z-tested flat sprite must be mutually exclusive with texel/alpha).",
$time);
end
// synthesis translate_on
vram_stub #(.BYTES(VRAM_BYTES)) u_vram (
.clk(clk), .rst_n(rst_n),
.write_en (vram_we_mux),
.write_addr(vram_waddr_mux),
.write_data(vram_wdata_mux),
.write_be (vram_wbe_mux),
.write_mask(vram_wmask_mux),
.read_addr (vram_raddr),
.read_data (vram_rdata),
.read2_addr(vram_read2_addr),
.read2_data(vram_read2_data)
);
// ---------------------------------------------------------------------
// gs_pcrtc_stub — PSMCT32 swizzled scanout
// ---------------------------------------------------------------------
logic end_of_frame;
gs_pcrtc_stub #(
.H_ACTIVE(H_ACTIVE), .H_FRONT(1), .H_SYNC(1), .H_BACK(1),
.V_ACTIVE(V_ACTIVE), .V_FRONT(1), .V_SYNC(1), .V_BACK(1),
.PSMCT32_SWIZZLE(PSMCT32_SWIZZLE)
) u_pcrtc (
.clk(clk), .rst_n(rst_n),
.pmode_q (pmode_q),
.dispfb1_q (dispfb1_q),
.display1_q (display1_q),
.vram_read_addr(vram_raddr),
.vram_read_data(vram_rdata),
.clut_enable (1'b0),
.clut_csa (5'd0),
.clut_read_idx (),
.clut_read_data(32'd0),
.hsync(hsync), .vsync(vsync), .de(de),
.r(r), .g(g), .b(b),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
// gs_pcrtc_stub doesn't expose end_of_frame as a port; the Ch123 TB
// taps it via hierarchical ref. For the top wrapper we synthesize an
// equivalent edge by watching vsync rise.
logic vsync_d;
always_ff @(posedge clk) begin
if (!rst_n) vsync_d <= 1'b0;
else vsync_d <= vsync;
end
assign end_of_frame = vsync && !vsync_d;
// ---------------------------------------------------------------------
// Sticky status outputs.
// ---------------------------------------------------------------------
logic dma_done_seen_q;
logic frame_seen_q;
always_ff @(posedge clk) begin
if (!rst_n) begin
dma_done_seen_q <= 1'b0;
frame_seen_q <= 1'b0;
end else begin
if (dmac_ev_valid && (dmac_ev_event == EV_DMA_DONE))
dma_done_seen_q <= 1'b1;
if (end_of_frame)
frame_seen_q <= 1'b1;
end
end
assign dma_done_seen = dma_done_seen_q;
assign frame_seen = frame_seen_q;
// ---------------------------------------------------------------------
// Ch174 — event toggles for HPS-visible counters.
// ---------------------------------------------------------------------
logic frame_toggle_q;
logic dma_done_toggle_q;
always_ff @(posedge clk) begin
if (!rst_n) begin
frame_toggle_q <= 1'b0;
dma_done_toggle_q <= 1'b0;
end else begin
if (end_of_frame)
frame_toggle_q <= ~frame_toggle_q;
if (dmac_ev_valid && (dmac_ev_event == EV_DMA_DONE))
dma_done_toggle_q <= ~dma_done_toggle_q;
end
end
assign frame_toggle = frame_toggle_q;
assign dma_done_toggle = dma_done_toggle_q;
endmodule : top_psmct32_raster_demo
File diff suppressed because it is too large Load Diff