Skip to content

RustyNES v2.6.6 — "Chassis" (the console becomes a MiSTer core: the framework vendored byte-identical, a top level, a clock, a palette, video sync and an audio mixer, compiled by Quartus 17.0.2 to a Cyclone V bitstream with 0 errors and timing closed; nine findings the tool produced that no reading would have, and two hardware defects found by asking whether the outputs would actually work)

Latest

Choose a tag to compare

@github-actions github-actions released this 30 Aug 18:37
1ce6642

RustyNES v2.6.6 "Chassis"

The console becomes a MiSTer core: sys/ vendored byte-identical, a top level, a clock, a palette, video sync and an audio mixer, compiled by Quartus 17.0.2 into a timing-clean Cyclone V bitstream.

A chassis is the frame everything else bolts to. It is not the engine, and this release does not touch the engine — nes_top computes exactly what it computed at v2.6.5. That makes the co-simulation suite an acceptance criterion here rather than a formality: integration work that quietly changed the core would otherwise show up as a green bitstream and nothing else. It earned that place immediately, because the cartridge's memories had to be rewritten and only the suite could adjudicate the rewrite.

The gate

# criterion result
1 sys/ is verbatim 57 files, 0 content differences, against Template_MiSTer@3ea1134c
2 the project compiles exits 0
3 a bitstream exists output_files/RustyNES.rbf
4 timing closes worst setup +0.363 ns, worst hold +0.245 ns, TNS 0.000 on every clock
5 warnings named 0 errors, warnings 111 → 3 — all three inside sys/ or Quartus's own megafunction
6 no co-simulation regression 87 passed, 0 failed

The console's own clock carries +13.514 ns of setup slack and an Fmax of 30.26 MHz against the 21.477272 MHz it needs.

The fitter seed is pinned (SEED 2), and that is not decoration: one path in the framework's HDMI domain sits within a tenth of a nanosecond of its requirement, and two compiles of the same RTL — differing only in how three unrelated SD pins were driven — landed it at +0.363 ns and −0.086 ns. A build that closes has to be reproducible, so the seed that produced the numbers above is recorded rather than left to luck.

An M10K read is registered

cart_nrom.sv read its arrays asynchronously under a comment claiming that inferred block RAM "from the source style alone". It cannot: there is no asynchronous-read block memory on this device, so 40 KiB of cartridge stayed in registers — 393,216 of roughly 166,000 available — and Quartus refused it.

The project had already written the rule down. The README has said since v2.4.3 that the core would use "synchronous read with a registered address, no asynchronous read anywhere"; cart_nrom.sv was written two releases later doing the opposite. A rule in a README is not a control — and nothing before this rung had ever asked Quartus about the cartridge, because v2.4.3 fitted a 2 KiB probe module and 2 KiB fits either way.

The rule as applied here is narrower than that sentence: it binds arrays too large to live in fabric, which is the cartridge. Work RAM is a deliberate, measured exception — 2 KiB is 16,384 registers, about a tenth of the device, and registering it too was tried and reverted because the harness samples o_wram_dout at the access itself and all 87 gates failed.

Whether the resulting one-clock latency is visible was probed rather than argued: bus_addr is stable for all twelve clocks of a CPU cycle and the access lands at the seventh, so the console never sees it. The harness did, and its cross-checks moved to where the CPU actually samples.

Two defects that only "will this work on hardware?" would have found

The audio would have been a DC rail. The mixer produces what the 2A03's resistor ladder produces: unipolar, silence at zero. Handed to the framework as unsigned, silence maps to −32768 — a full-scale offset into the DAC and into HDMI audio. Real hardware AC-couples; audio_dc_block is that capacitor, with its corner at the console's own documented ~90 Hz.

Two OSD options did nothing. "CRT 25%" and "CRT 50%" were offered while VGA_SL was tied to zero, and two aspect-ratio entries were served by a module this core does not instantiate.

A convention enforced by a glob has no error message

sys/sys_top.sdc puts the core's clock into its own clock group by matching a hierarchical name pattern — *|pll|pll_inst|altera_pll_i|*. Name the PLL anything else and the clock matches no group, every crossing to the framework's audio, HDMI and HPS domains is analysed as synchronous, and the report shows −13.901 ns of slack and −422,601 ns of total negative slack on a design whose Fmax was already 25.75 MHz for a 21.477 MHz requirement.

Nothing warned. The compile succeeded and the Assembler reported 0 errors and 0 warnings. Reading Fmax and slack together is what separates a missing constraint from a slow design.

Nine such findings are recorded in docs/rung6-integration.md in the sibling repository, including a Quartus internal error on the 256-entry decode table and the three fixes for it that were measured and failed.

Not in this release

  • Hardware. No DE10-Nano and no SuperStation One are attached to this machine, so a booting core, a synced display, audible sound and a working pad are not claimed. Rung 6 closes at v2.6.7.
  • Band-limited audio. The DUT has a lookup-table mixer and a DC blocker; the emulator has BLEP, so the core will alias where the emulator does not.
  • The v-copy delay depth (caveat C1) is deferred — discriminating inside the passing window of 1–4 dots needs a stimulus sensitive to the exact dot.
  • The per-cycle AccuracyCoin gate (C2) is half-built, and the half that exists already found something. The harness now emits rolling hash checkpoints and tb/ckpt_diff.py compares them — the same per-cycle information at 1/8192th the size of a 2.0 GB capture. On its first run it failed correctly: four of nine observable fields had been written as constant false into every .obs.bin the DUT ever produced, unnoticed because bus_diff.py compares four of them. Three are now correct. The residual is one CPU cycle on one signal, twice in two hundred thousand: the oracle's frame IRQ becomes visible during cycle 29,827 and this core's not until 29,828. A rung-4 question of the same family as PPU_LEAD, and which side is at fault is not yet known — blargg's 2005 APU battery passes 11/11 here, which bounds how wrong it can be without settling it, since those ROMs measure when the CPU takes the interrupt and latency can absorb a one-cycle difference in when the line asserts. A trace observation point and a real one-cycle difference are both live. Not registered as a gate until it closes.

The emulator itself is unchanged: AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff hold by construction, and were re-run.