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>
667 lines
29 KiB
Systemverilog
667 lines
29 KiB
Systemverilog
// 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
|