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)
LatestRustyNES 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.pycompares 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 constantfalseinto every.obs.binthe DUT ever produced, unnoticed becausebus_diff.pycompares 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 asPPU_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.