// retroDE_ps2 — gs_lpddr_fb_writer (Ch317) // // FIRST LPDDR-backed-framebuffer step: a write sink that takes the GS tile-FLUSH // pixel stream (PSMCT16, one pixel per emit) and commits it to an LPDDR-style // framebuffer, modelling the real EMIF AXI4 write path so the addressing / data / // stride / burst behaviour can be proven in sim before wiring the hard EMIF. // // SCOPE (Ch317, deliberately tight — see doc 0010 Ch317): // * Tile color/Z stay ON-CHIP; texture stays local. ONLY the framebuffer FLUSH // is redirected here. // * Address gen is the simple linear `fb_base + (screen_y*pitch + screen_x)*bpp` // — which the GS already produces on `raster_pixel_fb_addr_q` for PSMCT16 // ((fbp<<11) + (pixel_index<<1)), so we consume that byte address directly. // * PSMCT16 (2 bytes/pixel) — lower bandwidth, already-proven format. // * BURSTS: the flush emits a tile-row's 16 pixels at contiguous +2 byte // addresses, then jumps by `pitch` to the next row. The burst engine COALESCES // a contiguous +2 run into one burst, capped at MAX_BURST_BYTES (the doc 0008 // 4 KiB-boundary AXI rule). Real per-tile-row burst = 16 beats = 32 bytes. // * A staging FIFO decouples the 1-pixel/cycle emit from the burst engine and // surfaces under/overflow — the realistic shape a hard EMIF AXI master needs. // * Backing memory `fbmem` is byte-addressed and TB-readable for the // write/readback PROOF (a later rung swaps it for the EMIF AXI master + // LPDDR scanout). At enable=0 the whole module is inert (no writes, counters 0). // // COUNTERS (Codex acceptance — bandwidth/diag): bytes_written, burst_count, // busy_cycles (engine draining), fifo_overflow/underflow, fifo_occ_max. The TB // computes effective GB/s off bytes_written / (busy_cycles * clk_period). module gs_lpddr_fb_writer #( parameter int FB_BYTES = 8192, // backing FB size (64x64 PSMCT16 = 8 KiB) parameter int FIFO_DEPTH = 32, // pixel staging FIFO depth (power-of-2) parameter int MAX_BURST_BYTES = 4096 // AXI4 4 KiB-boundary cap (doc 0008 lesson) ) ( input logic clk, input logic rst_n, input logic enable, // LPDDR_FB_ENABLE; 0 → fully inert // GS tile-flush pixel stream (PSMCT16, one pixel per emit) input logic px_emit, input logic [31:0] px_addr, // linear FB byte address (raster_pixel_fb_addr_q) input logic [15:0] px_pix16, // raster_pixel_color_q[15:0] // diagnostics / proof (read hierarchically by the TB; no functional consumers) output logic [31:0] bytes_written, output logic [31:0] burst_count, output logic [31:0] busy_cycles, output logic [31:0] fifo_overflow_count, output logic [31:0] fifo_underflow_count, output logic [15:0] fifo_occ ); localparam int ADDR_W = $clog2(FB_BYTES); localparam int PTR_W = (FIFO_DEPTH > 1) ? $clog2(FIFO_DEPTH) : 1; // ---- byte-addressed backing framebuffer (the LPDDR model) ---- logic [7:0] fbmem [0:FB_BYTES-1]; // ---- staging FIFO of {addr, pix16} ---- logic [31:0] fifo_addr [0:FIFO_DEPTH-1]; logic [15:0] fifo_pix [0:FIFO_DEPTH-1]; logic [PTR_W-1:0] wptr, rptr; logic [PTR_W:0] count; // 0..FIFO_DEPTH (PTR_W+1 bits) // count==FIFO_DEPTH sets the top bit (FIFO_DEPTH is a power of 2 == 1< fifo_occ) fifo_occ <= 16'(count); // fifo_underflow_count: the engine never pops empty (do_pop gated on // !fifo_empty), so it stays 0 here — surfaced for the future EMIF rung // where an external AXI master could request beyond the staged data. end end endmodule : gs_lpddr_fb_writer