Conversation
…ecoder - docs: NDS hardware ground truth (from NitroSDK), memory budget analysis, architecture port plan from GBA_MiSTfits, M0-M10 roadmap - rtl: vendored ARM7TDMI (gba_cpu), proc bus + savestate convention, SDRAM/DDR3 controllers, SyncRam/SyncFifo primitives, MiSTer sys/ framework - rtl/nds_vram_map.vhd: full VRAMCNT decode (9 banks, all MST/OFS modes, ARM7 C/D slots), unit-tested against the NitroSDK gx_vramcnt.c truth table - sim: nvc flow (analyze-all smoke gate + 84-check VRAM map testbench) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- rtl/nds_vram.vhd: bank stores + CPU datapath. E-I (144 KB) in BRAM with the renderer port reserved; A-D (512 KB) behind a server channel (future SDRAM guest client). Hardware semantics: overlapping banks OR on read / fan out on write, unmapped reads return 0, dual CPU ports serialized with fairness. - rtl/nds_wram.vhd: 32 KB shared WRAM, all four WRAMCNT modes (melonDS MapSharedWRAM semantics), true dual-port, unmapped flagged to the membus. - sim/tb_vram_torture.vhd: randomized ops from both CPUs vs a byte-accurate behavioral model, behavioral A-D server with random 1-8 cycle latency, live VRAMCNT/WRAMCNT reconfiguration. Green: 50k ops (seed 1) + 500k soak (seed 7) on the k8s nvc pod. - build/remote-sim.sh + sim-pod.yaml: remote nvc runs on the cluster (ghcr.io/nickg/nvc pod, git-archive source streaming, GBA remote-build pattern); sim scripts are POSIX-sh and pod-friendly now. - docs/MEMORY_MAP.md: note on why the DC-style hybrid approach doesn't dodge the DS graphics-memory problem (Robert Peip, July 2026). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… exit test - nds_mainram: dual guest channels (ARM9/ARM7) on one SDRAM port with arbiter, gba_mem_ewram_sdram idiom; randomized torture tb (sequential + concurrent-pair phases against a behavioral controller model). - nds_irq: per-CPU 32-bit IME/IE/IF controller, IF-accumulate with ack-then-OR semantics. - nds_ipc: IPCSYNC + dual 16-deep FIFOs, both proc-bus sides, error/empty/ full flags and edge-detected IRQ pulses. - Timers vendored verbatim from GBA_MiSTer (register-compatible at 0x100). - nds_membus7: ARM7 bus decoder (BIOS / main RAM / shared+private WRAM / IO proc-bus / VRAM C-D), GBA read/write lane rotation, open-bus lastread feedback; accepts a new request on every completing cycle, matching the CPU's same-cycle ena-after-done behavior. - tb_arm7_island: vendored gba_cpu runs sim/tests/arm7_island.hex as BIOS; tb plays ARM9 (IPCSYNC echo, FIFO loopback+1) and snoops the 0x02FFFF00 mailbox. Exit test green: bitmask 0x7F (main RAM rw, shared WRAM, VRAM-C-as-WRAM, timer poll, timer overflow IRQ, SYNC echo, FIFO loop), magic 0xCAFEBABE, PASS on nvc (local + cluster). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- nds_cpu9: fork of the vendored gba_cpu with the ARMv5TE additions — CLZ, QADD/QSUB/QDADD/QDSUB with a sticky Q flag, the SMULxy/SMLAxy/ SMULWy/SMLAWy/SMLALxy DSP multiplies (accumulate beats reuse the MUL decode-swap idiom), BLX in all four forms (ARM imm/reg, Thumb reg/ suffix), LDRD/STRD as two-beat extensions of the single-transfer FSM, PLD/CDP/LDC/STC as nops, v5 LDR/LDM-to-PC interworking, and CP15 via MCR/MRC (ID regs, control, PU region file, TCM base/size, cache-op acceptance, c7,c0,4 wait-for-interrupt wired into the halt path). Exception vectors relocate to 0xFFFF0000 on control bit 13. - Fixed a halt-wake bug inherited from the GBA core: the IRQ injected on unhalt overwrote decode_PC with fetch_PC (one fetch ahead), making the handler return skip the first instruction after wait-for-interrupt. - nds_membus9: ARM9 bus decoder with the TCM overlay (ITCM mirror with load-mode write-only semantics, movable DTCM, data-only, both sized by CP15), boot ROM at 0xFFFF0000, main RAM/WRAM/IO/VRAM behind it. - tb_arm9_island + arm9_island.s (v5TE, linked at 0xFFFF0000, boot PC preset through the savestate bus like the future loader): 11 tests all green — main RAM, CP15 ID, DTCM, ITCM mirror, CLZ, saturation + Q, DSP multiplies incl. SMLALBB, LDRD/STRD with writeback, BLX both ways, LDR-to-PC interworking, high-vector timer IRQ + wait-for-interrupt. PASS on nvc locally and on the cluster (bitmask 0x7FF). - tb_arm9_trace + compare_trace.py + docs/TRACE_DIFF.md: per-instruction trace harness for the melonDS differential exit test (10M instr, zero divergence) — melonDS-side tracer patch is the next step. Still open for M3: I/D caches (write-back + clean/invalidate), 2x ce pacing, armwrestler + melonDS differential runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ergence Closes out M3 (docs/ROADMAP.md): - melonDS 0.9.5 headless tracer (sim/melonds_tracer/): pinned clone + tracer.patch + stub-Platform harness booting raw ARM9 binaries with the RTL-matching initial state. tracer.patch also fixes two genuine melonDS 0.9.5 interpreter bugs the differential flagged (EORS/LSR dispatch-table typo — still in melonDS master; ADC/SBC/RSC V-flag OR-vs-XOR on double overflow). - Differential workloads: arm9_diff.s (island tests at 0x02000000) and gen_arm9_torture.py (seeded random v5TE chunks, armwrestler-style, with optional cache-enabled mode). tb_arm9_trace LOADADDR generic boots main-RAM workloads. - M3 exit test PASSED: 10,000,000 retired instructions RTL vs melonDS, zero divergence (plus ~20 30k-instruction seeds, caches off and on). Fixed one RTL bug it found: ROR-by-register with a multiple-of-32 amount must set C := Rm[31], and only Rs[7:0] participates (the donor gba_cpu has the same bug — left untouched here). - nds_cache9.vhd: ARM946E-S caches — 8KB I / 4KB write-back D, 4-way, 32B lines, round-robin, read-allocate, write-miss no-allocate, dirty victim write-back, full maintenance op set (inv I/D all/line/index, clean, clean+inv) driven from the CP15 MCR c7 path with proper CPU stalling. PU cachability lookup exported from nds_cpu9. Self-checking exit test sim/run_arm9_cache.sh (write-back laziness, dirty-drop on invalidate, round-robin eviction, I-cache staleness, index-op flush). - 2x ce pacing: island runs the CPU at full rate with timers/IRQ fabric on ce/2 (the 66/33 MHz arrangement nds_top will use); membus9 gains io_ce_next and aligns the 1-cycle IO pulses onto active peripheral cycles so slow-domain register writes are never dropped. Full regression green on the cluster: vram_map, mainram, vram_torture, arm7_island, arm9_island, arm9_cache; arm9 trace diff re-verified after every RTL change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- nds_loader: parses the .nds header from the staged card image, copies both sections to main RAM, reports entry points for the boot-PC preset - nds_syscnt: EXMEMCNT/EXMEMSTAT + WRAMCNT/WRAMSTAT shared registers - nds_membus7: io_ce_next alignment port (mirror of the membus9 change) - gba_cpu: register-shifted ROR by nonzero multiples of 32 now behaves as ROR #32 (only Rs[7:0] participate), per ARM ARM - exit test: SDK-shaped boot pair (crt0 + PXI_InitFifo from the decomp) packed by build_nds_dual.sh, loaded by nds_loader, IPCSYNC echo handshake + FIFO ping-pong + WRAM/EXMEM handoff — PASS, both islands still green Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- nds_drawer_text: gba_drawer_mode0 fork - 256px lines, 512 KB BG space (full byte-address bases from the orchestrator, 19-bit wrap), extended palettes in 256-color mode (32 KB ext-pal port, slot from orchestrator) - nds_drawer_affine: gba_drawer_mode2 fork - same addressing; fixed a donor timing assumption where pixels were silently dropped when the palette FSM was busy (GBA's 4-phase service cadence hid it): color byte is now captured and handed off with a stall, and busy holds until the last pixel clears the palette pipe (both drawers) - tb_gpu_bg + gen_gpu_bg.py: line-level differential against a Python golden model written independently from GBATEK semantics - 20 cases covering 4/8bpp, all 4 ext-pal slots, all screen sizes, block-crossing scrolls, 512K wrap, mosaic, affine wrap on/off with negative deltas - PASS pixel-exact (vectors are generated, not checked in) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alettes nds_drawer_extended: NDS extended BG modes (8bpp tiles w/ 16-bit map entries, 256-color bitmap, direct-color bitmap) on the affine pipeline; 8 new golden cases in tb_gpu_bg (28 total, green). nds_drawer_obj: fork of gba_drawer_obj with 256px line, 256 KB OBJ space, 1D tile-mapping boundary (DISPCNT 21:20), no even-tile masking for 8bpp 2D (melonDS), OBJ ext palettes (DISPCNT.31), and bitmap sprites (mode 3) in 1D/2D-narrow/2D-wide layouts incl. affine; settings plane widened to 8 bits ([7:4] bitmap alpha, [3] bitmap, [2] semi-transparent, [1:0] prio). Fixed during bring-up: alpha nibble leaked the palette field for tile sprites. tb_gpu_obj + gen_gpu_obj.py: independent golden model (GBATEK/melonDS semantics, donor merge behavior incl. transparent-prio-update), 24 cases covering flips, boundaries, extpal, clipping, y-wrap, priority merge, OBJ window, alpha=0 and reserved-mode skips — all green. Palette/ext-pal ports are served as plain 1-cycle registered reads (no valid handshake). analyze-all now covers all four drawers + both GPU TBs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four read-only renderer channels for engine A: BG (512 KB main-BG space), OBJ (256 KB main-OBJ space), BG ext palette (32 KB / 4 slots from E MST=4, F/G MST=4 by OFS.0) and OBJ ext palette (8 KB from F/G MST=5). BG/OBJ reuse the unit-tested nds_vram_map decoder at the canonical region addresses; the ext-palette roles (no CPU mapping) get their own decode. Round-robin arbiter, one op in flight; E..I hits read BRAM port B, A..D hits go through the new read-only rsrv_* channel (the future SDRAM line-cache client; behavioral in sim). Multi-hit ORs, unmapped reads 0 — same semantics as the CPU path. v1 is correctness-first; prefetch/ parallelism deferred to hardware bring-up. tb_vram_ls + gen_vram_ls.py: independent GBATEK mapping golden, 12 VRAMCNT configs x 880 reads incl. window boundaries, overlaps (OR), holes, LCDC/disabled configs, engine-B roles that must not hit, CPU-port differential reads for BG/OBJ, and simultaneous four-channel batches for the arbiter. All green; vram_map (84 checks) and vram_torture unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fork of gba_drawer_merge: 256x192 geometry, OBJ input widened to 24 bits (settings from nds_drawer_obj), and NDS bitmap sprites force alpha blending with per-pixel coefficients EVA=alpha+1 / EVB=16-EVA (BLDALPHA ignored), joining the donor's semi-transparent forced-blend path. Window select, priority resolution (OBJ wins ties), first/second target rules, the effect>1-on-OBJ cancel rule and saturating blend math unchanged. tb_gpu_merge + gen_gpu_merge.py: independent golden model, 13 cases x 256 pixels - plain priority incl. ties, alpha/brighten/darken with random target masks, forced semi/bitmap blending, WIN0/WIN1 overlap with X/Y wraparound, OBJ window, effects-disabled windows, BD-as-second-target, DISPCNT layer disables, full chaos case - all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The GBA-donor fixed cadence (data exactly one cycle after a valid='0' latch slot) cannot ride the variable-latency VRAM line server. All four drawers' char/map/bitmap VRAM ports now use the repo-standard handshake: one request in flight, req one-cycle pulse, address held until the done pulse (data valid that cycle). Palette/ext-pal/OAM ports stay fixed- latency local BRAMs (ext-pal slots get shadowed into BRAM at vblank by the orchestrator - GBATEK: CPU can only write them via LCDC remaps). - text: cache-hit check moved to request time (a stale in-flight response would be misassociated); live cache check kept for the palette-stall window - affine/extended: out-of-range skip decided at request time; request address registered (the byteaddr mux drifts with the FSM state) - obj: NEXTADDR issues tile fetches or skips/reuses without fetching (address still updated on reuse - its low bits pick the byte lane), new PIXELWAIT waits for done, AFF_SUM keeps the affine summing cycle; eval always reads the done-registered word; missing req default-clear found via req/done trace (server free-ran one word behind) - per-line OBJ budget raised (8191/6400) until the line-server latency story settles; melonDS does not model the budget - tb_gpu_bg/tb_gpu_obj: per-port servers with randomized 1..8 cycle latency - a harsher contract than the old cadence. 28 + 24 cases green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gba_gpu_drawer role for NDS engine A: register file, mode routing (0-5), the four BG drawers + OBJ + merge, per-BG line buffers, OBJ parity double-buffer, and the memory plumbing onto the VRAM line server (per-BG req/done clients round-robin onto the BG channel, OBJ passes through, ext-pal shadow BRAMs streamed from the ext-pal channels at vblank). Line pacing is functional; real dot timing is M5 part 6 (nds_gpu_timing). - reg_nds_display: engine-A register map 0x000..0x05F, pReg_gba_display pattern (engine B instantiates the same layout at +0x1000 later); DISPSTAT/VCOUNT belong to timing, POWCNT/master brightness to compose - nds_gpu2d: OBJ renders one line ahead into the parity buffer, donor style; affine refs reload on vblank and CPU write, step by dmx/dmy per line; 3D-as-BG0 renders transparent (stub) - nds_vram: renderer arbiter latches request pulses in rpend — a one-cycle req landing while the FSM served another channel was lost, deadlocking every drawer sharing the channel (all four text drawers frozen on line 0) - nds_drawer_obj: busy output so the orchestrator can time the parity buffer swap - tb_gpu2d (line-level, 3 frames) and tb_gpu2d_frame (full 256x192 frames through the real line server, 4 cases incl. affine, extended, ext-pal) against gen_gpu2d.py / gen_gpu2d_frame.py goldens — both pixel-exact on the cluster; the diff hunt also fixed golden-model bugs (affine H-mosaic, sign extension, ext-pal masking), not just RTL Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The LCD timing module (gba_gpu_timing role), constants and event order per melonDS GPU.cpp: 355x6-cycle scanlines, 263 lines, hblank/render point at cycle 1584 (48-cycle lead + 256 dots), DISPSTAT/VCOUNT with one register set per CPU (9-bit V-match with bit 7 as MSB; VCOUNT writes are delayed to the next scanline, either CPU may write, last wins), per-CPU IRQ pulses, and the nds_gpu2d cadence: hblank_trigger -> line_trigger -> drawline on visible lines, drawObj one line ahead (line 262 pre-renders OBJ line 0), refpoint_update at line starts 1..191, vblank_trigger at 192 (affine ref reload + ext-pal shadow refill). The ce input paces the dot clock so the module can sit in a faster GPU fabric domain; timing free-runs and nds_gpu2d drops a drawline that lands while the previous line is still rendering. Free-running the cadence exposed the fetch budget: at the planned topology (GPU fabric at 3x the dot clock) the affine drawers' two line-server round-trips per pixel took just over a line time - every other line dropped - and the extended bitmaps re-fetched each word per pixel. Last-word caches on the map and char/pixel fetch streams (the text drawer's idiom; invalidated per drawline) bring every case inside the budget. - tb_gpu_timing: an independent arithmetic model asserts every DUT output on every clock edge over 4 frames - cadence, flag windows, both CPU buses, unreachable 9-bit matches, delayed VCOUNT writes, DISPSTAT write masks, per-frame pulse counts - tb_gpu2d_timed: nds_gpu_timing free-runs nds_gpu2d + nds_vram at CE_DIV=3; the 4 golden frames stay pixel-exact with 0 dropped lines (the drop monitor doubles as the line-budget regression) - regression green on the cluster: tb_gpu_bg 28 cases (caches are bit-identical under randomized 1..8-cycle latencies), line TB, frame TB (24% less sim time from the fetch reduction), timing TB, timed TB Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vs melonDS nds_top grows from the M0 scaffold into the full system: the proven M4 dual-boot fabric (nds_cpu9 + gba_cpu, membuses, shared/private WRAM, main RAM, IPC, IRQ, timers, syscnt) with boot sequencing as a synthesizable FSM (loader -> PC preset via savestate buses -> CPU release), plus the engine-A render path: nds_vram, nds_gpu_timing, nds_gpu2d, GPU vblank/hblank/vcount IRQs, and wired KEYINPUT/EXTKEYIN. The GPU dot cadence is ce-paced at 1-of-GPU_CE_DIV (default 3, the planned 100.5/33.5 MHz topology) — at 1 the v1 line server drops ~110 lines/frame on an affine scene, at 3 it drops none. Plumbing that landed with it: membus9 decodes 0x05/0x07 into gpu2d's palette/OAM write ports (reads are a documented zero-return gap), syscnt gains the VRAMCNT_A..I register file + ARM7 VRAMSTAT. M5 exit harness: tb_top_frame boots a .nds through nds_top and dumps every rendered frame; melonds_fbdump boots the same image in melonDS 0.9.5 with identical HLE-loader semantics; compare_fb.py diffs pixel-exact (RTL 555 expanded with melonDS's own 5->6->8 rules). The SDK-shaped sample (arm9_2d.s: text BG0 with transparent holes, affine BG3, 3 OBJs incl. edge clip, CPU-only loads, SDK crt0) renders pixel-perfect, 0 dropped lines. Sample images must follow the melonDS PU/POWCNT rules in its header. Regressions: run_analyze_all OK, tb_dual_boot PASS.
…erfect NDS color special effects operate in 6-bit-per-channel space, not the GBA donor's 5-bit: 555 palette colors expand c6 = c5 << 1 and the effects apply melonDS's hardware-verified rounding (alpha min(63, (c1*EVA + c2*EVB + 8) >> 4); whiter bias +8; blacker bias +7). nds_drawer_merge now blends in 666 and outputs 18-bit BGR666 (the NDS LCD format), carried through nds_gpu2d and nds_top; the golden models (gen_gpu_merge, gen_gpu2d, gen_gpu2d_frame), the merge/gpu2d/frame TBs and compare_fb.py follow. Measured before the fix: 35704/49152 pixels off by 1-2 666-LSBs vs melonDS on a blended scene; after: pixel-perfect. New heavy sample (build_nds_2dh.sh): BG ext palettes written via LCDC then remapped E->MST4/F->MST5, per-tile ext palettes on a 256-color BG1, an OBJ ext-pal sprite, alpha blending BG0+OBJ over BG1/BG3/backdrop with EVA/EVB 9/7, and a semi-transparent OBJ — pixel-perfect vs melonDS through nds_top (0 dropped lines in collected frames; the ~8 warm-up drops during CPU VRAM setup traffic are the known line-server arbitration cost, M9 prefetch item). Regressions: run_gpu_merge, run_gpu2d, run_gpu2d_frame, run_gpu2d_timed (0 drops), analyze-all — all PASS.
Third frame-diff sample (build_nds_2dw.sh): the ext-pal/blending scene with all three windows and mosaic layered on. WIN0 (BG0+BG1+OBJ, effects on), WIN1 (BG1+BG3, effects off - gates the alpha blend), an OBJ window from a mode-2 sprite (BG3 only), WINOUT hiding BG1; MOSAIC 0x3323 with BG h=3 v=2 on the text BG0 and OBJ h=v=3 on a sprite. Pixel-perfect through nds_top on the first run - the window/mosaic paths the merge and drawer unit tests covered RTL-vs-golden hold up against melonDS in the full system. Scene notes: OBJ mosaic keeps h==v because melonDS 0.9.5 indexes the sprite X-mosaic table with the V size (fixed in later melonDS); the affine BG stays mosaic-free (our affine mosaic is a documented TODO - live-ref sampling, no melonDS parity claim yet).
… fix sdk2d (sim/tests/sdk2d/): the first real-toolchain sample — C scene compiled with devkitARM against libnds 2.0.2 headers, packed by ndstool 2.3.1, ARM7 at the authentic 0x037F8000. Custom crt0 stands in for libnds/calico's (whose kernel needs the ARM7 BIOS IRQ trampoline, DMA and engine B — the M6+ shopping list for stock examples). Scene: text + ext-pal 256c + affine BGs, blending, WIN0/OBJWIN, mosaic, 5 OBJs. Pixel-perfect vs melonDS through nds_top, 0 dropped lines. The run caught a real drawer bug the hand-rolled scenes missed by luck: OBJ H-mosaic repeated relative to the sprite's left edge (donor behavior), but hardware/melonDS restart on the screen-aligned grid (x mod (size+1) = 0), at sprite changes, and after transparency holes — earlier scenes' mosaic sprites sat at grid-aligned x. nds_drawer_obj now uses a screen-grid restart table + sprite-first marker + opaque-run continuity; gen_gpu_obj models melonDS's post-pass and adds unaligned / holed mosaic cases (24/25) that fail under the old rule. Regressions: gpu_obj (26 cases), gpu2d, gpu2d_frame, analyze-all, and the 2dw windows+mosaic scene vs melonDS — all PASS.
nds_vram grows the four engine-B renderer channels (sub-BG 128 KB from C MST=4 / H MST=1 / I MST=1@0x8000, sub-OBJ 128 KB from D MST=4 / I MST=2, BG ext pal B from H MST=2, OBJ ext pal B from I MST=3), reusing the nds_vram_map decode at the 0x620000/0x660000 region addresses; the round-robin arbiter widens to 8 channels. The 8-channel tb_vram_ls (16 configs, 2314 reads, engine A+B windows, overlaps, CPU differentials on all four flat spaces, concurrent 8-channel batches) caught a latent arbiter bug: rpend re-latched from the still-held req of the request being served, re-dispatching the old address after done; when the channel's next request (different address) arrived while that straggler was in flight, the straggler's done pulse answered it with the previous read's data. The drawers dodged it (their requests are either back-to-back, which re-serves the new address correctly, or widely spaced) - interleaved CPU reads in the TB hit the window. rpend now never re-latches for the channel being served or just completed; back-to-back requests still re-latch the cycle after done. Regressions: vram_ls, gpu_obj, gpu2d, gpu2d_frame, gpu2d_timed (pod, 0 drops), analyze-all, sdk2d scene vs melonDS (pod, pixel-perfect).
nds_gpu2d gains an is_engine_b generic gating the A-only capabilities (3D-as-BG0, DISPCNT char/screen-base blocks, the 1D-bitmap OBJ boundary bit); the register map itself is shared. nds_top instantiates engine B behind an address window: the io bus reaches it with bit 12 stripped so 0x1000-0x106C decodes on the common map, and with the address forced unmatchable outside the window so both engines' wired-ors stay exclusive. membus9 passes the palette/OAM 2 KB mirrors through whole (word index bit 8 = engine half); nds_top splits them onto the two instances. Engine B reads VRAM through the part-1 sub channels (drawer addresses wrap at the 128 KB spaces) and shares the timing cadence. tb_top_frame collects and dumps both pixel streams (DUMPFILE_B). Validation beyond analyze/elaborate + engine-A regressions comes with part 3: the dual-screen sample vs melonDS on both screens.
sdk2d becomes a dual-screen scene: one engine-parametrized C routine programs A and B through their base offsets (regs +0x1000, palettes/OAM +0x400, BG/OBJ VRAM via banks A/B vs C/D, ext-pal staging via LCDC E/F vs H/I) with per-engine pattern variation - opposite affine rotation, shifted sprites/window, distinct palette ramps. melonds_fbdump grows an optional bottom-screen dump; tb_top_frame collects and dumps both pixel streams (DUMPFILE_B). Both screens compare pixel-perfect through nds_top, 0 dropped lines with both engines sharing the line server. Scene rule learned the hard way: no OBJ V-mosaic in frame-diff scenes - melonDS's OBJ mosaic-Y counter free-runs across frames (the BG one resets each vblank), so its block anchor depends on which scanline the MOSAIC write lands on, which is not reproducible across emulator/RTL CPU timing. BG V-mosaic and H-mosaic stay covered.
POWCNT1 (0x04000304) lands in nds_syscnt: readable, write-masked to 0x820F, driving 2D-A/2D-B power and the LCD swap. nds_top routes the engine streams to top/bottom screens by the swap bit, shows raw white for a powered-off engine B (melonDS-documented quirk: engine A keeps rendering with its bit clear), and gates palette/OAM writes by the owning engine's power. MASTER_BRIGHT (0x6C, per engine) applies in nds_gpu2d's output stage in 18-bit space with melonDS's exact asymmetric rounding (up: c += ((63-c)*f)/16, bias 0; down: c -= (c*f+15)/16, bias 0xF; factor clamped 16), after the forced-blank white (which composites like a normal line) and skipped for display mode 0 (white). Engine A's VRAM/FIFO display modes stay unimplemented. Scene: POWCNT swap CLEAR (engine B on the top screen - the routing is load-bearing), MASTER_BRIGHT up 4/16 on A / down 6/16 on B. Both screens pixel-perfect vs melonDS; the A backdrop brightening to exactly 0x3C888 confirmed the bias-0 up formula.
nds_dma9: 4 channels, 0x040000B0-EF incl. the FILL words, semantics per DualSOUP dma.c + GBATEK. Enable edge latches src/dst; the word count latches lazily when the remaining count hits 0 so repeat reloads per trigger (and dst ctrl 3 re-latches the address then); src ctrl 3 acts as inc+reload. Start timings 0/1/2 (immediate, vblank, hblank on visible lines via the gpu2d cadence pulses); card/GX come with their subsystems. Transfers master the ARM9 membus: dma_on pauses the CPU, CPU_bus_idle gates the grant, and membus9 bypasses the ITCM/DTCM windows while dma_bus is up (DMA cannot see the TCMs). 16-bit reads take the rotated lane, writes replicate; IRQs 8-11 wired. Timing is functional (one read + one write handshake per unit); the FSM is shaped first-pair/steady-pair so the DualSOUP dma.txt measurements can be dialed in at M9 without restructuring. Scene: sdk2d loads tiles via dmaCopyWords(3) and the OBJ palette via dmaCopyHalfWords(2) from uncached main RAM, BG1 tile 3 via dmaFillWords (FILL-word source through the IO bus), plus a vblank-repeat DMA0 with fixed src + dst-reload recoloring BG0 subpal 3 every frame — a dead vblank trigger or repeat path shows immediately. Both screens pixel-perfect vs melonDS. melonds_fbdump grows --direct (melonDS cart + SetupDirectBoot boot) ahead of the stock-libnds work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nds_bios7 (generated from sim/tests/hle_bios7/bios7.s, 93 words)
serves 0x0 through membus7's bios port: GBATEK NDS7 IRQ dispatch
(save {r0-r3,r12,lr} on sp_irq, call [0x0380FFFC] with lr at the
restore, pop + subs pc,lr,SplashDev88#4 — calico's __irq_handler consumes the
six stacked words itself on a context switch, so the frame layout is
load-bearing) plus the exact SWI set a stock libnds-2.x ARM7 links
(surveyed from hello_world + libcalico_ds7.a): 0x03 WaitByLoop,
0x06 Halt, 0x07 Sleep(=Halt), 0x08 SoundBias(nop), 0x09 Div,
0x0E GetCRC16, 0x0F IsDebugger(0), 0x1F CustomHalt. The dispatcher
decodes Thumb imm8 and ARM imm24 callers (calico wrappers are Thumb,
its bootstub + libnds7 Div calls are ARM). Unknown SWIs park loudly.
svcHalt goes through HALTCNT (0x04000301, new in nds_syscnt, halt7
pulse -> gba_cpu new_halt; wake = IE&IF via the existing unhalt).
POSTFLG7/9 added as readable sticky bits while in there. The ARM9
needs no BIOS: calico ds9 installs its own ITCM vectors.
Verified: sdk2d's ARM7 now computes svcGetCRC16 over a fixed pattern,
folds in svcIsDebugger, and posts the proof word from a svcHalt +
vblank-IRQ loop dispatched through the BIOS vector — SWIs issued from
both ARM and Thumb states. The ARM9 blocks on the exact word, then
recolors BG0 subpal 1 on both engines. Both screens pixel-perfect vs
melonDS (FreeBIOS). melonds_fbdump grows a TRACE9=<path> env knob
(ARM9 instruction trace via the tracer.patch hook) for boot debug.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… c13
Stock libnds-2.x (calico) ROMs crashed melonDS 0.9.5 at boot: calico's
ARM9 IRQ exit "ldmia sp!, {r0-r3,r12,pc}^" hit a 0.9.5 interpreter bug
where A_LDM does the SPSR-restore mode switch (JumpTo) before the base
writeback, landing the popped-past sp in the interrupted thread's SP
bank (traced: _pxiInit popped a corrupted frame and jumped to 8).
Commercial games return from IRQs via the BIOS subs pc,lr,SplashDev88#4, which is
why 0.9.5 got away with it. Backported the upstream fix (read pc,
write back, jump last) plus CP15 c13,c0,1 Trace Process ID (calico
stashes the active irq mask there across handler calls; unknown-reads
as 0 silently broke thread wakeups). tracer.patch now carries both -
hello_world (stock libnds 2.x) boots and renders console text under
melonds_fbdump --direct.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the headless tracer/fbdump harness to the melonDS 1.x instance API (melonDS::NDS object, FileHandle Platform layer, heap-allocated console - the 1.1 NDS object overflows the stack as a local). tracer.patch shrinks 8 files -> 3: upstream 1.1 already has the LDM^ writeback ordering, CP15 c13 Trace Process ID, byte-wide IPCSYNC, and the ADC/SBC/RSC V-flag fix (found here first on 0.9.5). Still carried: the trace hooks and the ARM_InstrTable EORS row typo (EORS ...,LSR #odd runs as ROR), which is still broken upstream as of 1.1. Validated by regenerating the hello2d (12 frames) and hello_world (20 frames) direct-boot references: frames 1+ pixel-identical to the 0.9.5 dumps on both engines; frame 0 shifts by 5 scanlines (1.1 lands console init slightly earlier inside the transient boot frame). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same format and pipeline conventions as the ARM9 hook; used for the dual-CPU differential trace that found the M7 direct-boot blockers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Direct-boot env synthesis in nds_loader (header copy, chip IDs, boot
flags, user-settings block) plus the four blockers that kept stock
calico/libnds-2.x ROMs (hello2d, hello_world) from booting, each found
with the new full-system dual-CPU trace diff vs melonDS 1.1:
- membus7/membus9: unclaimed NDS IO reads return 0, not GBA open bus.
calico probes SCFG 0x04000000+ for NTR/TWL detection; the prefetch
echo made it take the DSi init path.
- nds_top: pulse the firmware-left preset (WRAMCNT=3/POSTFLG/POWCNT)
WITH the CPU reset release - nds_syscnt resets on resetCpu, which
swallowed the ld_done-time pulse. WRAMCNT=0 made the crt0 section
copy to 0x037F8000 mirror into WRAM7 over the ARM7 stack.
- nds_bios9 (generated from sim/tests/hle_bios9/bios9.s): ARM9 HLE
BIOS at 0xFFFF0000. calico's ds9 bootstub calls svc 0x0f0000
(IsDebugger) before installing its own vectors; the zeroed BROM sent
the ARM9 NOP-sliding through the whole address space.
- nds_cpu9 + gba_cpu: implement ldm^ with pc (exception return) via a
DATARW_BLOCKSWITCH state: CPSR := SPSR + bank swap one cycle AFTER
the base writeback, so "ldmeqfd sp!, {r0-r3,r12,pc}^" leaves the
old mode's sp updated (the ordering melonDS 0.9.5 also got wrong).
Both vendored cores previously flagged error_cpu on it.
tb_top_frame grows TRACEFILE/TRACEFILE7/MAXINSTR generics writing
TRACE_DIFF-format retired-instruction logs from the new nds_top
dbg_export9/dbg_export7 ports.
With these the ARM9 runs instruction-identical to melonDS deep into
calico's scheduler and the ARM7 completes crt0; boot then blocks on
the (unimplemented) SPI firmware flash - next part. M6 dual-scene
sample re-verified pixel-perfect on both engines after the CPU edits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-settings CRC nds_spi (0x040001C0) modeled on melonDS 1.1 SPI.cpp: PMIC regs w/ masks, firmware flash 03/05/04/06 (0A bus-side only), TSC conversions matching headless melonDS (no touch: X/Y=0, mic=0x800, else 0xFFF). Byte timing 8*(8<<baud) clk, IRQ7 bit 23, hold/chipselect-release semantics. Firmware image: melonds_fwdump now mirrors FirmwareMem::Reset() - identity touch calibration patched into user data + UpdateChecksums - so the dumped image is byte-exact vs what melonDS serves at runtime (user-settings CRC 0xD739, confirmed in both ARM7 traces). nds_top serves it to nds_spi via new fw_addr/fw_data port (hex array in tb_top_frame; HPS-staged later). remote-sim.sh: ARTIFACTS="..." fetches files from the pod into simout/ before teardown (traces were previously lost with the pod). Verified (hello_world, direct boot, vs melonDS 1.1): ARM7 passes the settings CRC and idles in the same scheduler loops with identical iteration counts; top screen pixel-perfect for 3 frames. Bottom screen still blank: NEXT BLOCKER - ARM9 livelocks re-executing the bx lr at 0x01FF84CC after calico's IRQ-exit LDM^ (bx branches to itself instead of lr), so consoleInit/engine-B setup never runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The DATARW_BLOCKSWITCH mode-switch/bank-swap block sat inside the
'elsif (execute_now = 1)' arm of the execute process, but in the
BLOCKSWITCH cycle execute_stall=1 routes through the stall arm - the
'or execute_RW_State = DATARW_BLOCKSWITCH' half of its guard was dead
code. CPSR/mode still switched (separate process via
execute_msr_setvalue_ena), so an IRQ exit 'ldmia sp!,{...,pc}^' landed
in system mode with the IRQ-banked sp/lr still live, and the save half
clobbered the system-bank storage with them. Symptom: calico's idle
thread 'bx lr' branches to itself forever (lr = interrupted pc), the
ARM9 livelocks at 0x01FF84CC, consoleInit/engine-B setup never runs.
Hoisted the swap block into common scope in nds_cpu9 AND gba_cpu (the
ARM7 path was equally broken - its trace diff just never reached an
ldm^ exit).
Localized with a new per-cycle pipeline dump in tb_arm9_trace
(DBG_T0/DBG_T1 generics, external names -> pipe_debug.log). Regression:
sim/tests/ldm_bx_irq - 50 timer IRQs into a calico-shaped msr/mov/bx
idle loop, exits via conditional ldm^; loop retire counts now balanced
(1228/1227/1227, previously 100/159/1961) and both island suites pass.
Full-system (hello_world direct boot): ARM7 trace parity vs melonDS
1.1 up to benign IPCSYNC poll skew; top screen pixel-perfect; engine B
now renders (black) instead of stuck forced-blank white - console text
still missing, next investigation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POSTED_WRITES gates the queue the way NDS.qsf gates every other area tradeoff, so the RTL lives in the tree and can be A/B'd instead of sitting on a branch or being deleted. The A/B is the useful part, and it inverted my assumption. Five fits: stage 2 only (ac85027) 41,556 ALMs SUCCESSFUL 9 cyc/unit stage 3, POSTED_WRITES=1 4,197 / 4,200 / 4,204 failed 2 cyc/unit stage 3, POSTED_WRITES=0 4,214 LABs failed 7 cyc/unit Deleting the entire posted queue does NOT get back under 4,191. So the queue was never the expensive half - the combinational single-cycle fast-lane request in nds_dma9 is, and that is unconditional because it is precisely what makes an access one cycle. Two of my three area attempts were aimed at the wrong thing. Consequence worth stating plainly: there is no configuration that both fits and passes [04-02]. Passing it requires the single-cycle request, and that is what does not fit. The ~10-25 LABs have to come from somewhere with a named cost, not from this module. Stage 2 alone fits and has an RBF (build/artifacts-stage2/), 20 -> 9 cycles per unit. Its worst-case slack is -3.809 on the 67 MHz island, but the top paths are nds_membus9 target->creq_* and nds_cpu9 shiftervalue - the known 2:1 barrel-shifter family, at a different seed than the -2.259 the ticket recorded with seed 23. Nothing new is on that list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ROM asserted an exact +2 step, so on any build without single-cycle DMA accesses - including the stage-2 RBF that actually fits - it would print FAIL for a core working perfectly. That is worse than useless: it sends someone hunting a corruption bug that is not there. These are two questions. A UNIFORM step means the datapath is sound: no posted write lost, no merge that dropped a byte lane, no renderer stealing a word. That is the pass/fail, and it holds whatever the DMA costs. The step's VALUE is the cadence, reported next to it - 2 is what silicon does and what [04-02] wants, larger is slower but not broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nds_drawer_obj divided by x_div, a signal of range 1 to 2, in three places. A signal divisor infers a general lpm_divide even when it only ever holds 1 or 2, so Quartus built real divider fabric for what is a shift. Hoist the HICOLOR test one level and write the two constant forms directly; x_div and OAMfetch_x_div then have no readers and go away. nds_vram used `mod WQ_DEPTH` for the posted-write queue ring wraps. WQ_DEPTH is 3, not a power of two, so those are also general dividers. Explicit compare-and-reset instead. MEASURED across the fit sequence: lpm_divide instances 75 (artifacts-perf), 72 (pc5, pc8), 0 (pc9 onward). ALMs 41,118 -> 40,984 over that step. The divider count is the honest part of that number; 134 ALMs is near the fitter's own seed-to-seed spread, so do not read it as a precise area saving. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 67 MHz domain was missing by 2.4 ns with -575 ns of total negative slack. Four cuts, each measured on its own fit (artifacts-codex-pc13..pc16-s0): * PC writes leave the generic writeback mux. pcwrite_fetch used to be "any writeback to r15", which let UNPREDICTABLE r15 destinations on multiply and base-writeback paths fan into the fetch address. It is now two named states - alu_pcwrite_wait for ALU results and DATARW_PCWRITE for load/block-load results - and pcwrite_Addr reads execute_RW_dataRead, an existing timing boundary, instead of the live ALU result. Costs one beat on ALU-writes-PC and LDR/LDM-to-PC. * DATARW_ADDRWAIT registers register-offset LDR/STR addresses before issue, so the five-level barrel shift and the address adder no longer sit in front of the membus decode. Immediate and base-only forms keep their old timing. (pc16) * SBC/SUB share one 34-bit chain. The separate 32-bit `alu_op1 >= alu_op2` comparator behind the shifter is gone; bit 33 is ARM's no-borrow carry. (pc15) * The DMA9->membus9 bridge registers the whole request payload rather than just narrowing the enable pulse. The DMA fast IO and posted-VRAM lanes bypass this bridge, so the exact 2-cycle/unit cadence is unaffected - reconfirmed by sim/tests/dmaprio after the change. Two preserve attributes hold decode_Rn_op1 and execute_RW_dataRead against physical retiming, which otherwise rebuilds the full path. Preserving the whole register bank was counterproductive and preserving decode_RM_op2 cost 0.844 ns (pc11). Setup slack at 67.028 MHz: -2.376 (pc13), -1.444 (pc14), -1.228 (pc15), -1.008 (pc16). TNS -575 -> -26.6. STILL NOT CLOSED: Fmax is ~62.8 MHz. The residual is the barrel shifter itself feeding membus9|state - candidate cut 3 in docs/TICKET-arm9-2to1-timing.md, which is untouched here. Green on the current tree: arm9_island 12/12 (its LDR now uses a shifted register offset on purpose), arm9_cache 8/8, shifter_equiv 294,912 cases, mosaic_equiv, vram_torture, mainram, vram_ls, and the full dmaprio ROM at 2 cycles/unit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One setup cut: membus9's VRAM outputs to DMA9. The two masters share a muxed port but are mutually exclusive under dma_bus_on, which TimeQuest cannot infer, so it reports a 67 -> 33 MHz path that cannot exist. Only that source/ destination pair is cut; the real CPU-island <-> DMA register-bus crossings stay timed. Four hold cuts on related-clock payloads whose destination enables are generated by edge detectors or by clkMemIndex phase gating, and so cannot fire on the coincident edge being checked. Setup checks are kept in every case. CAVEAT, recorded because the next person needs it: these are the reason pc14's -0.395 ns hold failure is absent from pc15 onward. That failure was removed by assertion, not by RTL, and it is on the main-RAM path - if the clkMemIndex argument above is wrong the symptom is corrupted reads on silicon, not a failing testbench. The reasoning has not been checked by anyone but its author and has not been confirmed on hardware. Re-derive it before trusting an RBF built with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A hold failure showed up in pc14 with no endpoint detail to act on - the default STA report carries only the per-clock summary. Add report_timing -hold to the pod's TCL and NDS.paths_hold.rpt to the retrieved artifact list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Checkpoint 4 read the 32 KB mirror through a plain register offset, which does not touch the barrel shifter. Use LDR [r0, r5, LSL SplashDev88#2] to reach the same 0x7FFC word, so the check exercises the addressing path DATARW_ADDRWAIT cuts. Still 12/12. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NDS_ANALOG_AUDIO_ONLY drops the I2S and S/PDIF serializers; NDS_NO_AUDIO_FILTER is extended to bypass the two 40-bit DC blockers as well as the IIR low-pass. Measured cost of the removed serializers: 71 combinational ALUTs, 102 registers. TWO CAVEATS, both against this change and both recorded rather than silently carried: 1. The NDS.qsf rationale says I2S and S/PDIF "have no sink here". That is wrong for S/PDIF. sys_top.v:1553 drives AUDIO_SPDIF - the optical output on the analog I/O board this profile requires - from this signal, and :1548 uses it for SDCD_SPDIF when mcp_en. Forcing spdif to 0 therefore removes working optical audio, it does not remove dead logic. Any core built with this macro has no TOSLINK output. 2. 71 ALUTs is roughly 7 LABs. Commit 7e87734 established, from five seeds on a fixed design, that this image's fitter spread is 4,192..4,215 LABs - so a ~7 LAB saving is below the level at which a single fit can measure anything. The same applies to the DC-blocker bypass, which additionally changes analog output post-processing on a path nds_sound is only asserted, not measured, to leave bias-free. Committed separately so that `git revert` on this one commit restores both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…thod Replaces the two GPL framework modules NDS.sv instantiated for video scaling and HPS transport, and relicenses three core-authored files that carried a GPL-2.0 SPDX tag by inheritance (pll_video.v, nds_audio_ddr3.sv, hps_lw_bridge.sv). METHOD CORRECTION, which is the substance of this commit. The video_freak replacement was originally shipped as clash/rtl/nds_clash_video_freak.sv, whose own header described it as "a straight register-for-register port of" the GPL original. A port is a derivative work: copyright covers the expression, and reproducing a module register by register preserves that expression whatever language it is re-expressed in and whatever the module is renamed to. Shipping it did not remove the GPL dependency, it renamed it. The genuinely clean artifact was already in the tree, as the test oracle - video_freak_reference.sv, written fresh from the observed timing contract without the GPL source, sharing zero lines with it. The two roles are now the right way round: clash/rtl/nds_video_freak.sv ships (independently written) clash/tests/rtl/video_freak_port_oracle.sv test-only (the derivative port) Equivalence is unchanged and re-verified after the swap: 2,500,000 cycles, 5 frames, config changes including non-zero crop, three seeds (1badf00d, c0ffee, 5eed42) - all PASS. Port lists are identical, so NDS.sv's .* connection is untouched. run_video_freak_diff.sh defaulted to CYCLES=200000, which processes ZERO frames and still prints PASS. Default raised to 2,500,000 so the obvious invocation proves something. hps_io is NOT given the same treatment here and still needs it: nds_hps_io.sv was validated against clash/tests/rtl/hps_io_oracle.sv, a checked-in copy of the GPL hps_io, and its own provenance relative to that source has not been established. Its differential fuzz is also only 1,805 cycles. AUDIT: the claim that this work leaves "zero GPL in build" is false, by a wide margin. NDS.qsf:11 sets TOP_LEVEL_ENTITY sys_top, and sys/sys_top.v is GPL. NDS.qsf:269 -> sys/sys.tcl:219 adds QIP_FILE sys/sys.qip, which compiles 34 sys/ files including the very hps_io.sv and video_freak.sv this commit stops instantiating. Measured from artifacts-codex-pc16-s0, the GPL modules actually present in the bitstream and their cost: hps_io:legacy 503.6 ALMs (removed by this commit) osd:vga_osd 464.8 ALMs audio_out:audio_out 214.3 ALMs vga_out:vga_out 119.2 ALMs mcp23009 90.7 ALMs i2c:i2c 61.9 ALMs f2sdram_safe_terminator x2 61.7 ALMs gamma_corr 30.1 ALMs scanlines 25.0 ALMs sigma_delta_dac x2 19.0 ALMs plus sys_top.v itself, which is the top-level entity and cannot be scrubbed by replacing submodules. rtl/sdram.sv and rtl/ddram.sv remain GPL and instantiated (NDS.sv:837). The rtl/nds_*.vhd files carry GPL tags from the GBA core fork; if they are in fact derived from it, that is not a licensing decision that can be made by editing headers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First module of the leaf group. Replaces sys/sigma_delta_dac.v (19.0 ALMs across two instances) on the analog audio path, which is the only audio path this fit profile has. Written from the published second-order error-feedback modulator structure, NOT transcribed from the GPL module, and deliberately not bit-identical to it. For a 1-bit output feeding an RC filter, cycle agreement is not observable; the DC transfer and the noise floor are. So the test measures those, on both modules, with identical stimulus, and the GPL one is included only as a black-box benchmark with absolute pass criteria rather than "matches the oracle". MEASURED (clash/tests/run_sigma_delta_tb.sh, 16 codes, prime stride so none lands on an exact 1/16): worst DC deviation 0.0 LSB vs GPL 0.0 LSB monotonicity breaks 0 vs GPL 0 worst idle noise 67.7 LSB vs GPL 57.8 LSB DC tracking is exact. The idle noise floor is ~1.4 dB WORSE than the module it replaces - it passes the criterion but it is not a tie, and it is the honest number rather than a rounded one. Tunable via the integrator clamp if it ever matters; at this level it is far below the 16-bit signal. Two bugs the test caught and that are worth recording, because both would have shipped silently: * A second-order loop with a 1-bit quantiser is only stable well inside full scale. Driven to the rails the integrators ran away and code 0 produced a duty of 0.428 where it must produce 1.0. Fixed by clamping both integrators, which keeps full-scale range instead of trading 6 dB away for stability. * The output must be the quantiser decision that PRODUCED the feedback - the pre-update integrator sign. Sampling the post-update value delays the output one sample relative to its own error term. The test itself also had to be fixed twice: its ideal transfer was inverted (INV=1 means duty FALLS as the code rises - the GPL module reading 0.0 LSB at every code is what confirms the contract), and its first code sweep used exact multiples of FS/16, which makes the bitstream perfectly periodic so every decimation block is identical and the noise figure reads a meaningless 0.0 for both modules. Same failure mode as the video_freak default of CYCLES=200000 fixed in 09f9ae7: a test that passes without measuring anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tb_arm9_trace served brom_data combinationally. nds_membus9 latches the
fetch result on the next clk1x edge and the real BIOS is an M10K with a
registered read port, so every fetch returned the word at addr+4: each
boot-ROM workload executed word[1] of its own vector table as its first
instruction, branched into a `b hang`, and parked there. ldm_bx_irq,
arm9_island, arm9_cache and arm9_torture were all mute for this reason.
Two things kept it hidden. There was no verdict - the harness only wrote a
30k-line trace, so a workload that failed and a harness that never started
one looked identical from outside. And tb_arm9_island, the same island with
the read port done right, kept passing 12/12, so the RTL always looked fine.
Both testbenches now finish the island at the same sim time, 35465 ns.
Also re-synced drift against tb_arm9_island:
- mem9_pair / mem9_readdata_hi / sdram_done64 have been unwired since
53bf403, so cache line fills took a zeroed high word in every odd slot.
The island tb carries a comment warning about this exact omission.
- timers and nds_irq ran at ce='1' rather than ce_half, ticking every
timer at twice its rate - which rewrites the workload of any IRQ test.
And gave it the verdict it never had: MARKBASE declares the workload's
marker block, and the tb turns 0xCAFEBABE / 0x0BAD0BAD into PASS/FAIL with a
non-zero exit. Writing NO marker is a failure too, so a dead harness cannot
pass for "nothing to report" again. The block address is required because a
bare value snoop also catches the stmdb that spills a register holding
0xCAFEBABE onto the stack.
ldm_bx_irq now passes its documented contract: markers at 0x02001004,
0x02001008 and 0x0200100C, no fail marker. The pre-rework CPU (857835f)
passes it identically, so the fb80923 timing rework does not break exception
returns.
The LOADADDR path was never affected - it boots from main RAM - so the
melonDS differential results stand, with the exception of arm9_torture,
which is a boot-ROM workload. TRACE_DIFF.md's run line omitted LOADADDR
entirely, which runs the two sides on different memory maps; fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GBATEK names the NDS bit "OBJ Processing during H-Blank (was located in Bit5 on GBA)": SET = the OBJ engine gets the HBlank interval = 1210 cycles, CLEAR = 954. The donor port read it as the GBA's bit 5 "H-Blank Interval Free" (SET = the CPU takes the interval = 954) - the opposite meaning, so a bit-23 game drew its OAM under the 954 budget and lost the tail of every sprite-heavy line, steady, per frame. Invert the bit at the integration point (nds_gpu2d), rename the register constant to what it is (DISPCNT_OBJ_HBlank_Proc), and replace the "POLARITY IS UNVERIFIED" comment in the drawer with the verified statement. Pinned by sim/run_gpu_obj_budget.sh: a 1024-hw-cycle line (16 x 64 px sprites) that sits between the two budgets - full render at 1210, sprite 14 truncating after 58 px and sprite 15 dropped at 954. A line under 954 would prove nothing and one over 1210 hides the difference, so the cost is chosen for the asymmetry. Analyze + elaborate in the smoke gate. NDS_HARDWARE.md gains the budget/charge model: 1 cycle per field pixel (the sprite's whole field width, clip elisions charged at setup/walk-end), rot/scal 10 + 2 per pixel, and a line over budget loses its LAST sprites in OAM order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
melonDS's software rasterizer running on the DE10-Nano's Cortex-A9, answering the one question that decides the hybrid 3D core's architecture: can the HPS ARM rasterize a DS frame inside a 16.67 ms budget? Everything else in the plan (DDR3 rings, the fabric matrix shadow, the VRAM bank mirror) is only worth building if the answer is yes, or yes-at-30-Hz. main_gxbench.cpp (new melonds_gxbench target) builds natively to capture a scene savestate on the desktop, where 900 frames take seconds; build-armhf.sh cross-builds the board binary in a crossbuild-essential-armhf docker container, no qemu. The toolchain image is tagged and cached, so only the first run pays for apt. tools/a9-raster-bench.sh drives the board run. The ROM must be on the board even with a savestate: melonDS states carry machine state, not cart contents. Not yet timed: no numbers in the tree, and the scene must sit on a busy 3D screen - the bench prints the polygon count it found and a menu capture makes the timing meaningless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tput /work/ is nvc's default library dir when a runner is invoked from the repo root; sim/nvc_work_*/ already covered the named variants. build-native/ and build-armhf/ are the melonds_tracer build trees beside build/. melonds_trace.log and scene.mln are melonDS harness output - the savestate is generated from a copyrighted ROM, same class as kirby_*.hex. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The device budget and the predict-in-LABs discipline, the two mutually exclusive shipping images with their measured costs, the remote workflow (pods, breakpoint-bisect over overnight sims), the memory strategy, the clock plan, eight day-costing traps, and the verification discipline. Supersedes the status lines in README/ROADMAP where they disagree; the Open section is the live task list. Docs go stale faster than the tree - check the RTL before repeating a doc's claim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clocks, hierarchy, memory topology, the two address maps, the renderer request chain, where the documents disagree with the RTL, and what is not built yet. Standalone: the hosted artifact wrapper's bits are inlined, so it also works offline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Import the working source snapshot supplied by SplashDev88 on 2026-09-02 on top of public main 306abb8. It contains post-beta.6 HPS Engine B, display-capture, firmware, and external-screen work. The bundled beta.6 SHA256 manifest predates 54 modified files in this donor tree, so this commit records an uncommitted development snapshot rather than claiming release-artifact verification.
Merge SplashDev88's 2026-09-02 HPS Engine B and display-capture development tree into the shared Nitro_DarkSide provenance line. The donor remains an explicitly attributed second parent. Host RTL gates pass on the reconciled tree; HPS, Quartus, and hardware proof are tracked separately.
Author
|
aww hell GPT did NOT open this without me asking LMAOOOO oops uhhhh lemme make sure this is actually good lmao |
Author
|
Here's a build that actually completed plus the reports for it and all that |
struffl
marked this pull request as ready for review
September 3, 2026 14:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
vv I cannot be held responsible for what the robot wrote OK
Summary
This draft joins the published
Nitro_DarkSideandNDS4MiSTerhistories withreal two-parent merge commits, then imports SplashDev88's supplied 2026-09-02
Engine B development tree as an author-preserved child of the public beta.6
tip.
On top of that shared history it:
on HPS;
routing in write order;
DISPCNTbit 16, fixing the observed white screen;observed partial vertical-band corruption;
POWCNT1screenassignment, and keeps touch/cursor behavior on the bottom screen;
contributors;
GPLv3 contributor agreement.
History and attribution
f854addhas the final Nitro_DarkSide tip and current NDS4MiSTer public tip asits two parents.
af82e7erecords SplashDev88's supplied working tree with SplashDev88 as Gitauthor. Its bundled
SHA256SUMSstill describes beta.6 and therefore doesnot authenticate the 54 subsequently modified files; this is documented in
PROVENANCE.mdrather than presented as a verified release archive.f4697fcjoins that donor line to the shared branch with another two-parentmerge.
No graft commit reassigns copyright or authorship. Both parent projects' GPLv3
license files remain directly inspectable in Git history.
Verification
./tools/test_nitro_console_island_host.shnds_display_capture_oracle_test(capture sources, offsets, RGB666-to-RGB555,blending,
POWCNT1swap, and production Engine B publication)git diff --check41,242/41,910 ALMs (98%), 475/553 RAM blocks, and 69/112 DSP blocks. The
generated RBF has SHA-256
35c7c0e2bd896c1d7ccb532046ada3512de71d6698c58e01d8fb4821ecf2292d.TimeQuest completed with 0 analyzer errors but does not close timing or full
constraints: cross-corner worsts are setup -14.784 ns, hold -0.765 ns,
recovery -7.611 ns, removal -1.245 ns, and pulse width +0.932 ns. The RBF's
two subsequently removed reset-global assignments were reported as invalid
no-ops, so the committed QSF has the same effective fitter topology.
Boundaries
This is intentionally a draft until the generated RBF is exercised on MiSTer,
especially SplashDev88's LG C3. Seed 2 is retained because it has the known
stable display behavior on that setup. TimeQuest results are reported for
visibility but are not treated as board acceptance at this density.
The display-capture oracle is useful groundwork, but live dual-screen 3D via
capture/alternate-frame rendering is not yet accepted as complete. This PR
relates to #10; it does not close it. FPS reporting (#11) and audio distortion
(#13) remain separate work.
Addresses #12.
Relates to #10.