// retroDE_ps2 — ps2_sh3_tex_upload (Ch352) // // Uploads the reconstructed 512x512 PSMT8 SH3 texture (65536 32-bit words) into FPGA-private LPDDR4B via the // PS2 HPS-bridge write-probe, verifies the readback, arms the texture-cache fill, and retriggers the feeder. // One command — mmap'd register pokes (NOT 65536 devmem process spawns). Same bridge protocol as ps2_feeder.c // and docs/hardware/ps2_lpddr_tex_test.sh (Ch322), just scaled to the full 256 KiB texture. // // Build on the board: gcc -O2 -o ps2_sh3_tex_upload ps2_sh3_tex_upload.c // Run (after fit+boot): sudo ./ps2_sh3_tex_upload sh3_real_tex_lpddr.mem // (copy sh3_real_tex_lpddr.mem from sim/data/top_psmct32_raster_demo/ to the board alongside the binary.) // // Sequence: (1) write WRADDR=0x200000, stream 65536 words to WRDATA; (2) read each back via the read-probe and // confirm sum32/xor32 match the file; (3) arm cache fill (0x054), poll fill_done, check beats/bytes/rd_errs; // (4) pulse the feeder retrigger (0x0E8) so the scene re-renders with the now-warm cache. #include #include #include #include #include #include #include #include #define OFF_LPDDR_STATUS 0x02C // R: [3] rd_pending (read-probe in flight) #define OFF_LPDDR_RDADDR 0x03C // W: set read byte addr + trigger ; R: 32-bit word #define OFF_LPDDR_WRADDR 0x04C // W: set LPDDR byte addr (auto-increments +4 per WRDATA write) #define OFF_LPDDR_WRDATA 0x050 // W: data word -> single 32-bit LPDDR write + addr += 4 #define OFF_TEX_FILL_CTRL 0x054 // W[0]: arm cache fill ; R: [0]fill_done [1]wr_busy #define OFF_LPDDR_WR_ERRS 0x06C // R: write-probe non-OKAY (BRESP) responses (expect 0) #define WR_PENDING_BIT 0x4 // 0x054 bit2 — Ch352 STABLE write-done flag (poll instead of transient wr_busy) #define RD_PENDING_BIT 0x8 // 0x02C bit3 #define OFF_TEX_FILL_BEATS 0x058 // R: beats filled (expect TEX_BYTES/32 = 8192) #define OFF_TEX_FILL_BYTES 0x05C // R: bytes filled (expect 262144) #define OFF_TEX_RD_ERRS 0x068 // R: fill non-OKAY read responses (expect 0) #define OFF_TEX_FILL_CRC 0x070 // R: sum32 of EVERY word the cache wrote into tex_mem (must == file sum32) #define OFF_FEEDER_GO 0x0E8 // W[0]: trigger/retrigger the feeder // Ch353 — GS_SH3_LPDDR_FB (--lpddr-fb) additions: preclear the LPDDR framebuffer + drive Codex's host-start sequence. #define OFF_LPDDR_CTRL 0x018 // W: [0]arm [1]canary [2]video_src [3]scanout_lb (arm/base/canary latch on write) #define OFF_LPDDR_FB_BASE 0x01C // W: FB base byte addr (writer fb_base); set BEFORE the 0x018 arm-commit #define OFF_LPDDR_STATUS 0x02C // R: [0]idle (writer drained, stable post-render) [3]rd_pending #define OFF_LPDDR_BYTES 0x030 // R: FB writer beats written (snapped at idle) — non-zero after a real render #define OFF_FEEDER_STATUS 0x0D8 // R: [0] feeder control FSM in C_READY (setup + CLUT done) #define IDLE_BIT 0x1 // 0x02C bit0 — writer idle (TRANSIENT: pulses between feeder render batches) #define FRAME_DRAINED_BIT 0x40 // 0x02C bit6 — Ch353 STABLE ordered drain ack (poll THIS, not idle) #define READY_BIT 0x1 // 0x0D8 bit0 #define N_WORDS 65536 // 512*512 PSMT8 / 4 #define TEX_BYTES 262144 #define N_BEATS 8192 // TEX_BYTES / 32 #define FB_WORDS 85504 // Ch353 — 256*334 PSMCT32 framebuffer (0x000000..0x0537FF) #define FB_BYTES 342016 typedef struct { volatile uint8_t *base; int dry; } bridge_t; static void wr32(bridge_t *b, int off, uint32_t v){ if(!b->dry) *(volatile uint32_t*)(b->base+off)=v; } static uint32_t rd32(bridge_t *b, int off){ return b->dry?0:*(volatile uint32_t*)(b->base+off); } int main(int argc, char **argv){ unsigned long base = 0x40000000UL; // PS2 HPS-bridge base (override --base or PS2_BRIDGE_BASE) unsigned long lpddr_base = 0x00200000; // EMIF byte base where the texture is staged (= TEX_LPDDR_BASE RTL) const char *texfile = "sh3_real_tex_lpddr.mem"; int dry=0, do_fill=1, do_retrig=1, lpddr_fb=0, fb_rows=334; // fb_rows: 334 (Ch353 single) / 338 (Ch354 multi) char *env = getenv("PS2_BRIDGE_BASE"); if (env) base = strtoul(env,NULL,0); for (int i=1;i LPDDR 0x%lx (bridge base 0x%lx%s)\n", n, texfile, sum, xr, lpddr_base, base, dry?", DRY-RUN":""); // ---- open the bridge ---- bridge_t br = {0,dry}; int fd=-1; void *map=NULL; if (!dry){ fd=open("/dev/mem", O_RDWR|O_SYNC); if (fd<0){ fprintf(stderr,"error: open /dev/mem (run as root?): %s\n", strerror(errno)); return 1; } map=mmap(NULL,0x1000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,(off_t)base); if (map==MAP_FAILED){ fprintf(stderr,"error: mmap 0x%lx: %s\n", base, strerror(errno)); close(fd); return 1; } br.base=(volatile uint8_t*)map; } // ---- Ch353 GS_SH3_LPDDR_FB (Codex host sequence, steps 1-2): wait for the feeder to reach C_READY (setup + // CLUT complete; with FEEDER_AUTOSTART=0 this happens WITHOUT a boot render), then PRECLEAR the LPDDR // framebuffer 0x000000..0x0537FF (85504 words) via the same proven write-probe. This is the "no hardware clear // engine" path (Codex): ~30% more traffic than the 65536-word texture upload, poll write_pending + check BRESP. ---- if (lpddr_fb && !dry){ int g=0; while(!(rd32(&br,OFF_FEEDER_STATUS)&READY_BIT) && g<20000000) g++; printf("[ps2_sh3_tex_upload] (lpddr-fb) feeder ready=%d (setup+CLUT done, no boot render)\n", (rd32(&br,OFF_FEEDER_STATUS)&READY_BIT)?1:0); unsigned fb_words = 256u*(unsigned)fb_rows; // 256x334=85504 (Ch353) ; 256x338=86528=0x54800 (Ch354) wr32(&br, OFF_LPDDR_WRADDR, 0x00000000u); // FB base 0 for (unsigned i=0;i tex_mem %s\n", fcrc, sum, (fcrc==sum) ? "INTEGRITY OK" : "CORRUPT"); if (!done || beats!=N_BEATS || bytes!=TEX_BYTES || errs!=0) fprintf(stderr,"WARN: cache fill stats off — texels may be wrong; check EMIF cal + LPDDR base.\n"); if (fcrc!=sum) fprintf(stderr,"WARN: cache fill_crc mismatch — tex_mem corrupt on board (NOT a divider/sampler issue).\n"); } // ---- Ch353 GS_SH3_LPDDR_FB (Codex host sequence, steps 5-8): base 0 + canary off + ARM the writer, GO, await // the ordered drain, then enable the scanout. The render epoch (RTL) emits exactly ONE EOF on the post-GO // feeder_ready rise -> ONE frame_drained, which HARD-gates the line-buffer scanout (no timeout; failure stays // black). Confirm the drain via LPDDR_STATUS[0] idle (stable post-render) + LPDDR_BYTES!=0 (a frame was written). ---- if (lpddr_fb && !dry){ wr32(&br, OFF_LPDDR_FB_BASE, 0x00000000u); // step 5: FB base 0 ... wr32(&br, OFF_LPDDR_CTRL, 0x1); // ... canary off, ARM the writer (commit snapshots base/arm) wr32(&br, OFF_FEEDER_GO, 0x1); // step 6: GO -> render epoch -> one EOF -> one frame_drained printf("[ps2_sh3_tex_upload] (lpddr-fb) writer armed (base 0, canary off), feeder GO issued.\n"); // step 7: await the ORDERED drain ack (0x02C[6] frame_drained) — STABLE, asserts only after the EOF marker's // last BRESP. Do NOT poll [0]idle: it pulses between the feeder's render batches and reads a mid-render count. { int g=0; while(!(rd32(&br,OFF_LPDDR_STATUS)&FRAME_DRAINED_BIT) && g<40000000) g++; } uint32_t fd = (rd32(&br,OFF_LPDDR_STATUS)&FRAME_DRAINED_BIT)?1:0, fbb = rd32(&br,OFF_LPDDR_BYTES); printf("[ps2_sh3_tex_upload] (lpddr-fb) drain: frame_drained=%u, FB beats written=%u (exp ~6500 for the full frame)\n", fd, fbb); if (!fd || fbb==0) fprintf(stderr,"WARN: drain not confirmed (frame_drained=%u beats=%u) — display stays BLACK (hard gate, no timeout).\n", fd, fbb); wr32(&br, OFF_LPDDR_CTRL, 0x1|0x4); // step 8: arm=1 + video_src=1 -> HDMI sources the LPDDR scanout printf("[ps2_sh3_tex_upload] (lpddr-fb) video_src=1 -> HDMI now sources the LPDDR line-buffer scanout (256x%u).\n", (unsigned)fb_rows); } // ---- (4) retrigger the feeder so the scene re-renders with the warm cache (crop profile path) ---- else if (do_retrig && !dry){ wr32(&br, OFF_FEEDER_GO, 0x1); printf("[ps2_sh3_tex_upload] feeder retriggered.\n"); } if (!dry){ munmap(map,0x1000); close(fd); } printf("[ps2_sh3_tex_upload] DONE — check HDMI vs the crop reference (recon/sh3_real_ref.png).\n"); return 0; }