// 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