code wiki / _hdl_build / nx_fpga_dc.nx

nx_fpga_dc.nx source

↩ module page · 120 lines · 6562 B

1// nx_fpga_dc.nx -- LIB: RUNG 30 of the sovereign FPGA-boot sim -- the DISPLAY-CONTROLLER peripheral 2// (an MMIO-mapped framebuffer + scanout). This is the missing rung between the (complete in-sim) RV64IM 3// CPU and a GAME with graphics: the SoC modeled so far has UART text only; this adds pixels. 4// 5// THE HONEST HARDWARE MODEL (what is "real gates" vs what is a memory block): 6// - FRAMEBUFFER STORAGE = a caller-owned memory block of W*H packed-RGB i64 words. In real silicon a 7// framebuffer is BRAM / external DRAM -- a memory block -- NOT a LUT4/DFF fabric (the fabric RAM tops 8// out at 16 words by its single-LUT4 address decode). So modeling storage as a memory block is the 9// FAITHFUL hardware model, not a shortcut. 10// - THE MMIO ADDRESS DECODE runs ON THE FABRIC -- this is the gate-level logic a display controller adds 11// to the bus. It composes the PROVEN nx_fpga_addsub subtractor (rungs R6/R7, gate-verified == the 12// behavioral ALU): for a store address `addr`, 13// idx = addr - FB_BASE (fabric subtractor result = the linear pixel index) 14// ge_base = carry_out(addr - FB_BASE) (== 1 iff addr >= FB_BASE; the R8b SLTU convention: 15// carry-out of a-b is 1 iff a >= b unsigned) 16// ge_end = carry_out(addr - FB_END) (== 1 iff addr >= FB_END) 17// in_fb = ge_base AND (NOT ge_end) (FB_BASE <= addr < FB_END) 18// The substantive decision (two wide unsigned compares + the index subtraction) is a real fabric 19// computation driven only from the subtractor's bitstream via fab_eval; the final 1-bit AND of the two 20// decoded flags is orchestration (exactly as the fabric-CPU gates read fabric POs and orchestrate). 21// 22// SCANOUT: read the framebuffer block -> the games' i64 packed-RGB format (R | G<<8 | B<<16) -> the gate 23// encodes it to a PNG (write_png) so the fabric's pixels are eyeball-verifiable. 24// 25// NEVER-BRICK (#26): pure integer, bounded (exactly 2 subtractor passes per decoded store, each O(W) cells), 26// total, deterministic, caller-owned state, ZERO persistent/hardware writes. A framebuffer pixel write is 27// volatile VRAM -- a `gui`-axis capability that cannot corrupt firmware/CMOS/NVRAM/EEPROM BY CONSTRUCTION, 28// so it carries no brick risk even when this peripheral is later bound to a real linear framebuffer. 29// license_tier: ORIGINAL 30import "nx_fpga_addsub.nx" 31import "nx_fpga_fabric.nx" 32import "nx_syscalls.nx" 33const K_MAGIC_2654435761: i64 = 2654435761 34 35// A display controller instance: an MMIO framebuffer + the on-fabric address decode it adds to the bus. 36struct NxFpgaDC { 37 fb: *i64 // W*H packed-RGB framebuffer words (the BRAM/DRAM analog -- a memory block) 38 fbw: i64 39 fbh: i64 40 base: i64 // MMIO window base (FB_BASE) 41 fbend: i64 // MMIO window end (FB_BASE + W*H), one past the last pixel addr 42 addr_w: i64 // address width in bits for the on-fabric decode 43 sub_npi: i64 // the subtractor fabric's primary-input count (from fab_build_addsub) 44 sub_init: *i64 // subtractor bitstream: per-cell LUT init 45 sub_src: *i64 // subtractor bitstream: per-cell wire sources 46 sub_po: *i64 // subtractor primary-output sources 47 pi: *i64 // scratch primary-input vector (caller-owned -> no per-call mmap) 48 co: *i64 // scratch cell-output vector (caller-owned) 49 last_idx: i64 // the pixel index produced by the most recent dc_decode (set in-place, no out-ptr) 50} 51 52// pack an RGB triple into the games' i64 framebuffer format (R low byte, then G, then B). 53func dc_pack(r: i64, g: i64, b: i64) -> i64 { 54 return (r & 0xff) | ((g & 0xff) << 8) | ((b & 0xff) << 16) 55} 56 57// build the on-fabric MMIO-decode subtractor + bind the framebuffer block. Caller allocates everything: 58// fb[fbw*fbh], sub_init[3*addr_w], sub_src[3*addr_w*4], sub_po[addr_w], pi[2*addr_w+1], co[3*addr_w]. 59func dc_init(dc: *NxFpgaDC, fb: *i64, fbw: i64, fbh: i64, base: i64, addr_w: i64, 60 sub_init: *i64, sub_src: *i64, sub_po: *i64, pi: *i64, co: *i64) -> i64 { 61 dc.fb = fb 62 dc.fbw = fbw 63 dc.fbh = fbh 64 dc.base = base 65 dc.fbend = base + fbw * fbh 66 dc.addr_w = addr_w 67 dc.sub_init = sub_init 68 dc.sub_src = sub_src 69 dc.sub_po = sub_po 70 dc.pi = pi 71 dc.co = co 72 dc.last_idx = 0 73 dc.sub_npi = fab_build_addsub(addr_w, sub_init, sub_src, sub_po) 74 return 0 75} 76 77// DECODE an MMIO store address ON THE FABRIC. Returns in_fb (0/1); sets dc.last_idx = the pixel index. 78// Two subtractor passes (addr-base, addr-end); the top carry cell of nx_fpga_addsub holds carry-out. 79func dc_decode(dc: *NxFpgaDC, addr: i64) -> i64 { 80 let W: i64 = dc.addr_w 81 let cc: i64 = W + 2 * (W - 1) + 1 // top-bit carry cell (carry-out of the subtraction) 82 let idx: i64 = fab_addsub_run(W, dc.sub_npi, dc.sub_init, dc.sub_src, dc.sub_po, dc.pi, dc.co, addr, dc.base, 1) 83 let ge_base: i64 = dc.co[cc] & 1 // 1 iff addr >= FB_BASE 84 fab_addsub_run(W, dc.sub_npi, dc.sub_init, dc.sub_src, dc.sub_po, dc.pi, dc.co, addr, dc.fbend, 1) 85 let ge_end: i64 = dc.co[cc] & 1 // 1 iff addr >= FB_END 86 dc.last_idx = idx 87 var in_fb: i64 = 0 88 if ge_base == 1 { if ge_end == 0 { in_fb = 1 } } // FB_BASE <= addr < FB_END 89 return in_fb 90} 91 92// MMIO STORE: if the on-fabric decode places `addr` in the framebuffer window, write the pixel. 93// Returns in_fb (1 = the store hit a framebuffer pixel, 0 = ignored -- the peripheral does not claim it). 94// Bounds-checked at the device/memory boundary (#12: defensive at the boundary). 95func dc_store(dc: *NxFpgaDC, addr: i64, value: i64) -> i64 { 96 let in_fb: i64 = dc_decode(dc, addr) 97 if in_fb == 1 { 98 let idx: i64 = dc.last_idx 99 let n: i64 = dc.fbw * dc.fbh 100 if idx >= 0 { if idx < n { dc.fb[idx] = value & 0xffffffff } } 101 } 102 return in_fb 103} 104 105// fill the whole framebuffer with one packed color (clear-to-color). 106func dc_clear(dc: *NxFpgaDC, color: i64) -> i64 { 107 let n: i64 = dc.fbw * dc.fbh 108 var i: i64 = 0 109 while i < n { dc.fb[i] = color; i = i + 1 } 110 return 0 111} 112 113// SCANOUT helper: an xor-fold checksum over the framebuffer (used by gates to prove "nothing changed"). 114func dc_checksum(dc: *NxFpgaDC) -> i64 { 115 let n: i64 = dc.fbw * dc.fbh 116 var s: i64 = 0 117 var i: i64 = 0 118 while i < n { s = s ^ (dc.fb[i] + i * K_MAGIC_2654435761); i = i + 1 } 119 return s 120}