RP2350 / Hazard3 RISC-V core (re-based on latest upstream + reviewed)#1
Conversation
These 27 files are the RP2350 (Hazard3 RISC-V, machine-mode) emulation core, imported verbatim from c1570's fork of rp2040js: source: https://github.com/c1570/rp2040js branch rp2350js/WIP at commit: 2f0eebc forked from: upstream wokwi/rp2040js at 554f23e (merge-base) All RP2350-RISC-V work here is c1570's (MIT, same license as upstream). The full 253-commit history of his branch is preserved as a git bundle in the GhostLabs project that seeds this fork; see CREDITS.md for attribution. This commit is the engine verbatim (pre-ESM, as in his tree). Subsequent commits adapt the imports to upstream's ESM build, wire RP2350 into the barrel exports + shared peripherals, and apply the Zcb / trap-entry fixes. Contents: the RISC-V CPU + assembler (src/riscv/*), the RP2350 chip and its SIO / IRQ / core abstraction (rp2350.ts, rpchip.ts, core.ts, sio*.ts, irq_rp2350.ts), and the RP2350 peripheral variants (*_rp2350.ts, bootram, powman, syscfg, sysinfo, pll, pads, io, dma). Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
The engine was imported verbatim from c1570's pre-ESM tree. Upstream rp2040js uses "module": "NodeNext", which requires explicit .js extensions on relative import specifiers. Mechanical codemod over the 27 imported files: 80 relative specifiers across 22 files. No logic changes. Verified: c1570's RISC-V unit specs (41) and the full upstream + RP2350 vitest suite (333 tests) pass on the upstream base. Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
…fects The imported engine is c1570's WIP. An adversarial spec review (7 ISA-area reviewers, each finding independently verified) found 19 confirmed defects, clustered in the M extension and trap/PC handling. This applies the fixes, each guarded by a falsifiable test (src/riscv/test/cpu-fixes.spec.ts) proven to fail on the pre-fix engine and pass on the fixed one. RV32M (multiply was computed with JS float64, wrong above 2^53): - MUL low word via Math.imul (was a rounded float64 product; ~95% of random operands wrong). - MULH upper word via BigInt (float64 `/2^32 >>> 0` truncates toward zero, so every negative product was wrong). - MULHSU: implement it (was undecoded -> threw and aborted the core). - MULHU upper word via BigInt (float64 division was off-by-one near 2^32). Trap / control transfer (the next_pc==0 sentinel collided with real targets): - Synchronous traps (ECALL/EBREAK) now execute the handler's first instruction at mtvec instead of mtvec+ilen, via an explicit branch_taken flag. - Control transfers to address 0 (bootrom base) are honoured, not dropped. - CPU.reset() restores the PC so a warm reset re-enters the boot vector. - Trap-entry mstatus clears only MIE (was `&= 1<<7`, which wiped MPP etc.). RV32I / Zicsr: - SLTIU sign-extends the immediate before the unsigned compare (was comparing against the zero-extended 0..4095 immediate). - JALR clears bit 0 of the computed target (spec-mandated mask was missing). - CSRRC write-suppression keyed on the rs1 index, not its runtime value. Hazard3 Xh3irq: - External IRQ index 0 (TIMER0_IRQ_0) is now deliverable (NOIRQ tested via meinext bit 31, not irq==0). - mip.MEIP gated by PREEMPT, not PPREEMPT (was spuriously asserted in a frame). Plus the Zcb decoder completion (c.lbu/lhu/lh/sb/sh, c.zext/sext.b/h, c.mul, c.not) the RP2350 bootrom needs. Findings credited to the review; the engine remains c1570's work. Full suite: 342 tests pass (was 333; +9 fix tests). Illegal-instruction encodings still throw rather than trapping mcause=2 - an intentional debug aid, documented. Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
- package.json: rename to rp2350js, point at the GhostRoboticsLab repo, and move the build off `prepublish` (which ran on every `npm install` and failed because the peripheral type-clean is still pending) to `prepublishOnly`. - LICENSE: add c1570 (RP2350 RISC-V) and GhostRoboticsLab copyright lines. - CREDITS.md: the wokwi -> c1570 -> this-fork lineage, attribution intact. - README.md / ROADMAP.md: status, the RISC-V core fixes, and the staged RP2350 peripheral parameterization work. - .github/workflows/ci.yml: run the vitest suite on node 20/22; drop the three upstream RP2040 workflows (micropython / pico-sdk / lint) that need toolchains a fork can't satisfy. - Import c1570's RP2350 demo fixtures (bootrom + 3 RISC-V firmware images) and enable src/rp2350.spec.ts. Its three firmware-integration cases are skipped pending the peripheral parameterization (they fail identically on the pristine import, so they are not a RISC-V core regression). Full suite: 345 pass, 3 skipped. Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
Make the peripheral layer chip-aware so the RP2350 (and RP2040) share one
peripheral set through a common interface. `npx tsc --noEmit` goes from 63
errors to 0, and the `blink_simple` firmware-integration test now passes
(RP2350 boots its A2 bootrom and drives GPIO via SIO).
Core abstraction:
- RP2040 now `implements IRPChip`; `BasePeripheral` holds an `IRPChip` instead
of the concrete `RP2040`, so both chips construct the shared peripherals.
- `GPIOPin` delegates its function-select -> peripheral-output dispatch to the
chip via a narrow `IGPIOChipHost` (each chip owns its function map; RP2350
adds `FUNCTION_PIO2`). Fixes the construction-order trap by seeding
`lastValue` to a constant.
Peripheral parameterization (RP2350 numbers differ from RP2040):
- RESETS: per-chip `reset_mask` (RP2350 = 0x1fffffff, 29 blocks). This was the
boot-hang: the bootrom de-asserts a high-numbered block and spins on
RESET_DONE for it, which the 25-bit RP2040 mask never set.
- TIMER: `timer_irq_base` param + RP2350 INTR/INTE/INTF/INTS offsets (shifted
+8). With this the RP2350 timer interrupt fires.
- PWM / ADC / USB: IRQ / DREQ base constructor params.
- PIO: `IRPChip`, `dreq{Rx,Tx}_base`, `getPinValue`/`getPinOutputEnabled`, a
`gpiobase` field for the RP2350 GPIOBASE window.
- UART / SPI / I2C: `IRPChip`; numeric DREQ ids (the chips' DREQChannel enums
differ). DMA DREQ raised via `IRPChip.dma_{set,clear}DREQ`.
- IClock gains `tick()`; RP2040-only syscfg/ppb narrow the chip ref to reach
the ARM core.
cpu.ts: avoid ES2020 BigInt literals (target is es2018) and make the MSTATUS
trap store explicit (no switch fallthrough).
Tests: 346 pass, 2 skipped. `blink_simple` un-skipped and passing; the ~300
RP2040 tests are unaffected by the shared gpio-pin / rp2040 refactor. The two
remaining skips (hello_timer timing/runtime, pio_blink GPIOBASE) are documented
in ROADMAP.md.
Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
Update: RP2350 peripheral parameterization landed (
|
Tracing showed the RP2350 timer interrupt fires and delivers cleanly (5 fires); the stall after ~4 iterations is a multicore inter-core-FIFO interaction (read-on-empty state sensitive to core0/core1 step interleaving), not the idle/clock-stepping model. Update the skip note + ROADMAP accordingly. Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
Found by lockstepping this engine against c1570's original (which passes both firmware tests) and bisecting the first architectural divergence. 1. MEINEXT must reset to NOIRQ, not 0. A zeroed MEINEXT reads as "IRQ 0, NOIRQ clear". The IRQ-0-delivery gate (!NOIRQ) then treats it as a real pending IRQ 0 the instant the firmware enables interrupts — a phantom external trap before any IRQ has fired. That trap corrupts the stack and, several layers down, is what stalled hello_timer after a few timer periods. Resetting MEINEXT to NOIRQ keeps index 0 deliverable (a real IRQ 0 sets MEINEXT=0/NOIRQ-clear) while the idle state reads correctly. 2. Trap targets must mask the MTVEC mode bits (mtvec[1:0]). Exceptions and direct-mode interrupts go to BASE = mtvec & ~3; only vectored interrupts add an offset. The old code jumped to the raw mtvec, so a vectored mtvec (RP2350 firmware sets 0x20000001) sent an EBREAK/exception to the odd address 0x20000001 -> fetch of a misaligned "instruction 0" -> crash. This is the pio_blink early-crash; with the mask it correctly vectors to 0x20000000. Also reverts the updateMEINEXT mip.MEIP gating from PREEMPT back to PPREEMPT (matching c1570 / the Hazard3 reference) — that earlier change was untested and firmware-visible; PPREEMPT is the validated behavior. Tests: 347 pass, 1 skipped. hello_timer is un-skipped and passing (250M-step firmware run, exactly 19 timer-driven prints). pio_blink stays skipped — its crash is fixed, but driving GPIO from PIO on RP2350 still needs the RP2350 PIO feature set (GPIOBASE, 32-bit pin mask, IN_COUNT) ported from c1570. Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
Update:
|
The last skipped firmware integration test, pio_blink, drives two PIO blocks: one toggles GPIO3/4, the other GPIO32/33. GPIO32 is only reachable once a PIO block re-bases its internal 32-pin window onto chip GPIO16..47 via the RP2350 GPIOBASE register. The early crash was already fixed (the MTVEC mask); what remained was this pin-window feature, which c1570 implemented and the modern upstream lacks. Surgical port (does NOT adopt c1570's SM step-model rewrite): 1. GPIOBASE register (0x168), RP2350-only: read returns gpiobase; write sets gpiobase = value & 16 (the GPIOBASE bit is bit 4, so 0 or 16). On RP2040 the address falls through to BasePeripheral unchanged. 2. checkChangedPins now iterates the PIO's internal 32-pin space (0..31) and notifies gpio[pinIndex + gpiobase]. The old loop ran gpioIndex over gpio.length and did `1 << gpioIndex`, which aliases pins 32..47 back onto 0..15 (JS shifts are mod-32) and never applied the offset, so GPIO16..47 were never notified. RP2040 behaviour is unchanged (gpiobase 0, bounds-checked). 3. 32-bit pad mask in pinValuesChanged/pinDirectionsChanged (isRp2040 ? 0x3fffffff : 0xffffffff); RP2040 keeps its 30-bit mask. 4. isRp2040 discriminator (this.rp2040.identifier === 'rp2040') gates the above. Why no step-model change: the blink program uses the default clock divider (1.0), at which the new fork's one-instruction-per-cycle PIO model and c1570's clock-divided model are identical, so the working RP2040 PIO step model is left untouched. pio_blink's `2 toggles / 2M steps` calibration therefore holds. Deferred (not exercised by pio_blink, each to land with its own test): IN_COUNT masking, the PIO IRQ-index mode (resolveIrqTarget), neighbour-SM synchronous restart, FJOIN RX/TX, and the WAIT PIN gpiobase offset. See ROADMAP.md. Verification: pio_blink un-skipped and passing (gpio3toggle == 2, gpio32toggle == 2). Full suite 348/348, none skipped; tsc clean. Adversarially verified: reverting either checkChangedPins or the GPIOBASE write drives the gate red (gpio32toggle -> 0), GPIOBASE=16 is observed at runtime, and no c1570 step-model fields were introduced. Signed-off-by: Pratheek Balakrishna <pratheekb96@gmail.com>
Update:
|
What this is
The foundation of rp2350js — c1570's RP2350 / Hazard3 RISC-V emulation core re-based onto the latest upstream
wokwi/rp2040js, with the RISC-V core put under an adversarial spec review and corrected.This is the first reviewable cut. The RISC-V core is in good shape and fully tested; the RP2350 peripheral layer is the next phase (tracked in
ROADMAP.md).Lineage & attribution (all MIT)
rp2350js/WIP— added the entire RP2350 / Hazard3 RISC-V core (~50 h). Imported here in a commit authored to c1570, withCREDITS.mdand a full-history git bundle preserving provenance.Commits
1b761aced0bfd690487dabca1a2eRISC-V core correctness
An adversarial spec review (7 ISA-area reviewers, every finding independently verified) surfaced 19 confirmed defects in c1570's WIP core. All fixed, each guarded by a regression test in
src/riscv/test/cpu-fixes.spec.tsproven to fail on the pre-fix engine (negative control). Highlights:MULused JS float64*(silently wrong for products > 2^53);MULHSUwas undecoded and crashed the core. Now exact viaMath.imul/BigInt.ECALL/EBREAKlanded atmtvec + ilen, skipping the handler's first instruction.irq==0instead of themeinextsign bit).SLTIUsign-extension,JALRLSB masking,CSRRCwrite gating, warm-reset PC,mip.MEIPpreemption gating, the trapmstatusupdate, and the Zcb compressed instructions the bootrom needs.See
ROADMAP.mdfor the full defect table.Tests
345 pass, 3 skipped. The 3 skips are chip-level integration tests (
blink_simple,hello_timer,pio_blink) that depend on the RP2350-parameterized peripherals — they fail identically on the pristine import, i.e. a peripheral gap, not a CPU regression. CI runs vitest on Node 20 & 22.Next
RP2350 peripheral parameterization (port c1570's
IRPChipIRQ/DREQ abstraction onto the upstream peripherals) → type-cleantscbuild and un-skip the 3 integration tests. SeeROADMAP.md.