// retroDE_ps2 — ps2_sh3_multitex (Ch355 Brick 1 host two-group RUNTIME-STAGING flow) // // Composites two authentic SH3 draws with DIFFERENT textures/CLUTs (A=idx19562, B=idx89761) into ONE LPDDR // framebuffer (320x381) via scene-level texture rebind + staged-list retriggering: // preclear ONCE -> fill+CRC tex A -> STREAM list A -> GO A + fresh drain -> REFILL+CRC tex B -> STREAM list B -> // GO B + frame_drained high->low->high (anti-stale) -> enable scanout after the 2nd fresh drain. // Requires the frame_drained diagnostic bit (0x02C[6]) — bundled since Ch354's fit. // // Build on the board: gcc -O2 -o ps2_sh3_multitex ps2_sh3_multitex.c // Run (after fit+boot): sudo ./ps2_sh3_multitex sh3_mtA_tex_lpddr.mem sh3_mtB_tex_lpddr.mem feeder_sh3_mtA.mem feeder_sh3_mtB.mem #include #include #include #include #include #include #include #include #include #define OFF_LPDDR_CTRL 0x018 // W: [0]arm [1]canary [2]video_src [3]scanout_lb #define OFF_LPDDR_FBBASE 0x01C #define OFF_LPDDR_STATUS 0x02C // R: [0]idle [6]frame_drained (STABLE ordered ack) #define OFF_LPDDR_BYTES 0x030 #define OFF_WRADDR 0x04C #define OFF_WRDATA 0x050 #define OFF_TEX_FILL 0x054 // W[0] arm fill ; R[0] fill_done [2] write_pending #define OFF_TEX_BEATS 0x058 #define OFF_TEX_BYTES 0x05C #define OFF_TEX_RDERRS 0x068 #define OFF_WR_ERRS 0x06C #define OFF_TEX_CRC 0x070 #define OFF_STG_STATUS 0x0D8 // R[0] feeder ready ; W reset staging write address #define OFF_STG_LO 0x0DC // W low32 ; R current staging addr #define OFF_STG_HI 0x0E4 // W high32 (commits {hi,lo}, ++addr) ; R records emitted #define OFF_GO 0x0E8 // W[0] trigger feeder #define FRAME_DRAINED 0x40 // 0x02C[6] #define IDLE_BIT 0x01 #define WR_PENDING 0x04 // 0x054[2] #define READY_BIT 0x01 #define N_TEX 65536 // 512x512 PSMT8 / 4 #define TEX_BYTES 262144 #define N_BEATS 8192 #define STG_MAX 2048 #define FB_WORDS (320*381) // 121920 ; bytes = 0x77100 typedef struct { volatile uint8_t *base; int dry; } br_t; static void wr32(br_t*b,int o,uint32_t v){ if(!b->dry) *(volatile uint32_t*)(b->base+o)=v; } static uint32_t rd32(br_t*b,int o){ return b->dry?0:*(volatile uint32_t*)(b->base+o); } // NOTE: buffer must exceed the longest banner line; skip any line whose first non-space char isn't a hex digit // (comments/blank) — a too-small buffer that splits a long comment must NOT parse the tail as a data word. static int load32(const char*p, uint32_t*a, int n){ FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;} int k=0; char ln[512]; while(kdry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} } uint32_t e=rd32(b,OFF_WR_ERRS); printf("[mt] preclear FB %u words (0..0x%x) wr_bresp_errs=%u\n", FB_WORDS, FB_WORDS*4, e); return e?-1:0; } // upload a texture to LPDDR (write-probe), then arm a FRESH cache fill and verify crc/beats/bytes/rd_errs. static int upload_and_fill(br_t*b, const char*nm, uint32_t*tex, uint32_t exp_crc){ wr32(b, OFF_WRADDR, 0x200000u); for (int i=0;idry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} } uint32_t werr=rd32(b,OFF_WR_ERRS); // FRESH fill: arm, then require fill_done low->high (cache rearm), not a lingering high. wr32(b, OFF_TEX_FILL, 0x1); if(!b->dry){ int g=0; while((rd32(b,OFF_TEX_FILL)&0x1)&&g<2000)g++; // drops (busy) g=0; while(!(rd32(b,OFF_TEX_FILL)&0x1)&&g<400000)g++; } // rises (done) uint32_t crc=rd32(b,OFF_TEX_CRC), beats=rd32(b,OFF_TEX_BEATS), bytes=rd32(b,OFF_TEX_BYTES), rderr=rd32(b,OFF_TEX_RDERRS); printf("[mt] tex %s: upload wr_errs=%u ; fill crc=0x%08x (exp 0x%08x) beats=%u (exp %d) bytes=%u (exp %d) rd_errs=%u\n", nm, werr, crc, exp_crc, beats, N_BEATS, bytes, TEX_BYTES, rderr); if(b->dry) return 0; if(werr || crc!=exp_crc || beats!=N_BEATS || bytes!=TEX_BYTES || rderr){ fprintf(stderr," FAIL tex %s fill mismatch\n", nm); return -1; } return 0; } static int wait_ready(br_t*b){ if(b->dry)return 0; for(int i=0;i<3000000;i++) if(rd32(b,OFF_STG_STATUS)&READY_BIT) return 0; fprintf(stderr," FAIL: feeder never reached C_READY\n"); return -1; } // stream a 64-bit staging list over the bridge; report words + the 12-bit address bound. static int stream_list(br_t*b, const char*nm, uint64_t*list, int nwords){ if (nwords > 4095){ fprintf(stderr," FAIL list %s %d words exceeds 12-bit bridge addr\n", nm, nwords); return -1; } if (wait_ready(b)) return -1; // feeder in C_READY before staging wr32(b, OFF_STG_STATUS, 0); // reset staging write address to 0 (value MUST be 0 — matches ps2_feeder) for (int i=0;i>32)); } if (wait_ready(b)) return -1; // staging accepted uint32_t addr=rd32(b,OFF_STG_LO); printf("[mt] staged list %s: %d words written, staged_addr=%u (exp %d, < 4096)\n", nm, nwords, addr, nwords); if (!b->dry && addr!=(uint32_t)nwords) fprintf(stderr," warn: staged_addr=%u != %d (addressing off)\n", addr, nwords); return 0; } // GO + await a fresh ordered drain. expect_stale: frame_drained is HIGH from a prior scene -> require high->low->high. // After the drain, the feeder returns to C_READY (whole scene drained); records_emitted then reads the tri count. static int go_await_drain(br_t*b, const char*nm, int exp_records, int expect_stale){ wr32(b, OFF_GO, 0x1); if(b->dry){ printf("[mt] scene %s: GO (dry-run)\n", nm); return 0; } int g; if (expect_stale){ g=0; while((rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED)&&g<40000000)g++; // wait the FALL (fresh render) if (rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED){ fprintf(stderr," FAIL %s: frame_drained never fell — STALE drain\n", nm); return -1; } } g=0; while(!(rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED)&&g<40000000)g++; // wait the RISE (drained) if (wait_ready(b)) return -1; // feeder back to C_READY uint32_t fd=rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED, by=rd32(b,OFF_LPDDR_BYTES), recs=rd32(b,OFF_STG_HI); printf("[mt] scene %s: GO -> fresh drain frame_drained=%u, records_emitted=%u (exp %d), FB beats written=%u\n", nm, fd?1:0, recs, exp_records, by); if (fd==0 || by==0){ fprintf(stderr," FAIL %s: empty render (frame_drained=%u beats=%u)\n", nm, fd?1:0, by); return -1; } if (!b->dry && (int)recs!=exp_records) fprintf(stderr," warn %s: records=%u != %d\n", nm, recs, exp_records); return 0; } int main(int argc, char**argv){ unsigned long base=0x40000000UL; int dry=0; char*env=getenv("PS2_BRIDGE_BASE"); if(env) base=strtoul(env,NULL,0); const char *fa="sh3_mtA_tex_lpddr.mem",*fb="sh3_mtB_tex_lpddr.mem",*la="feeder_sh3_mtA.mem",*lb="feeder_sh3_mtB.mem"; int ai=1; for (int i=1;i not stale) rc|=upload_and_fill(&br,"A",texA,crcA); rc|=stream_list(&br,"A",listA,nwA); wr32(&br, OFF_LPDDR_CTRL, 0x1); // arm writer (video_src OFF) rc|=go_await_drain(&br,"A",ntA,0); // (4) scene B: REFILL tex B (cache rearm), stream list B, GO B, high->low->high fresh drain (stale from A) rc|=upload_and_fill(&br,"B",texB,crcB); rc|=stream_list(&br,"B",listB,nwB); rc|=go_await_drain(&br,"B",ntB,1); // (5) enable scanout ONLY after the 2nd fresh drain wr32(&br, OFF_LPDDR_CTRL, 0x1|0x4); // arm + video_src printf("[mt] video_src=1 -> HDMI sources the LPDDR line-buffer scanout (320x381, A+B composited)\n"); if(!dry){ munmap(map,0x2000); close(fd); } printf("[mt] DONE rc=%d %s\n", rc, rc?"(a gate failed above)":"(all gates passed)"); return rc?1:0; }