Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
23fa85b
spec: initial spec commit
erik-3milabs Dec 17, 2025
27da436
spec: Basic chip data format and layout
RobinJadoul Dec 24, 2025
efd7868
spec: Fix some chip rendering pain points (#83)
RobinJadoul Dec 30, 2025
3084695
spec: support array-like types (#85)
erik-3milabs Dec 30, 2025
5e6a7d8
spec: Fixup wrong type sanity check for array types (#86)
RobinJadoul Dec 30, 2025
5157849
Make precedence a lookup table instead of hardcoding it
RobinJadoul Dec 30, 2025
9f9c2b9
Render type cast expressions
RobinJadoul Dec 30, 2025
9ad78c3
spec: Allow desc field on non-arith constraints as clarification
RobinJadoul Dec 30, 2025
03d3011
spec: Modify cast operator precedence
RobinJadoul Dec 31, 2025
f3f21bc
spec: improve definitions (#91)
erik-3milabs Jan 2, 2026
442e327
spec: update table rendering (#93)
erik-3milabs Jan 2, 2026
c271c93
spec: introduce "condition" column type
erik-3milabs Jan 3, 2026
a0801d9
spec: is_bit template
erik-3milabs Jan 3, 2026
37d4cc0
spec: CPU chip for RV64IMC (#88)
RobinJadoul Jan 5, 2026
4b8c801
spec: improve multi-poly definition rendering (#98)
erik-3milabs Jan 5, 2026
217d2d7
spec: BRANCH chip (#92)
RobinJadoul Jan 5, 2026
e53d2a8
spec: conditionally render constraint table headers (#94)
erik-3milabs Jan 5, 2026
1b83850
spec: do not print index in assumption/constraint ref (#96)
erik-3milabs Jan 5, 2026
52ba976
Merge branch 'main' into spec/main
RobinJadoul Jan 6, 2026
07000c7
spec: Make constraint numbering restart when displaying multiple chip…
RobinJadoul Jan 7, 2026
7e842e5
spec: Introduce LT chip (#90)
RobinJadoul Jan 8, 2026
4af29ef
spec: Fix constraint group lookup (#105)
RobinJadoul Jan 8, 2026
9d07e5d
spec: `SHIFT` chip (#84)
erik-3milabs Jan 9, 2026
795a722
spec: `ADD` template (#97)
erik-3milabs Jan 9, 2026
1ba9421
spec: have column table subheaders repeat on page wrap (#121)
erik-3milabs Jan 9, 2026
62fc94b
spec: drop `dot` when multiplying constant with one-letter variable. …
erik-3milabs Jan 9, 2026
bf9662f
spec: `MUL` chip (#122)
erik-3milabs Jan 13, 2026
11a0c64
spec: Add support for specifying padding values of columns (#133)
RobinJadoul Jan 13, 2026
eb3297a
spec: update range specifications to iters concept (#130)
RobinJadoul Jan 13, 2026
22fa781
spec: `BITWISE` chip (#138)
erik-3milabs Jan 14, 2026
52d1522
spec: Initial inefficient MEMW chip (#104)
RobinJadoul Jan 15, 2026
11cd790
spec: LOAD chip (#144)
RobinJadoul Jan 15, 2026
760f446
fix CPU-CA41 typo (#189)
ColoCarletti Jan 20, 2026
a358eed
spec: `DECODE` (#143)
erik-3milabs Jan 21, 2026
1be9a48
 spec: placeholder chapters for chips to come (#190)
RobinJadoul Jan 21, 2026
2d39c55
fix(spec): Use a better precedence value for "idx" (#197)
RobinJadoul Jan 21, 2026
9cb3aff
fix(spec): Missing `write_register` multiplicity. (#196)
RobinJadoul Jan 21, 2026
e68549f
spec: Initial version of memory argument (#164)
RobinJadoul Jan 21, 2026
4ca2a4e
fix(spec): Correct typo in spec README and align style (#210)
cdesaintguilhem Jan 22, 2026
5e6fdc3
spec: CPU padding (#195)
RobinJadoul Jan 23, 2026
a184f95
spec: update `ECALL` signature (#244)
erik-3milabs Jan 27, 2026
37a9a9f
spec: Allow for cross referencing between different chapters, both in…
RobinJadoul Jan 27, 2026
5084c80
spec: Update LT interaction signature so that it can be used properly…
RobinJadoul Jan 27, 2026
c72eefe
spec: `HALT` chip (#235)
erik-3milabs Jan 27, 2026
c902acd
spec: minor `MUL` fixes (#223)
erik-3milabs Jan 29, 2026
d5000f0
spec: `SIGN` (#279)
erik-3milabs Feb 3, 2026
485b93f
spec: drop `IsZero` template (#278)
erik-3milabs Feb 3, 2026
9bb2eed
spec: fix header levels (#264)
erik-3milabs Feb 3, 2026
ed3d883
spec: LOAD: fix LOAD-C9 signature (#284)
erik-3milabs Feb 5, 2026
76a008c
spec: `NEG` template (#270)
erik-3milabs Feb 5, 2026
f86e427
spec: Introduce DVRM chip
erik-3milabs Dec 24, 2025
8f0e8d3
spec: signatures (#280)
erik-3milabs Feb 5, 2026
1b2cfb9
spec: Leverage `NEG` in `DVRM` (#287)
erik-3milabs Feb 6, 2026
742a5bd
spec: Add initial tooling to check data formats, prepare for more ela…
RobinJadoul Feb 10, 2026
26ae833
spec: Introduce array expressions (#295)
RobinJadoul Feb 10, 2026
9fd3bf8
spec: separate ALU path for STORE to enable byte representation of rv…
RobinJadoul Feb 10, 2026
172cf3e
spec: `COMMIT` chip (#283)
erik-3milabs Feb 12, 2026
8af2cb7
spec: Typecheck signatures and make all chips pass (#312)
RobinJadoul Feb 12, 2026
5b81913
spec: Variable category for constants (#327)
RobinJadoul Feb 12, 2026
68457ea
spec: Fix interaction signatures for COMMIT (#328)
RobinJadoul Feb 13, 2026
b0bdd60
spec: Cleanup, uniformize chapters, make colors work better on web. (…
RobinJadoul Feb 17, 2026
2a259a5
spec: LogUp: Vanilla protocol description (#243)
cdesaintguilhem Feb 23, 2026
fa26492
spec: Add a version and title/front pages (#367)
RobinJadoul Feb 27, 2026
fa1b567
Merge branch 'main' into spec/main
diegokingston Mar 9, 2026
376a726
spec: Losing some MEMW weight (#398)
RobinJadoul Mar 13, 2026
42b4c9f
spec: Some fixes and improvements for SHIFT (#400)
RobinJadoul Mar 13, 2026
5cf5369
Fix type checking for MEMW_A (#423)
RobinJadoul Mar 16, 2026
8f7ddea
Spec/memw update (#434)
erik-3milabs Mar 23, 2026
a9c073a
spec/MEMW(_A): minor update (#459)
erik-3milabs Mar 23, 2026
7d4518f
spec/MEMW_R: register access fast path (#457)
erik-3milabs Mar 24, 2026
1247426
spec: Fix CPU sign bit constraints for `word_instr` (#435)
RobinJadoul Mar 25, 2026
950c525
spec: interaction counter (#469)
erik-3milabs Apr 9, 2026
6eecb98
spec: run spec-tooling in CI (#440)
erik-3milabs Apr 10, 2026
34afbba
spec: update TOC (#478)
erik-3milabs Apr 10, 2026
46b4024
spec: cleanup before v0.2 (#479)
erik-3milabs Apr 10, 2026
7ff3235
spec/tooling: add default case in `build_signature`
erik-3milabs Apr 10, 2026
33cf885
spec/tooling: fix hidden global variable
erik-3milabs Apr 10, 2026
9a0f932
spec/tooling: fix silent side effect
erik-3milabs Apr 10, 2026
c895bac
spec/tooling: context manage file reads
erik-3milabs Apr 10, 2026
23179c3
spec: fix precedence
erik-3milabs Apr 10, 2026
13c31a0
Merge branch 'main' into spec/main
erik-3milabs Apr 10, 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
25 changes: 25 additions & 0 deletions .github/workflows/pr_spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Spec tests
on:
pull_request:
branches:
- main
- 'spec/**'
push:
branches: ["**"]
paths: ["spec/**"]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
spec_structure:
name: Spec structure test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- run: python3 spec/tooling/chip.py spec/src/config.toml spec/src/signatures.toml spec/src/*.toml
10 changes: 10 additions & 0 deletions spec/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

[*.typ]
indent_style = space
indent_size = 2
2 changes: 2 additions & 0 deletions spec/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/*
ebook.pdf
11 changes: 11 additions & 0 deletions spec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# LambdaVM specification
This repository contains specification for [`LambdaVM`](https://github.com/yetanotherco/lambda_vm).
The specification is written in [`Typst`](https://typst.app/) and can be rendered by [`shiroa`](https://myriad-dreamin.github.io/shiroa/) as either a file (pdf) or a wiki (html).

## Installation & Development setup
1. [Install `Typst`](https://github.com/typst/typst?tab=readme-ov-file#installation).
2. [Install `shiroa`](https://myriad-dreamin.github.io/shiroa/guide/installation.html).
3. Clone this repository.
4. Open the repository in a terminal and execute `shiroa serve`.

At this point, the wiki version is hosted locally and is actively updated as you modify the specification files.
24 changes: 24 additions & 0 deletions spec/about_ecalls.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import "/book.typ": book-page, aside
#import "/src.typ": load_config, load_chip
#import "/chip.typ": (
render_chip_variable_table,
total_nr_variables,
total_nr_instantiated_columns,
render_constraint_table,
render_chip_assumptions,
render_chip_padding_table,
)

#let config = load_config()

#show: book-page("about_ecalls.typ")

ECALLs provide system-level functionalities to the guest program.

When `ECALL` is executed, it is assumed that:
- register `A7` contains the system call number
#footnote([The RISC-V system call ABI; libriscv.no, #link("https://web.archive.org/web/20260128152107/https://libriscv.no/docs/concepts/syscalls/#the-risc-v-system-call-abi")[[src]]]),
- the arguments are located in registers `A0`-`A6`, and
- the return value is written to `A0`,
where `A0`-`A7` are symbolic names for the registers `x10`-`x17`
#footnote([RISC-V - Register sets; en.wikipedia.org, #link("https://web.archive.org/web/20260209053447/https://en.wikipedia.org/wiki/RISC-V#Register_sets")[[src]]]).
33 changes: 33 additions & 0 deletions spec/add.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#import "/book.typ": book-page, et
#import "/src.typ": load_config, load_chip
#import "/chip.typ": render_chip_variable_table, render_chip_assumptions, render_constraint_table, set_nr_interactions, compute_nr_interactions,

#let config = load_config()
#let chip = load_chip("src/add.toml", config)

#show: book-page(chip.name)

#set_nr_interactions(chip, name: "SUB")
#let nr_interactions = compute_nr_interactions(chip)

#let add = raw(chip.name)
#let sub = raw("SUB")

#add is a constraint template that is used to assert that $#`sum` equiv #`lhs` + #`rhs` (mod 2^64)$, under the condition that `cond` is non-zero.
For ease of notation, we moreover introduce the #sub constraint template
$
#`SUB<diff; lhs, rhs>` := #`ADD<lhs; rhs, diff>`,
$
in both conditional and unconditional versions.
It constrains that $#`diff` equiv #`lhs` - #`rhs` (mod 2^64)$ when the expression `cond` is non-zero.

= Variables
This template introduces #nr_interactions interaction(s).
#render_chip_variable_table(chip, config)

= Assumptions
#render_chip_assumptions(chip, config)

= Constraints
This template introduces the following constraints
#render_constraint_table(chip, config)
45 changes: 45 additions & 0 deletions spec/bitwise.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#import "/book.typ": book-page, rj
#import "/src.typ": load_config, load_chip
#import "/chip.typ": (
render_chip_assumptions,
render_chip_variable_table,
total_nr_variables,
total_nr_instantiated_columns,
render_constraint_table,
)

#let config = load_config()
#let chip = load_chip("src/bitwise.toml", config)

#let bitwise = raw(chip.name)

#show: book-page(chip.name)
#let bitwise = raw(chip.name)

The #bitwise chips deal with precomputed lookup tables for bitwise boolean operations
and convenience functionalities over small domains.

= Variables
#let nr_variables = total_nr_variables(chip)
#let nr_columns = total_nr_instantiated_columns(chip, config)
#let nr_precomputed = ("input", "output").map(c => chip.variables.at(c)).flatten().len()

The #bitwise chip is comprised of #nr_variables variables that are expressed using #nr_columns columns.
Of these, the _input_ and _output_ variables (#nr_precomputed in total) are precomputed.
#render_chip_variable_table(chip, config)

*Note*: This table contains one row for every possible value of `(X, Y, Z)`.
As such, it has length $2^8 dot 2^8 dot 2^4 = 2^(20)$.

= Lookup
This chip adds the following interactions to the lookup:
#render_constraint_table(chip, config)

= Notes/Optimizations
The following ideas may prove to be optimizations for the #bitwise chip:
+ Extend `IS_BYTE[X]` to `ARE_BYTES[X, Y]`, such that two bytes are range checked at once.
When only a single check is required, one can still execute `IS_BYTE[X] := ARE_BYTES[X, 0]`.
+ Drop `MSB8` column, and instead define the `MSB8` lookup as `MSB8<X> := MSB16[256X]`.
Note: currently, `MSB8` also implicity range checks the input `X` (the lookup fails if `X` is not a `Byte`).
This optimization should only be executed when all chips leveraging `MSB8` do _not_ need this implicit range check.
+ Place the 16-bit (`AND`, `OR`, `XOR`, `MSB16`, etc.) and 20-bit (`HWSL`, `IS_B20`, `ZERO`) lookups in separate tables.
185 changes: 185 additions & 0 deletions spec/book.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#import "@preview/shiroa:0.3.1": *
#import "/templates/page.typ": project

#show: book

#let meta = (
title: "Lambda VM specification",
authors: ("3MI Labs", "Aligned"),
version: "0.2",
summary: (
("PROOF SYSTEM", (
("logup.typ", [`LogUp` argument], <logup>),
("memory.typ", [Memory argument], <memory>),
)),
("OVERVIEW", (
("variables.typ", [Variables], <vars>),
("signatures.typ", [Signatures], <signatures>),
)),
("TEMPLATES", (
("is_bit.typ", [`IS_BIT` template], <isbit>),
("sign.typ", [`SIGN` template], <sign>),
("add.typ", [`ADD`/`SUB` template], <add>),
("neg.typ", [`NEG` template], <neg>),
)),
("MEMORY", (
("memw.typ", [`MEMW` chip], <memw>),
)),
("CPU", (
("decode.typ", [`DECODE` table], <decode>),
("cpu.typ", [`CPU` chip], <cpu>),
)),
("ALU", (
("shift.typ", [`SHIFT` chip], <shift>),
("branch.typ", [`BRANCH` chip], <branch>),
("lt.typ", [`LT` chip], <lt>),
("mul.typ", [`MUL` chip], <mul>),
("dvrm.typ", [`DVRM` chip], <dvrm>),
("load.typ", [`LOAD` chip], <load>),
("bitwise.typ", [`BITWISE` chips], <bitwise>),
)),
("ECALLS", (
("about_ecalls.typ", [About `ECALL`], <ecall>),
("halt.typ", [`HALT` chip], <halt>),
("commit.typ", [`COMMIT` chip], <commit>),
))
)
)
#let meta_sections = meta.summary.map(m => m.at(1)).sum()
#book-meta(
title: meta.title,
authors: meta.authors,
summary: prefix-chapter("front.typ", meta.title)
+ meta.summary.map(
((title, sections)) => {
heading(depth: 1, title) + sections.map(((ch, title, _ref)) => chapter(ch, title)).join()
}
).join()
)

#let common-formatting(body) = {
set footnote(numbering: "[1]")
show raw.where(block: true): it => block(it, inset: 1em, width: 100%, radius: 5pt)
body
}


#let todo(background: white, foreground: black, name: none, body) = block(fill: background, outset: 0.4em, radius: 20%, stroke: black)[
#set text(fill: foreground)
*TODO #if name != none { [(#name)] }*: #body
]
#let rj = todo.with(background: teal, name: "Robin")
#let et = todo.with(background: rgb("d4aa3a"), name: "Erik")
#let cdsg = todo.with(background: olive, name: "Cyprien")

#let aside(title, body) = context figure(
block(inset: (left: 1em, right: 1em, bottom: 1em), stroke: luma(50%), breakable: false)[
#block(inset: (left: 1em, right: 1em, top: .75em, bottom: .75em),
width: 100% + 2em,
fill: rgb("55aaff"),
stroke: luma(50%),
align(center, strong(text(fill: black, title))))
#align(left, body)
])


#let is-shiroa = "x-target" in sys.inputs

// Strip styling to keep only "pure" content.
// This is useful to avoid errors on the `set document(...)` in `project`
// when invisibly including other chapters to resolve xrefs.
#let strip-all(content) = {
if repr(content.func()) == "sequence" {
for c in content.children {
strip-all(c)
}
} else if repr(content.func()) == "styled" {
strip-all(content.child)
} else {
content
}
}

#let _toplevel = state("_toplevel", none)
#let _xref-included = state("_xref-included", (:))

// Invisibly include another chapter, so that its labels can be resolved
#let xref-include(f) = {
context {
place(hide(box(width: auto, height: 0%, strip-all(include "/" + f))))
}
}

// Generate a cross-link for references to other chapters.
// Leaves the ref untouched if it can't be resolved or points to the current chapter.
#let xref(rf) = {
assert(is-shiroa, message: "xref should only be used when compiling for shiroa")
let lbl = rf.target
let found = meta_sections.find(((_, _, tag)) => str(lbl).starts-with(str(tag)))
context if found != none and found.at(0) != _toplevel.final() {
let (ch, title, ref) = found
if ref == lbl {
cross-link("/" + ch, [Chapter #(meta_sections.position(x => x == found) + 1)])
} else {
// Because shiroa does weird url escaping
let shiroa-label = label(str(lbl).replace(":", "%3A"))
context _xref-included.update(x => x + ((ch): true))
// The ideal would be to use `rf` directly as content argument to `cross-link`,
// as that would inherit any/all formatting of the ref we want or need.
// Unfortunately the ref link seems to take precedence over the cross-link hyperlink
// when clicking.
// There may still be some way around it by messing with some html output
let link-content = context {
let fig = query(lbl).first()
let counter = if fig.has("counter") {
fig.counter
} else {
counter(fig.func())
}

let supplement = if rf.supplement == auto {
fig.fields().at("supplement", default: none)
} else {
rf.supplement
}
[#supplement#numbering(fig.numbering, ..counter.at(lbl))]
}
cross-link("/" + ch, reference: shiroa-label, link-content)
}
} else {
rf
}
}

#let book-page(file, ..args) = {
if not file.ends-with(".typ") {
file = lower(file) + ".typ"
}

assert(meta_sections.find(s => s.at(0) == file) != none, message: "Couldn't resolve typst source file " + file)

if is-shiroa {
(body) => {
show: common-formatting
context _toplevel.update(s => {
if s == none {
file
} else {
s
}
})
let cond() = _toplevel.final() == file
project.with(..args, title: context meta_sections.find(x => x.at(0) == _toplevel.final()).at(1), cond: cond)([
#show ref: it => context if _toplevel.final() == file {
xref(it)
}
#context _xref-included.final().pairs().map(((key, value)) => context if value and cond() {
xref-include(key)
}).join()
#body
])
}
} else {
body => body
}
}
48 changes: 48 additions & 0 deletions spec/branch.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#import "/book.typ": book-page, rj
#import "/src.typ": load_config, load_chip
#import "/chip.typ": (
render_chip_assumptions,
render_chip_variable_table,
compute_nr_interactions,
total_nr_variables,
total_nr_instantiated_columns,
render_constraint_table,
render_chip_padding_table,
)

#let config = load_config()
#let chip = load_chip("src/branch.toml", config)

#show: book-page(chip.name)
#let branch = raw(chip.name)

The #branch chip computes the target address of a branching instruction.

= Variables
#let nr_variables = total_nr_variables(chip)
#let nr_columns = total_nr_instantiated_columns(chip, config)
#let nr_interactions = compute_nr_interactions(chip)

The #branch chip is comprised of #nr_variables variables that are expressed using #nr_columns columns and leverages #nr_interactions interaction(s):
#render_chip_variable_table(chip, config)

= Assumptions

#render_chip_assumptions(chip, config)

= Constraints

We constrain `next_pc` to be $#`base_address` + #`offset`$,
where `base_address` equals `pc` when $#`JALR` = 0$ and `register` otherwise.

The range checks on `unmasked_low_byte` and `next_pc_low[0]` are performed implicitly by the `AND_BYTE` lookup.
#render_constraint_table(chip, config, groups: "all")

This chip contributes the following to the lookup argument.
#render_constraint_table(chip, config, groups: "output")

= Padding

The table can be padded to the next power of two with the following value assignments:

#render_chip_padding_table(chip, config)
Loading
Loading