Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c462652
feat: RV32I ISA formal proofs — 36 theorems, zero sorry
junjihashimoto Mar 25, 2026
f582e31
feat: add B-type and J-type immediate roundtrip proofs
junjihashimoto Mar 25, 2026
b042d78
feat: RV32I pipeline invariant proofs — 26 theorems, zero sorry
junjihashimoto Mar 25, 2026
ecb6a88
feat: CSR formal proofs — 21 theorems exposing MSTATUS WPRI bug
junjihashimoto Mar 25, 2026
d4eee08
feat: Signal DSL ↔ pure spec equivalence proofs — 17 theorems
junjihashimoto Mar 25, 2026
22b6e8a
docs: add Phase 48 (AXI4-Lite) and Phase 49 (RV32 102 proofs + MSTATU…
junjihashimoto Mar 25, 2026
c736549
feat: idle-loop oracle improvements — MIE/MTIE guard + WFI fast-path …
junjihashimoto Mar 25, 2026
05af349
docs: add Phase 50 (Linux Boot Idle-Loop Skipping) to changelog, stat…
junjihashimoto Mar 25, 2026
03a40a5
docs: update Phase 4 (SV Transpiler Extended ISA) status after invest…
junjihashimoto Mar 25, 2026
7761b5c
fix: SVParser flattener root-cause fixes for sub-module wiring
junjihashimoto Mar 25, 2026
4e587a7
fix: SVParser 2-pass parameter override + iterative flattening
junjihashimoto Mar 25, 2026
48df837
fix: SVParser unary minus (-expr) + all PicoRV32 modules now parsed
junjihashimoto Mar 25, 2026
3fe3604
docs: M-ext root cause — if-conversion priority bug in collectGuardedNB
junjihashimoto Mar 25, 2026
ef6ef74
fix: strip backtick-assert macro — PicoRV32 M-extension now works!
junjihashimoto Mar 25, 2026
40d2d18
docs: M-ext progress — instructions execute but MUL/DIV results wrong
junjihashimoto Mar 25, 2026
cf4f19e
fix: constant folding in if-conversion + forLoop in collectGuardedBlock
junjihashimoto Mar 25, 2026
03e047b
feat: [base +: width] part-select + forLoop unroll in SVParser
junjihashimoto Mar 25, 2026
625d959
feat: multi-variable concat-LHS decomposition + collectBlockNamesTop fix
junjihashimoto Mar 25, 2026
87897b8
feat: carry-save accumulator almost working — MUL 7*6 = 40 (expected 42)
junjihashimoto Mar 25, 2026
8a3680c
docs: M-ext status — blocking assignment self-reference needs SSA wir…
junjihashimoto Mar 25, 2026
b1ea168
feat: SSA-style forLoop unrolling with depth-aware naming
junjihashimoto Mar 25, 2026
cb97a42
feat: self-ref-only SSA + recursive LHS rename + OR-merge + SSA base
junjihashimoto Mar 25, 2026
68c3cff
fix: topoSort SSA prologue dependency + remaining blocking assign issue
junjihashimoto Mar 25, 2026
401f4a8
feat: sequential always @* emit + SVExpr width for +: — 8-bit mul PASS!
junjihashimoto Mar 25, 2026
dbf5424
docs: M-ext status — 8-bit mul PASS, full PicoRV32 blocked by cpuregs…
junjihashimoto Mar 25, 2026
f078904
fix: exclude memory names from CppSim local wire declarations
junjihashimoto Mar 25, 2026
22d3b3b
fix: hybrid mux/sequential emit for always @* + seqBody placement
junjihashimoto Mar 25, 2026
148c691
docs: M-ext CPU executes ~25 instructions then stalls at PC=0x64 (PCP…
junjihashimoto Mar 25, 2026
36baaa2
debug: decoder instr_trap=1 for ADDI after SW — constant folding chan…
junjihashimoto Mar 25, 2026
2437b86
fix: remove tryEvalConst from collectGuardedNB/Block
junjihashimoto Mar 25, 2026
886e6b4
feat: rewrite always @* to pure MUX dataflow, remove seqBody
junjihashimoto Mar 25, 2026
cbbb4f1
test: add 9 IR-level unit tests for SVParser lowering regression
junjihashimoto Mar 25, 2026
4d7a78b
feat: introduce MemorySSA sequential emitter for always @*
junjihashimoto Mar 25, 2026
27d7f60
fix: handle if-else merge for uninitialized variables in sequential SSA
junjihashimoto Mar 25, 2026
e1e095a
test: add 5 JIT pair tests for always @* patterns
junjihashimoto Mar 25, 2026
f90e447
fix: resolve cyclic deps in case statements + RV32I/M-ext PASS
junjihashimoto Mar 26, 2026
fd33792
test: update all tests for sequential SSA naming, add large MUL tests
junjihashimoto Mar 26, 2026
de7c38d
test: add consecutive MUL test + fix all 30 tests to PASS
junjihashimoto Mar 26, 2026
aa55ac5
test: add SoC-like pcpi_mul wrapper test (Test 21e), 31/31 PASS
junjihashimoto Mar 26, 2026
88cd5da
fix: implement Verilog bit replication {N{expr}} + store/load debug
junjihashimoto Mar 26, 2026
23bcda6
fix: byte-lane write shift + add JIT byte-lane test
junjihashimoto Mar 26, 2026
95bb716
fix: multi-bit replication {N{expr}} for non-literal operands
junjihashimoto Mar 26, 2026
860af48
fix: UART byte display (mask low 8 bits for ASCII)
junjihashimoto Mar 26, 2026
8db2c69
docs: add comment explaining UART byte replication in Test 10
junjihashimoto Mar 26, 2026
637f0aa
docs: update STATUS, CHANGELOG, RV32, README for Phase 51 (M-ext)
junjihashimoto Mar 26, 2026
b99df85
ci: add RV32 JIT benchmark to CI, commit generated_soc_jit.cpp
junjihashimoto Mar 26, 2026
6607990
ci: push RV32 JIT benchmark results to GitHub Pages
junjihashimoto Mar 26, 2026
6312530
ci: add RV32 Verilator vs JIT benchmark (10M cycles)
junjihashimoto Mar 26, 2026
18551bb
ci: add rv32i_soc_wrapper.sv for Verilator build in CI
junjihashimoto Mar 26, 2026
fbc9470
ci: fix verilator bench link — remove duplicate verilated_threads.o
junjihashimoto Mar 26, 2026
5aca6f2
ci: remove generated_soc_jit.cpp from repo (generated by lake build)
junjihashimoto Mar 26, 2026
b986b06
ci: exclude tb_soc.o from verilator_bench link (duplicate main)
junjihashimoto Mar 26, 2026
933bc66
ci: fix JIT .so path — use ./generated_soc_jit.so for dlopen
junjihashimoto Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
branches: [ main, master ]
workflow_dispatch:

permissions:
deployments: write
contents: write

jobs:
build:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -98,11 +102,96 @@ jobs:
- name: Run Compiler improvement tests (complement, lambda, hw_let)
run: lake env lean Tests/CompilerTests.lean

# ---- Oracle Accuracy Tests ----

- name: Run oracle accuracy tests (halt loop detection, timer skip, MIE/MTIE guard)
run: lake exe oracle-accuracy-test

# ---- SVParser Tests ----

- name: "Run SVParser tests (11 tests: parse, lower, JIT, PicoRV32, C firmware)"
- name: "Run SVParser tests (34 tests: parse, lower, JIT, pcpi_mul, byte-lane, replication)"
run: lake exe svparser-test

# ---- RV32 JIT vs Verilator Benchmark ----
# Signal DSL RV32 SoC (122 registers, 4-stage pipeline)
# Runs 10M cycles with both backends, compares cyc/s

- name: Install Verilator
run: sudo apt-get install -y verilator

- name: Build Verilator simulation
run: cd verilator && make build

- name: "Build JIT shared library (.so)"
run: |
cd verilator
g++ -O2 -std=c++17 -shared -fPIC -o generated_soc_jit.so generated_soc_jit.cpp

- name: "Build benchmark binaries"
run: |
cd verilator
# JIT bench
g++ -O2 -std=c++17 -o jit_bench tb_jit_bench.cpp -ldl
# Verilator bench — link model objects but exclude tb_soc.o (has its own main)
VERILATOR_ROOT=$(verilator --getenv VERILATOR_ROOT)
g++ -O2 -std=c++17 -o verilator_bench tb_verilator_bench.cpp \
$(ls obj_dir/*.o | grep -v tb_soc) \
-Iobj_dir -I${VERILATOR_ROOT}/include -I${VERILATOR_ROOT}/include/vltstd \
-lpthread

- name: "Run RV32 benchmark (10M cycles, Verilator vs JIT)"
run: |
cd verilator
CYCLES=10000000

echo "=== Verilator Benchmark ==="
./verilator_bench ../firmware/firmware.hex $CYCLES 2>&1 | tee verilator_output.txt

echo ""
echo "=== JIT Benchmark ==="
./jit_bench ../firmware/firmware.hex $CYCLES ./generated_soc_jit.so 2>&1 | tee jit_output.txt

- name: Parse benchmark results
run: |
cd verilator
# Extract cycles/sec from Verilator output
VERILATOR_CPS=$(grep -oE '[0-9]+ cycles/sec' verilator_output.txt | head -1 | awk '{print $1}')
# Extract cycles/sec from JIT output (Benchmark 1: pure eval+tick)
JIT_PURE_CPS=$(grep 'cycles/sec' jit_output.txt | head -1 | grep -oE '[0-9]+ cycles/sec' | awk '{print $1}')
# Benchmark 4: evalTick (fused)
JIT_FUSED_CPS=$(grep 'cycles/sec' jit_output.txt | grep -A0 'evalTick' | grep -oE '[0-9]+ cycles/sec' | head -1 | awk '{print $1}')
# Benchmark 5: evalTick + 6 wires
JIT_6WIRE_CPS=$(grep 'cycles/sec' jit_output.txt | tail -1 | grep -oE '[0-9]+ cycles/sec' | awk '{print $1}')

echo "Verilator: ${VERILATOR_CPS:-0} cyc/s"
echo "JIT eval+tick: ${JIT_PURE_CPS:-0} cyc/s"
echo "JIT evalTick: ${JIT_FUSED_CPS:-0} cyc/s"
echo "JIT evalTick+6w: ${JIT_6WIRE_CPS:-0} cyc/s"

cat <<EOF > ../rv32-bench-results.json
[
{ "name": "Verilator (10M cycles)", "unit": "cycles/sec", "value": ${VERILATOR_CPS:-0} },
{ "name": "JIT eval+tick (10M cycles)", "unit": "cycles/sec", "value": ${JIT_PURE_CPS:-0} },
{ "name": "JIT evalTick fused (10M cycles)", "unit": "cycles/sec", "value": ${JIT_FUSED_CPS:-0} },
{ "name": "JIT evalTick+6wires (10M cycles)", "unit": "cycles/sec", "value": ${JIT_6WIRE_CPS:-0} }
]
EOF
cat ../rv32-bench-results.json

- name: Store RV32 benchmark result
uses: benchmark-action/github-action-benchmark@v1
if: github.event_name == 'push'
with:
name: RV32 SoC Simulation Benchmark (Verilator vs JIT)
tool: customBiggerIsBetter
output-file-path: rv32-bench-results.json
benchmark-data-dir-path: dev/rv32-bench
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
alert-threshold: "80%"
comment-on-alert: true
fail-on-alert: false

# ---- Unit Tests ----

- name: Run full test suite (481 tests)
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ Thumbs.db
plans/
Sparkle/Verification/Generated/
Examples/CDC/gen/
verilator/generated_soc_jit.cpp
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ Sparkle ships with production-grade IP cores — each with pure Lean specs, form
|----|-------------|:------:|:-----:|---------|
| **[BitNet b1.58](docs/BitNet.md)** | Formally verified LLM inference accelerator. Ternary weights, Q16.16 datapath, dual architecture (1-cycle vs 12-cycle) | 60+ theorems | Full | 202K / 99K cells |
| **[YOLOv8n-WorldV2](docs/YOLOv8.md)** | Open-vocabulary object detection. INT4/INT8 quantized, 15 modules, CLIP text embeddings | Golden validation | Full | Backbone + Neck + Head |
| **[RV32IMA SoC](docs/RV32.md)** | RISC-V CPU — boots Linux 6.6.0. 4-stage pipeline, Sv32 MMU, UART, CLINT. JIT at 13M cyc/s (1.17x Verilator) | ISA proofs | Full | 122 registers |
| **[SV→Sparkle Transpiler](docs/RV32.md#sv-transpiler)** | Parse Verilog → JIT + formal verification. `verilog!` macro, `assert` auto-proof via `bv_decide`, type-safe JIT wrappers. C firmware: Fib, Sort, GCD | 6+ theorems | JIT | 11 parser tests |
| **[RV32IMA SoC](docs/RV32.md)** | RISC-V CPU — boots Linux 6.6.0. 4-stage pipeline, Sv32 MMU, UART, CLINT. JIT at 13M cyc/s (1.17x Verilator). 102 formal proofs including Signal DSL ↔ spec equivalence | 102 theorems | Full | 122 registers |
| **[AXI4-Lite Bus](docs/RV32.md)** | Verified AXI4-Lite slave/master. Protocol compliance (valid persistence, deadlock-free), synthesizable | 14 theorems | Full | 23 sim tests |
| **[SV→Sparkle Transpiler](docs/RV32.md#sv-transpiler)** | Parse Verilog → JIT + formal verification. `verilog!` macro, `assert` auto-proof via `bv_decide`. PicoRV32 M-ext (MUL/DIV/REM) operational. 34 CI-safe tests | 6+ theorems | JIT | 34 tests |
| **[H.264 Codec](docs/H264.md)** | Baseline Profile encoder + decoder. Hardware MP4 muxer produces playable files. 14 modules | 15+ theorems | Full | 709-byte MP4 output |
| **[CDC Infrastructure](docs/CDC.md)** | Lock-free multi-clock simulation. SPSC queue (210M ops/sec), rollback mechanism, JIT.runCDC | 12 theorems | C++ | 2-thread Time-Warping |

Expand Down Expand Up @@ -799,10 +800,12 @@ Contributions welcome! Areas of interest:
- [x] ~~**Type-Safe JIT Wrappers**~~ - Done (Phase 45): `SimInput`/`SimOutput`/`Simulator` generated by `verilog!` macro
- [x] ~~**Signal Operator Refactoring**~~ - Done (Phase 46): Mixed Signal/BitVec operators (`a + 1#8`, `1#64 <<< b`), compiler fix for inline expansion
- [x] ~~**Imperative `<~` Assignment**~~ - Done (Phase 47): `Signal.circuit` macro with `<~` register assignment, unified `Signal.loop` memoization
- [ ] **Linux Boot Idle-Loop Skipping** - Extend dynamic oracle to detect WFI/idle loops during Linux boot
- [x] ~~**AXI4-Lite Bus Protocol**~~ - Done (Phase 48): Verified slave/master, 14 proofs, 23 sim tests, synthesizable
- [x] ~~**RV32I Formal Verification**~~ - Done (Phase 49): 102 theorems, **MSTATUS WPRI bug found**, Signal DSL ↔ spec equivalence
- [x] ~~**Linux Boot Idle-Loop Skipping**~~ - Done (Phase 50): MIE/MTIE interrupt guard, WFI fast-path, 4 CI oracle accuracy tests
- [ ] **Verified Standard IP — Parameterized FIFO** - Generic depth/width FIFO with power-of-2 depth
- [ ] **Verified Standard IP — N-way Arbiter** - Generalize 2-client round-robin arbiter to N clients
- [ ] **Verified Standard IP — AXI4-Lite / TileLink** - Bus protocol interfaces with formal properties
- [ ] **Verified Standard IP — TileLink / AXI4 Interconnect** - Full AXI4 (bursts, IDs) and TileLink
- [ ] **GPGPU / Vector Core** - Apply the Verification-Driven Design (VDD) framework to highly concurrent, memory-bound accelerator architectures
- [ ] **FPGA Tape-out Flow** - End-to-end examples deploying Sparkle-generated Linux SoCs to physical FPGAs

Expand Down
7 changes: 5 additions & 2 deletions Sparkle/Backend/CppSim.lean
Original file line number Diff line number Diff line change
Expand Up @@ -413,13 +413,16 @@ def emitModule (m : Module) (design : Option Design := none)
| none => internalWires.filter fun (w : Port) =>
let sn := sanitizeName w.name
sn.startsWith "_gen_" || tickRefs.contains sn
-- Collect memory names to avoid declaring them as local scalars
let memoryNames := m.body.filterMap fun s => match s with
| .memory name _ _ _ _ _ _ _ _ _ => some (sanitizeName name) | _ => none
let localWires := match observableWires with
| some ws => internalWires.filter fun (w : Port) =>
let sn := sanitizeName w.name
!ws.contains sn && !tickRefs.contains sn
!ws.contains sn && !tickRefs.contains sn && !memoryNames.contains sn
| none => internalWires.filter fun (w : Port) =>
let sn := sanitizeName w.name
!sn.startsWith "_gen_" && !tickRefs.contains sn
!sn.startsWith "_gen_" && !tickRefs.contains sn && !memoryNames.contains sn

let wireDecls := memberWires.map fun (p : Port) =>
s!" {emitCppType p.ty} {sanitizeName p.name};"
Expand Down
42 changes: 39 additions & 3 deletions Sparkle/Core/Oracle.lean
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ structure SelfLoopConfig where
skipToTimerCompare : Bool := false
/-- Maximum cycles to skip per trigger (caps skipToTimerCompare distance) -/
maxSkip : Nat := 10_000_000
/-- Guard: check MIE (global interrupt enable) and MTIE (timer interrupt enable)
before performing timer-compare skip. Prevents skipping when interrupts
are disabled (the timer interrupt wouldn't fire anyway). -/
checkInterruptEnable : Bool := false
/-- Register index for MSTATUS CSR (contains MIE at bit 3) -/
mstatusRegIdx : UInt32 := 58
/-- Register index for MIE CSR (contains MTIE at bit 7) -/
mieRegIdx : UInt32 := 59
/-- WFI fast-path: when set, the oracle checks if the instruction at this
wire index is WFI (0x10500073) and triggers immediately (threshold=1). -/
wfiWireArrayIdx : Option Nat := none

/-- Mutable state for the self-loop detector -/
structure SelfLoopState where
Expand Down Expand Up @@ -75,14 +86,34 @@ def mkSelfLoopOracle (config : SelfLoopConfig)
let pc := vals[config.pcWireArrayIdx]?.getD 0
let st ← stateRef.get

-- WFI fast-path: if WFI wire is provided and active, trigger immediately
let isWFI := match config.wfiWireArrayIdx with
| some idx => (vals[idx]?.getD 0) != 0
| none => false

-- Check if PC is within tolerance of anchor (handles multi-instruction loops)
let pcDiff := if pc >= st.anchorPC then pc - st.anchorPC else st.anchorPC - pc
let isNearAnchor := pcDiff <= config.pcTolerance

if isNearAnchor then
-- WFI overrides the threshold: trigger after just 1 cycle of WFI
let effectiveThreshold := if isWFI then 1 else config.threshold

if isNearAnchor || isWFI then
let newCount := st.sameCount + 1
if newCount >= config.threshold then
-- Self-loop detected — skip forward
if newCount >= effectiveThreshold then
-- Self-loop detected — attempt skip

-- Guard: check interrupt enable before timer-compare skip
if config.checkInterruptEnable then do
let mstatus ← JIT.getReg handle config.mstatusRegIdx
let mieReg ← JIT.getReg handle config.mieRegIdx
let globalIE := (mstatus.toNat >>> 3) &&& 1 -- MSTATUS.MIE (bit 3)
let timerIE := (mieReg.toNat >>> 7) &&& 1 -- MIE.MTIE (bit 7)
if globalIE == 0 || timerIE == 0 then
-- Interrupts disabled — timer skip would be futile
stateRef.set { st with sameCount := newCount }
return none

-- Read current CLINT timer values
let oldLo ← JIT.getReg handle config.mtimeLoRegIdx
let oldHi ← JIT.getReg handle config.mtimeHiRegIdx
Expand Down Expand Up @@ -131,6 +162,7 @@ def mkSelfLoopOracle (config : SelfLoopConfig)

/-- Create a boot-optimized oracle for Linux boot idle-loop skipping.
Uses timer-compare-aware skipping with wider PC tolerance (32 bytes).
Enables MIE/MTIE guard to avoid skipping when interrupts are disabled.
Resets sameCount after each trigger so the timer interrupt can fire. -/
def mkBootOracle (config : SelfLoopConfig := {})
: IO ((JITHandle → Nat → Array UInt64 → IO (Option Nat)) × IO.Ref SelfLoopState) :=
Expand All @@ -145,6 +177,10 @@ def mkBootOracle (config : SelfLoopConfig := {})
mtimecmpHiRegIdx := config.mtimecmpHiRegIdx
skipToTimerCompare := true
maxSkip := config.maxSkip
checkInterruptEnable := true
mstatusRegIdx := config.mstatusRegIdx
mieRegIdx := config.mieRegIdx
wfiWireArrayIdx := config.wfiWireArrayIdx
}

end Sparkle.Core.Oracle
Loading
Loading