Files
retroDE_ps2/rtl/gif_gs/vram_normalize_pkg.sv
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

201 lines
8.6 KiB
Systemverilog

// retroDE_ps2 — vram_normalize_pkg (Ch155)
//
// Writer-side normalization for `vram_bram_stub`. The new BRAM-friendly
// VRAM (Ch154) requires word-aligned writes (`write_addr[1:0] == 0`)
// with payload pre-shifted into the selected byte lane(s) and
// `write_be` set per byte. Today's writer-side RTL emits at sub-word
// boundaries for PSMCT16 (halfword), PSMT8 (byte), and PSMT4 (nibble);
// this package's `normalize_write` function bridges the contract.
//
// Codex Ch155 framing: "Add a small helper module or function for
// VRAM write normalization: input: natural byte address, PSM,
// pixel/index payload, old byte for PSMT4 if needed; output:
// word-aligned write_addr, shifted write_data, write_be."
//
// Scope (Ch155):
// - Function is defined + standalone-verified for all 4 PSMs.
// - NOT yet applied inside `gs_stub.raster_pixel_emit` or
// `gif_image_xfer_stub`. The PSMT4 case needs a read-then-write
// pipeline upstream (to source `old_byte`); that's a Ch156+
// RTL plumbing chapter. CT32/CT16/T8 cases are pure-comb and
// can be plumbed in as soon as the wiring lands.
//
// Pure-comb function — no RTL pipelining inside the function itself.
// Callers that need a read-then-write pipeline (PSMT4) own that
// pipelining and pass the read result as `old_byte`.
`timescale 1ns/1ps
package vram_normalize_pkg;
// GS PSM codes (subset relevant to VRAM writes).
localparam logic [5:0] PSM_PSMCT32 = 6'h00;
localparam logic [5:0] PSM_PSMCT16 = 6'h02;
localparam logic [5:0] PSM_PSMT8 = 6'h13;
localparam logic [5:0] PSM_PSMT4 = 6'h14;
typedef struct packed {
logic [31:0] write_addr; // word-aligned
logic [31:0] write_data; // payload shifted to lane
logic [3:0] write_be; // per-byte write enable
} norm_out_t;
// ----------------------------------------------------------------
// normalize_write — pure-comb writer-side normalization.
//
// Inputs:
// byte_addr — natural byte address as the legacy writers
// already emit. CT32 callers must already pass
// a word-aligned address. CT16 callers may pass
// a halfword address (byte_addr[1] selects low
// or high halfword). T8/T4 callers may pass any
// byte address.
// psm — GS PSM code (use one of the localparams above).
// payload — payload bits in the LSBs:
// CT32 → payload[31:0] is the full ABGR word.
// CT16 → payload[15:0] is the RGB5A1 halfword.
// T8 → payload[ 7:0] is the byte index.
// T4 → payload[ 3:0] is the nibble index.
// nibble_hi — T4 only. 0 = splice payload[3:0] into the LOW
// nibble of the byte at byte_addr, 1 = HIGH.
// Ignored for CT32/CT16/T8.
// old_byte — T4 only. Current value of mem[byte_addr]; the
// function splices the new nibble into this byte
// to preserve the other nibble. Ignored for
// CT32/CT16/T8.
//
// Output: word-aligned write_addr + shifted write_data + write_be.
//
// For PSMs other than CT32/CT16/T8/T4 the function returns a
// dropped write (write_be = 4'b0000, write_data = 32'd0); this
// matches `vram_stub`'s "unsupported PSMs are silent no-ops"
// posture (Ch95).
// ----------------------------------------------------------------
function automatic norm_out_t normalize_write(
input logic [31:0] byte_addr,
input logic [5:0] psm,
input logic [31:0] payload,
input logic nibble_hi,
input logic [7:0] old_byte
);
norm_out_t r;
// Word-aligned base address common to every PSM.
r.write_addr = byte_addr & ~32'd3;
unique case (psm)
// ------------------------------------------------------
// PSMCT32 — natural 32-bit-aligned write. byte_addr MUST
// already be word-aligned; if it isn't, the function
// produces a dropped write so the BRAM module never sees
// the misuse.
// ------------------------------------------------------
PSM_PSMCT32: begin
if (byte_addr[1:0] != 2'b00) begin
r.write_data = 32'd0;
r.write_be = 4'b0000;
end else begin
r.write_data = payload;
r.write_be = 4'b1111;
end
end
// ------------------------------------------------------
// PSMCT16 — halfword write. byte_addr[1] picks low or
// high halfword; byte_addr[0] MUST be 0.
// ------------------------------------------------------
PSM_PSMCT16: begin
if (byte_addr[0] != 1'b0) begin
r.write_data = 32'd0;
r.write_be = 4'b0000;
end else if (byte_addr[1] == 1'b0) begin
r.write_data = {16'd0, payload[15:0]};
r.write_be = 4'b0011;
end else begin
r.write_data = {payload[15:0], 16'd0};
r.write_be = 4'b1100;
end
end
// ------------------------------------------------------
// PSMT8 — single byte at any byte address. byte_addr[1:0]
// selects which of the 4 byte lanes gets the byte.
// ------------------------------------------------------
PSM_PSMT8: begin
unique case (byte_addr[1:0])
2'b00: begin
r.write_data = {24'd0, payload[7:0]};
r.write_be = 4'b0001;
end
2'b01: begin
r.write_data = {16'd0, payload[7:0], 8'd0};
r.write_be = 4'b0010;
end
2'b10: begin
r.write_data = {8'd0, payload[7:0], 16'd0};
r.write_be = 4'b0100;
end
2'b11: begin
r.write_data = {payload[7:0], 24'd0};
r.write_be = 4'b1000;
end
endcase
end
// ------------------------------------------------------
// PSMT4 — nibble splice. The function takes `old_byte` as
// the current value of mem[byte_addr] and produces a
// full-byte write at that address containing the new
// byte: (old_byte & ~nibble_mask) | (new_nibble in lane).
//
// The caller is responsible for sourcing `old_byte` —
// typically a 1-cycle read of mem[byte_addr] before the
// write fires. Ch156+ inserts that read pipeline inside
// gs_stub.raster_pixel_emit + gif_image_xfer_stub.
//
// byte_addr[1:0] selects the byte lane in the 32-bit
// word; nibble_hi selects which nibble of that byte gets
// the new value.
// ------------------------------------------------------
PSM_PSMT4: begin
logic [7:0] new_byte;
if (nibble_hi)
new_byte = {payload[3:0], old_byte[3:0]};
else
new_byte = {old_byte[7:4], payload[3:0]};
unique case (byte_addr[1:0])
2'b00: begin
r.write_data = {24'd0, new_byte};
r.write_be = 4'b0001;
end
2'b01: begin
r.write_data = {16'd0, new_byte, 8'd0};
r.write_be = 4'b0010;
end
2'b10: begin
r.write_data = {8'd0, new_byte, 16'd0};
r.write_be = 4'b0100;
end
2'b11: begin
r.write_data = {new_byte, 24'd0};
r.write_be = 4'b1000;
end
endcase
end
// ------------------------------------------------------
// Unsupported PSM → drop the write. Matches vram_stub's
// Ch95 stance.
// ------------------------------------------------------
default: begin
r.write_data = 32'd0;
r.write_be = 4'b0000;
end
endcase
return r;
endfunction
endpackage : vram_normalize_pkg