Files
retroDE_ps2/rtl/top/top_psmct32_raster_demo.sv
T
thejayman77 ec82764bef 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>
2026-06-29 20:10:50 -04:00

667 lines
29 KiB
Systemverilog
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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