mirror of
https://github.com/powdr-labs/powdr.git
synced 2026-04-20 03:03:25 -04:00
Merge pull request #421 from powdr-labs/powdr-book
Create the powdr book
This commit is contained in:
43
.github/workflows/deploy-book.yml
vendored
Normal file
43
.github/workflows/deploy-book.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# adapted from https://github.com/rust-lang/mdBook/wiki/Automated-Deployment%3A-GitHub-Actions#GitHub-Pages-Deploy
|
||||
|
||||
name: Deploy book
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # To push a branch
|
||||
pull-requests: write # To create a PR from that branch
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install latest mdbook
|
||||
run: |
|
||||
tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name')
|
||||
url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz"
|
||||
mkdir mdbook
|
||||
curl -sSL $url | tar -xz --directory=./mdbook
|
||||
echo `pwd`/mdbook >> $GITHUB_PATH
|
||||
- name: Deploy GitHub Pages
|
||||
run: |
|
||||
# generate cli docs and inject them into the book
|
||||
cargo run --package powdr_cli -- --markdown-help > book/src/cli/README.md
|
||||
# build the book
|
||||
cd book
|
||||
mdbook build
|
||||
git worktree add gh-pages
|
||||
git config user.name "Deploy from CI"
|
||||
git config user.email ""
|
||||
cd gh-pages
|
||||
# Delete the ref to avoid keeping history.
|
||||
git update-ref -d refs/heads/gh-pages
|
||||
rm -rf *
|
||||
mv ../book/* .
|
||||
git add .
|
||||
git commit -m "Deploy $GITHUB_SHA to gh-pages"
|
||||
git push --force --set-upstream origin gh-pages
|
||||
46
README.md
46
README.md
@@ -43,52 +43,6 @@ The assembly language is designed to be extensible. This means that it does not
|
||||
native instruction. Instead, all instructions are user-defined and because of that,
|
||||
it is easy to adapt *powdr* assembly to any VM.
|
||||
|
||||
## How to run the Rust-RISCV example
|
||||
|
||||
```sh
|
||||
# Install the riscv target for the rust compiler
|
||||
rustup target add riscv32imc-unknown-none-elf
|
||||
# Run the compiler. It will generate files in /tmp/.
|
||||
# -i specifies the prover witness input (see below)
|
||||
cargo run --release rust riscv/tests/riscv_data/sum.rs -o /tmp -f -i 10,2,4,6
|
||||
```
|
||||
|
||||
The following example Rust file verifies that a supplied list of integers sums up to a specified value.
|
||||
Note that this is the full and only input file you need for the whole process!
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use runtime::get_prover_input;
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
// This is the sum claimed by the prover.
|
||||
let proposed_sum = get_prover_input(0);
|
||||
// The number of integers we want to sum.
|
||||
let len = get_prover_input(1) as usize;
|
||||
// Read the numbers from the prover and store them
|
||||
// in a vector.
|
||||
let data: Vec<_> = (2..(len + 2))
|
||||
.map(|idx| get_prover_input(idx as u32))
|
||||
.collect();
|
||||
// Compute the sum.
|
||||
let sum: u32 = data.iter().sum();
|
||||
// Check that our sum matches the prover's.
|
||||
assert_eq!(sum, proposed_sum);
|
||||
}
|
||||
```
|
||||
|
||||
The function `get_prover_input` reads a number from the list supplied with `-i`.
|
||||
|
||||
This is just a first mechanism to provide access to the outside world.
|
||||
The plan is to be able to call arbitrary user-defined `ffi` functions that will translate to prover queries,
|
||||
and can then ask for e.g. the value of a storage slot at a certain address or the
|
||||
root hash of a merkle tree.
|
||||
|
||||
### Notes on Efficiency
|
||||
|
||||
Currently, the code is extremely wasteful. It generates many unnecessary columns.
|
||||
|
||||
1
book/.gitignore
vendored
Normal file
1
book/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
book
|
||||
14
book/README.md
Normal file
14
book/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# the powdr book
|
||||
|
||||
This is the powdr book.
|
||||
|
||||
## Contributing
|
||||
|
||||
To serve the book:
|
||||
|
||||
```
|
||||
cargo install mdbook
|
||||
mdbook serve
|
||||
```
|
||||
|
||||
Then put changes in the `src` folder.
|
||||
6
book/book.toml
Normal file
6
book/book.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[book]
|
||||
authors = ["schaeff"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "powdr"
|
||||
21
book/src/README.md
Normal file
21
book/src/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Introduction
|
||||
|
||||
**powdr** is a modular compiler stack to build zkVMs.
|
||||
It is ideal for implementing existing VMs and experimenting with new designs with minimal boilerplate.
|
||||
|
||||
* Domain specific languages are used to specify the VM and its underlying constraints, not low level Rust code
|
||||
* Automated witness generation
|
||||
* Support for multiple provers as well as aggregation schemes
|
||||
* Support for hand-optimized co-processors when performance is critical
|
||||
* Built in Rust 🦀
|
||||
|
||||
## Contributing
|
||||
|
||||
powdr is free and open source. You can find the source code on
|
||||
[GitHub](https://github.com/powdr-labs/powdr). Issues and feature requests can be posted on
|
||||
the [GitHub issue tracker](https://github.com/powdr-labs/powdr/issues).
|
||||
|
||||
## License
|
||||
|
||||
The powdr source and documentation are released under
|
||||
the [MIT License](https://opensource.org/license/mit/).
|
||||
26
book/src/SUMMARY.md
Normal file
26
book/src/SUMMARY.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Summary
|
||||
|
||||
[Introduction](./README.md)
|
||||
|
||||
# Getting Started
|
||||
- [Installation](./installation.md)
|
||||
- [Hello World](./hello_world.md)
|
||||
|
||||
# Reference Guide
|
||||
- [CLI](./cli/README.md)
|
||||
- [asm](./asm/README.md)
|
||||
- [Machines](./asm/machines.md)
|
||||
- [Registers](./asm/registers.md)
|
||||
- [Functions](./asm/functions.md)
|
||||
- [Expressions](./asm/expressions.md)
|
||||
- [Instructions](./asm/instructions.md)
|
||||
- [PIL](./pil/README.md)
|
||||
- [Fixed Columns](./pil/fixed_columns.md)
|
||||
- [Macros](./pil/macros.md)
|
||||
- [Frontends](./frontends/README.md)
|
||||
- [RISCV](./frontends/riscv.md)
|
||||
- [Valida](./frontends/valida.md)
|
||||
- [EVM](./frontends/evm.md)
|
||||
- [Backends](./backends/README.md)
|
||||
- [Halo2](./backends/halo2.md)
|
||||
- [eSTARK](./backends/estark.md)
|
||||
3
book/src/asm/README.md
Normal file
3
book/src/asm/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# asm
|
||||
|
||||
powdr asm is the higher level of abstraction in powdr. It allows defining Instruction Set Architectures (ISA) using dynamic and static machines.
|
||||
23
book/src/asm/expressions.md
Normal file
23
book/src/asm/expressions.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Expressions
|
||||
|
||||
## Field element literals
|
||||
|
||||
Field element literals are signed elements of the prime field.
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:literals}}
|
||||
```
|
||||
|
||||
## Registers and columns
|
||||
|
||||
Registers can be used as expressions, with the exception of assignment registers.
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:read_register}}
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
Instructions which return outputs can be used as expressions.
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:instruction}}
|
||||
```
|
||||
49
book/src/asm/functions.md
Normal file
49
book/src/asm/functions.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Functions
|
||||
|
||||
Machine functions are the entry points to a machine. They can be called from another machine or from the outside.
|
||||
|
||||
For static machines, functions simply indicate which columns should be exposed as inputs and outputs. They do not contain statements, since the business logic of all functions in a static machine is defined directly in constraints. We refer to the previous example [here](./machines.md#static-machines).
|
||||
|
||||
In the rest of this section, we describe functions in dynamic machines with the example of this simple machine:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:all}}
|
||||
```
|
||||
|
||||
## Function inputs and outputs
|
||||
|
||||
> For dynamic machines, function inputs and outputs are not supported yet
|
||||
|
||||
## Statements
|
||||
|
||||
### Labels
|
||||
|
||||
Labels allow referring to a location in a function by name.
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:label}}
|
||||
```
|
||||
|
||||
### Assignments
|
||||
|
||||
Assignments allow setting the value of a write register to the value of an [expression](#expressions) using an assignment register.
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:instruction}}
|
||||
```
|
||||
|
||||
One important requirement is for the assignment register of the assignment to be compatible with that of the expression. This is especially relevant for instructions: the assignment register of the instruction output must match that of the assignment. In this example, we use `Y` in the assignment as the output of `square` is `Y`:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:square}}
|
||||
```
|
||||
|
||||
> Specifying the assignment register of the assignment is currently required even in cases where it could be inferred. That restriction may be lifted in the future.
|
||||
|
||||
### Instructions
|
||||
|
||||
Instructions which do not return outputs can be used as statements.
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/function.asm:instruction_statement}}
|
||||
```
|
||||
30
book/src/asm/instructions.md
Normal file
30
book/src/asm/instructions.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Instructions
|
||||
|
||||
Instructions are declared as part of a powdr asm machine. Their inputs and outputs are [assignment registers](./registers.md) as well as labels. Once defined, they can be called by any function of this machine.
|
||||
|
||||
# Local instructions
|
||||
|
||||
A local instruction is the simplest type of instruction. It is called local because its behavior is defined using constraints over registers and columns of the machine it is defined in.
|
||||
|
||||
```
|
||||
instr add X, Y -> Z {
|
||||
X + Y = Z
|
||||
}
|
||||
```
|
||||
|
||||
Instructions feature:
|
||||
- a name
|
||||
- some inputs
|
||||
- some outputs
|
||||
- a set of PIL constraints to activate when the instruction is called
|
||||
|
||||
# External instructions
|
||||
|
||||
An external instruction delegates calls to a function inside a submachine of this machine. When it is called, a call is made to the submachine function. An example of an external instruction is the following:
|
||||
|
||||
```
|
||||
instr assert_zero X = my_submachine.assert_zero // where `assert_zero` is a function defined in `my_submachine`
|
||||
```
|
||||
|
||||
Note that external instructions cannot link to functions of the same machine: they delegate computation to a submachine.
|
||||
|
||||
56
book/src/asm/machines.md
Normal file
56
book/src/asm/machines.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Machines
|
||||
|
||||
Machines are the first main concept in powdr asm. They can currently be of two types: dynamic or static.
|
||||
|
||||
## Dynamic machines
|
||||
|
||||
Dynamic machines are defined by:
|
||||
- a degree, indicating the number of execution steps
|
||||
- a set of registers, including a program counter
|
||||
- an instruction set
|
||||
- constraints
|
||||
- a set of functions
|
||||
- a set of submachines
|
||||
|
||||
> Dynamic machines are currently limited to having a single function called `main`
|
||||
|
||||
An example of a simple dynamic machine is the following:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/hello_world.asm}}
|
||||
```
|
||||
|
||||
## Static machines
|
||||
|
||||
Static machines are a lower-level type of machine. They do not have registers, and instead rely on simple committed and fixed columns. They are used to implement hand-optimized computation.
|
||||
|
||||
They are defined by:
|
||||
- a degree, indicating the number of execution steps
|
||||
- a latch, used to identify rows at which the machine can be accessed from the outside (where the inputs and outputs are passed)
|
||||
- a set of functions
|
||||
|
||||
> Currently, the latch must be a fixed column named `latch`
|
||||
|
||||
An example of a simple static machine is the following:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/simple_static.asm}}
|
||||
```
|
||||
|
||||
For more details on the constraints, check out the [pil](../pil) section of this book. Note that the parameters of the function are columns declared within the constraints block.
|
||||
|
||||
## Submachines
|
||||
|
||||
Machines can have submachines which they access by defining [external instructions](./instructions.md). They are declared as follows:
|
||||
|
||||
```
|
||||
machine MySubmachine {
|
||||
...
|
||||
}
|
||||
|
||||
machine MyMachine {
|
||||
MySubmachine my_submachine;
|
||||
}
|
||||
```
|
||||
|
||||
> Currently only dynamic machines can have submachines, and these must be static
|
||||
40
book/src/asm/registers.md
Normal file
40
book/src/asm/registers.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Registers
|
||||
|
||||
Registers are central to a machine. powdr supports a few types of registers:
|
||||
|
||||
## Program counter
|
||||
|
||||
Each machine can have at most one program counter. In the absence of a program counter, the machine is considered static, and no other register can be declared. The program counter is defined as follows:
|
||||
|
||||
```
|
||||
reg pc[@pc]
|
||||
```
|
||||
|
||||
At each step execution step, the program counter points to the [function](./functions.md) line to execute.
|
||||
The program counter behaves like a [write register](#write-registers), with the exception that its value is incremented by default after each step.
|
||||
|
||||
## Write registers
|
||||
|
||||
Write registers are the default type for registers. They are declared as follows:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/write_register.asm:declaration}}
|
||||
```
|
||||
|
||||
They hold a field element, are initialized as 0 at the beginning of a function and keep their value by default. They can be read from and written to.
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/write_register.asm:component}}
|
||||
```
|
||||
|
||||
## Assignment registers
|
||||
|
||||
Assignment registers are transient to an execution step: their value is not persisted across steps. They are required in order to pass inputs and receive outputs from instructions, as well as in assignments.
|
||||
For example, if we want to assert that write register `A` is `0`, we can use the following instruction:
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/assert_write_register.asm:component}}
|
||||
```
|
||||
However, if we want the instruction to accept any write register as input, we use an assignment register.
|
||||
```
|
||||
{{#include ../../../test_data/asm/book/assert_assignment_register.asm:component}}
|
||||
```
|
||||
3
book/src/backends/README.md
Normal file
3
book/src/backends/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Backends
|
||||
|
||||
powdr aims to have full flexibility when it comes to generating proofs and comes with a few built-in backends to get started with zkVMs.
|
||||
3
book/src/backends/estark.md
Normal file
3
book/src/backends/estark.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# eSTARK
|
||||
|
||||
Integrated support for [eSTARK](https://eprint.iacr.org/2023/474) with the Goldilocks field is under development.
|
||||
3
book/src/backends/halo2.md
Normal file
3
book/src/backends/halo2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Halo2
|
||||
|
||||
powdr supports the [PSE fork of halo2](https://github.com/privacy-scaling-explorations/halo2) with the bn254 field.
|
||||
1
book/src/cli/.gitignore
vendored
Normal file
1
book/src/cli/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
README.md
|
||||
3
book/src/frontends/README.md
Normal file
3
book/src/frontends/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Frontends
|
||||
|
||||
While any frontend VM can be implemented in powdr asm, powdr comes with several frontends for popular instruction set architectures.
|
||||
3
book/src/frontends/evm.md
Normal file
3
book/src/frontends/evm.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# EVM
|
||||
|
||||
An [EVM](https://ethereum.org/en/developers/docs/evm/) frontend for powdr is under development. If you are interested, feel free to [reach out](https://matrix.to/#/#powdr:matrix.org)!
|
||||
48
book/src/frontends/riscv.md
Normal file
48
book/src/frontends/riscv.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# RISCV
|
||||
|
||||
A [RISCV](https://riscv.org/technical/specifications/) frontend for powdr is already available.
|
||||
|
||||
## How to run the Rust-RISCV example
|
||||
|
||||
```sh
|
||||
# Install the riscv target for the rust compiler
|
||||
rustup target add riscv32imc-unknown-none-elf
|
||||
# Run the compiler. It will generate files in /tmp/.
|
||||
# -i specifies the prover witness input (see below)
|
||||
powdr rust riscv/tests/riscv_data/sum.rs -o /tmp -f -i 10,2,4,6
|
||||
```
|
||||
|
||||
The following example Rust file verifies that a supplied list of integers sums up to a specified value.
|
||||
Note that this is the full and only input file you need for the whole process!
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use runtime::get_prover_input;
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
// This is the sum claimed by the prover.
|
||||
let proposed_sum = get_prover_input(0);
|
||||
// The number of integers we want to sum.
|
||||
let len = get_prover_input(1) as usize;
|
||||
// Read the numbers from the prover and store them
|
||||
// in a vector.
|
||||
let data: Vec<_> = (2..(len + 2))
|
||||
.map(|idx| get_prover_input(idx as u32))
|
||||
.collect();
|
||||
// Compute the sum.
|
||||
let sum: u32 = data.iter().sum();
|
||||
// Check that our sum matches the prover's.
|
||||
assert_eq!(sum, proposed_sum);
|
||||
}
|
||||
```
|
||||
|
||||
The function `get_prover_input` reads a number from the list supplied with `-i`.
|
||||
|
||||
This is just a first mechanism to provide access to the outside world.
|
||||
The plan is to be able to call arbitrary user-defined `ffi` functions that will translate to prover queries,
|
||||
and can then ask for e.g. the value of a storage slot at a certain address or the root hash of a Merkle tree.
|
||||
3
book/src/frontends/valida.md
Normal file
3
book/src/frontends/valida.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Valida
|
||||
|
||||
A [Valida](https://github.com/valida-xyz/valida-compiler/issues/2) front end for powdr is under development. If you are interested, feel free to [reach out](https://matrix.to/#/#powdr:matrix.org)!
|
||||
22
book/src/hello_world.md
Normal file
22
book/src/hello_world.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Hello World
|
||||
|
||||
Let's write [a minimal VM](https://github.com/powdr-labs/powdr/blob/main/test_data/asm/book/hello_world.asm) and generate a SNARK!
|
||||
|
||||
```
|
||||
{{#include ../../test_data/asm/book/hello_world.asm}}
|
||||
```
|
||||
|
||||
Then let's generate a proof of execution for the valid prover input `0` (since for `0 + 1 - 1 == 0`)
|
||||
|
||||
```console
|
||||
powdr pil hello_world.asm --field bn254 --force --inputs 0 --prove-with halo2
|
||||
```
|
||||
|
||||
We observe that a proof was created at `proof.bin`.
|
||||
Now let's try for the invalid input `1`
|
||||
|
||||
```console
|
||||
powdr pil hello_world.asm --field bn254 --force --inputs 1 --prove-with halo2
|
||||
```
|
||||
|
||||
We observe that witness generation fails, and no proof is created.
|
||||
29
book/src/installation.md
Normal file
29
book/src/installation.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Installation
|
||||
|
||||
The only way to install powdr currently is to build it from source.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need the [Rust](https://rust-lang.org) compiler and Cargo, the Rust package manager.
|
||||
The easiest way to install both is with [`rustup.rs`](https://rustup.rs/).
|
||||
|
||||
On Windows, you will also need a recent version of [Visual Studio](https://visualstudio.microsoft.com/downloads/),
|
||||
installed with the "Desktop Development With C++" Workloads option.
|
||||
|
||||
## Building
|
||||
|
||||
Using a single Cargo command:
|
||||
|
||||
```sh
|
||||
cargo install --git https://github.com/powdr-labs/powdr powdr_cli
|
||||
```
|
||||
|
||||
Or, by manually building from a local copy of the [powdr repository](https://github.com/powdr-labs/powdr):
|
||||
|
||||
```sh
|
||||
# clone the repository
|
||||
git clone https://github.com/powdr-labs/powdr.git
|
||||
cd powdr
|
||||
# install powdr_cli
|
||||
cargo install --path ./powdr_cli
|
||||
```
|
||||
3
book/src/pil/README.md
Normal file
3
book/src/pil/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# PIL
|
||||
|
||||
powdr PIL is the lower level of abstraction in powdr. It is strongly inspired by and compatible with [Polygon zkEVM PIL](https://github.com/0xPolygonHermez/pilcom/). We refer to the [Polygon zkEVM PIL documentation](https://zkevm.polygon.technology/PIL/introduction) and document deviations from the original design here.
|
||||
34
book/src/pil/fixed_columns.md
Normal file
34
book/src/pil/fixed_columns.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Fixed columns
|
||||
|
||||
powdr PIL requires the definition of fixed columns at the time of declaration.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fixed_columns.pil:declare_and_define}}
|
||||
```
|
||||
|
||||
A number of mechanisms are supported to declare fixed columns. Let `N` be the total length of the column we're defining.
|
||||
|
||||
## Values with repetitions
|
||||
|
||||
powdr PIL supports a basic language to define the value of constant columns using:
|
||||
- arrays, for example `[1, 2, 3]`
|
||||
- repetition, for example `[1, 2]*`
|
||||
- concatenation, for example `[1, 2] + [3, 4]`
|
||||
|
||||
These mechanisms can be combined, as long as a single repetition is used per column definition.
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fixed_columns.pil:repetitions}}
|
||||
```
|
||||
|
||||
## Mappings
|
||||
|
||||
A column can be seen as a mapping from integers to field elements. In this context, different functions are supported:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fixed_columns.pil:mapping}}
|
||||
```
|
||||
|
||||
> Note that conversion from integer to field element is currently implicit, as seen in the first example above.
|
||||
34
book/src/pil/macros.md
Normal file
34
book/src/pil/macros.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Macros
|
||||
|
||||
powdr PIL exposes a macro system which can generate arbitrary powdr PIL code.
|
||||
|
||||
## Definition
|
||||
|
||||
Let's define some macros which generate powdr PIL expressions:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fib_macro.pil:expression_macro_definitions}}
|
||||
```
|
||||
|
||||
In particular, we can generate constraints inside macros:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fib_macro.pil:constraint_macro_definitions}}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
> Macros currently have global scope
|
||||
|
||||
Usage of the defined macros happens as expected in powdr PIL code:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fib_macro.pil:expression_macro_usage}}
|
||||
```
|
||||
|
||||
Generating constraints:
|
||||
|
||||
```
|
||||
{{#include ../../../test_data/pil/fib_macro.pil:constraint_macro_usage}}
|
||||
```
|
||||
|
||||
BIN
book/src/powdr_logo_pink.png
Normal file
BIN
book/src/powdr_logo_pink.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
book/theme/favicon.png
Normal file
BIN
book/theme/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
14
book/theme/favicon.svg
Normal file
14
book/theme/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="717" height="717" viewBox="0 0 717 717" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="358.5" cy="358.5" r="358.5" fill="#FF88EC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M261.74 331.461C246.692 331.461 234.493 343.686 234.493 358.766C234.493 373.847 246.692 386.072 261.74 386.072C276.788 386.072 288.987 373.847 288.987 358.766C288.987 343.686 276.788 331.461 261.74 331.461ZM211.897 358.766C211.897 331.181 234.213 308.818 261.74 308.818C289.267 308.818 311.582 331.181 311.582 358.766C311.582 386.352 289.267 408.715 261.74 408.715C234.213 408.715 211.897 386.352 211.897 358.766Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M397.977 313.48V372.065C397.977 392.371 381.562 408.848 361.293 408.848C341.024 408.848 324.609 392.371 324.609 372.065V313.48H347.204V372.065C347.204 379.883 353.521 386.205 361.293 386.205C369.065 386.205 375.382 379.883 375.382 372.065L375.382 313.48L397.977 313.48Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M448.749 313.48V372.065C448.749 392.371 432.334 408.848 412.065 408.848C391.797 408.848 375.381 392.371 375.381 372.065V313.48H397.977V372.065C397.977 379.883 404.293 386.205 412.065 386.205C419.838 386.205 426.154 379.883 426.154 372.065L426.154 313.48L448.749 313.48Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M624.326 331.461C609.278 331.461 597.079 343.686 597.079 358.766H574.483C574.483 331.181 596.799 308.818 624.326 308.818V331.461Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M574.484 403.52L574.484 358.766L597.08 358.766L597.08 403.52L574.484 403.52Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M153.817 331.461C138.769 331.461 126.57 343.686 126.57 358.766C126.57 373.847 138.769 386.072 153.817 386.072C168.865 386.072 181.064 373.847 181.064 358.766C181.064 343.686 168.865 331.461 153.817 331.461ZM103.975 358.766C103.975 331.181 126.29 308.818 153.817 308.818C181.344 308.818 203.659 331.181 203.659 358.766C203.659 386.352 181.344 408.715 153.817 408.715C126.29 408.715 103.975 386.352 103.975 358.766Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M103.976 390.201L103.976 358.766L126.571 358.766L126.571 390.201L103.976 390.201Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.6741 426.404C102.946 411.451 103.974 396.603 103.974 390.201L126.569 390.201C126.569 398.953 125.18 419.016 111.286 439.243L92.6741 426.404Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M561.461 326.799L561.461 358.766L538.866 358.766L538.866 326.799L561.461 326.799Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M511.616 386.205C526.664 386.205 538.863 373.98 538.863 358.9C538.863 343.819 526.664 331.594 511.616 331.594C496.568 331.594 484.369 343.819 484.369 358.9C484.369 373.98 496.568 386.205 511.616 386.205ZM561.459 358.9C561.458 386.485 539.143 408.848 511.616 408.848C484.089 408.848 461.774 386.485 461.774 358.9C461.774 331.314 484.089 308.951 511.616 308.951C539.143 308.951 561.459 331.314 561.459 358.9Z" fill="#FCF9F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M572.76 290.596C562.489 305.549 561.461 320.397 561.461 326.799L538.866 326.799C538.866 318.047 540.254 297.984 554.149 277.757L572.76 290.596Z" fill="#FCF9F6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -85,3 +85,24 @@ fn full_pil_constant() {
|
||||
verify_asm::<GoldilocksField>(f, Default::default());
|
||||
gen_proof(f, Default::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn book() {
|
||||
for f in fs::read_dir("../test_data/asm/book/").unwrap() {
|
||||
let f = f.unwrap().path();
|
||||
let f = f.strip_prefix("../test_data/asm/").unwrap();
|
||||
// passing 0 to all tests currently works as they either take no prover input or 0 works
|
||||
let i = [0];
|
||||
|
||||
verify_asm::<GoldilocksField>(f.to_str().unwrap(), slice_to_vec(&i));
|
||||
gen_proof(f.to_str().unwrap(), slice_to_vec(&i));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Witness generation failed."]
|
||||
fn hello_world_asm_fail() {
|
||||
let f = "book/hello_world.asm";
|
||||
let i = [1];
|
||||
verify_asm::<GoldilocksField>(f, slice_to_vec(&i));
|
||||
}
|
||||
|
||||
@@ -132,3 +132,8 @@ fn test_single_line_blocks() {
|
||||
fn test_two_block_machine_functions() {
|
||||
verify_pil("two_block_machine_functions.pil", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fixed_columns() {
|
||||
verify_pil("fixed_columns.pil", None);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ halo2 = { path = "../halo2", optional = true }
|
||||
backend = { path = "../backend" }
|
||||
pilopt = { path = "../pilopt" }
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
clap-markdown = "0.1.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.6"
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
mod util;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use compiler::{compile_pil_or_asm, Backend};
|
||||
use env_logger::{Builder, Target};
|
||||
use log::LevelFilter;
|
||||
use number::{Bn254Field, FieldElement, GoldilocksField};
|
||||
use riscv::{compile_riscv_asm, compile_rust};
|
||||
use std::io::BufWriter;
|
||||
use std::io::{self, BufWriter};
|
||||
use std::{borrow::Cow, collections::HashSet, fs, io::Write, path::Path};
|
||||
use strum::{Display, EnumString, EnumVariantNames};
|
||||
|
||||
@@ -34,10 +34,13 @@ pub enum CsvRenderMode {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(name = "powdr", author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(long, hide = true)]
|
||||
markdown_help: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -233,7 +236,7 @@ fn split_inputs<T: FieldElement>(inputs: &str) -> Vec<T> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), io::Error> {
|
||||
let mut builder = Builder::new();
|
||||
builder
|
||||
.filter_level(LevelFilter::Info)
|
||||
@@ -242,8 +245,17 @@ fn main() {
|
||||
.format(|buf, record| writeln!(buf, "{}", record.args()))
|
||||
.init();
|
||||
|
||||
let command = Cli::parse().command;
|
||||
run_command(command);
|
||||
let args = Cli::parse();
|
||||
|
||||
if args.markdown_help {
|
||||
clap_markdown::print_help_markdown::<Cli>();
|
||||
Ok(())
|
||||
} else if let Some(command) = args.command {
|
||||
run_command(command);
|
||||
Ok(())
|
||||
} else {
|
||||
Cli::command().print_help()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_command(command: Commands) {
|
||||
@@ -524,7 +536,6 @@ fn optimize_and_output<T: FieldElement>(file: &str) {
|
||||
mod test {
|
||||
|
||||
use backend::Backend;
|
||||
use tempfile;
|
||||
|
||||
use crate::{run_command, Commands, CsvRenderMode, FieldArgument};
|
||||
#[test]
|
||||
|
||||
23
test_data/asm/book/assert_assignment_register.asm
Normal file
23
test_data/asm/book/assert_assignment_register.asm
Normal file
@@ -0,0 +1,23 @@
|
||||
machine Machine {
|
||||
|
||||
degree 8;
|
||||
|
||||
// ANCHOR: component
|
||||
reg pc[@pc];
|
||||
reg X[<=];
|
||||
reg A;
|
||||
|
||||
instr assert_zero X {
|
||||
X = 0
|
||||
}
|
||||
|
||||
function main {
|
||||
assert_zero A;
|
||||
loop;
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
|
||||
instr loop {
|
||||
pc' = pc
|
||||
}
|
||||
}
|
||||
22
test_data/asm/book/assert_write_register.asm
Normal file
22
test_data/asm/book/assert_write_register.asm
Normal file
@@ -0,0 +1,22 @@
|
||||
machine Machine {
|
||||
|
||||
degree 8;
|
||||
|
||||
// ANCHOR: component
|
||||
reg pc[@pc];
|
||||
reg A;
|
||||
|
||||
instr assert_A_is_zero {
|
||||
A = 0
|
||||
}
|
||||
|
||||
function main {
|
||||
assert_A_is_zero;
|
||||
loop;
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
|
||||
instr loop {
|
||||
pc' = pc
|
||||
}
|
||||
}
|
||||
81
test_data/asm/book/function.asm
Normal file
81
test_data/asm/book/function.asm
Normal file
@@ -0,0 +1,81 @@
|
||||
/* ANCHOR: all */
|
||||
|
||||
machine Machine {
|
||||
|
||||
degree 256;
|
||||
|
||||
reg pc[@pc];
|
||||
reg X[<=];
|
||||
reg Y[<=];
|
||||
reg CNT;
|
||||
reg A;
|
||||
|
||||
// an instruction to assert that a number is zero
|
||||
instr assert_zero X {
|
||||
X = 0
|
||||
}
|
||||
|
||||
// an instruction to jump to a label
|
||||
instr jmp l: label {
|
||||
pc' = l
|
||||
}
|
||||
|
||||
// an instruction to jump to a label iff `X` is `0`, otherwise continue
|
||||
instr jmpz X, l: label {
|
||||
pc' = XIsZero * l + (1 - XIsZero) * (pc + 1)
|
||||
}
|
||||
|
||||
// an instruction to return the square of an input
|
||||
// ANCHOR: square
|
||||
instr square X -> Y {
|
||||
Y = X * X
|
||||
}
|
||||
// ANCHOR_END: square
|
||||
|
||||
function main {
|
||||
// initialise `A` to 2
|
||||
A <=X= 2;
|
||||
// initialise `CNT` to `3`
|
||||
// ANCHOR: literals
|
||||
CNT <=X= 3;
|
||||
// ANCHOR_END: literals
|
||||
// ANCHOR: label
|
||||
start::
|
||||
// ANCHOR_END: label
|
||||
// if `CNT` is `0`, jump to `end`
|
||||
jmpz CNT, end;
|
||||
// decrement `CNT`
|
||||
// ANCHOR: read_register
|
||||
CNT <=X= CNT - 1;
|
||||
// ANCHOR_END: read_register
|
||||
// square `A`
|
||||
// ANCHOR: instruction
|
||||
A <=Y= square(A);
|
||||
// ANCHOR_END: instruction
|
||||
// jump back to `start`
|
||||
jmp start;
|
||||
end::
|
||||
// check that `A == ((2**2)**2)**2`
|
||||
// ANCHOR: instruction_statement
|
||||
assert_zero A - ((2**2)**2)**2;
|
||||
// ANCHOR_END: instruction_statement
|
||||
// loop forever
|
||||
loop;
|
||||
}
|
||||
|
||||
// an instruction to loop forever
|
||||
instr loop {
|
||||
pc' = pc
|
||||
}
|
||||
|
||||
// some superpowers on `X` to allow us to check if it's 0
|
||||
constraints {
|
||||
col witness XInv;
|
||||
col witness XIsZero;
|
||||
XIsZero = 1 - X * XInv;
|
||||
XIsZero * X = 0;
|
||||
XIsZero * (1 - XIsZero) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ANCHOR_END: all */
|
||||
41
test_data/asm/book/hello_world.asm
Normal file
41
test_data/asm/book/hello_world.asm
Normal file
@@ -0,0 +1,41 @@
|
||||
machine HelloWorld {
|
||||
|
||||
degree 8;
|
||||
|
||||
// this simple machine does not have submachines
|
||||
|
||||
reg pc[@pc];
|
||||
reg X[<=];
|
||||
reg Y[<=];
|
||||
reg A;
|
||||
|
||||
instr incr X -> Y {
|
||||
Y = X + 1
|
||||
}
|
||||
|
||||
instr decr X -> Y {
|
||||
Y = X - 1
|
||||
}
|
||||
|
||||
// an instruction to loop forever, as we must fill the whole execution trace
|
||||
instr loop {
|
||||
pc' = pc
|
||||
}
|
||||
|
||||
instr assert_zero X {
|
||||
X = 0
|
||||
}
|
||||
|
||||
constraints {
|
||||
// in this machine, we do not add more constraints
|
||||
}
|
||||
|
||||
// the main function assigns the first prover input to A, increments it, decrements it, and loops forever
|
||||
function main {
|
||||
A <=X= ${ ("input", 0) };
|
||||
A <=Y= incr(A);
|
||||
A <=Y= decr(A);
|
||||
assert_zero A;
|
||||
loop;
|
||||
}
|
||||
}
|
||||
22
test_data/asm/book/simple_static.asm
Normal file
22
test_data/asm/book/simple_static.asm
Normal file
@@ -0,0 +1,22 @@
|
||||
machine SimpleStatic {
|
||||
|
||||
degree 8;
|
||||
|
||||
function power_4 x -> y {
|
||||
}
|
||||
|
||||
constraints {
|
||||
col fixed latch = [0, 0, 0, 1]*;
|
||||
col witness x;
|
||||
col witness y;
|
||||
|
||||
// initialise y to x at the beginning of each block
|
||||
latch * (y' - x') = 0;
|
||||
// x is unconstrained at the beginning of the block
|
||||
|
||||
// x is constant within a block
|
||||
(1 - latch) * (x' - x) = 0;
|
||||
// y is multiplied by x at each row
|
||||
(1 - latch) * (y' - x * y) = 0;
|
||||
}
|
||||
}
|
||||
29
test_data/asm/book/write_register.asm
Normal file
29
test_data/asm/book/write_register.asm
Normal file
@@ -0,0 +1,29 @@
|
||||
machine Machine {
|
||||
|
||||
degree 8;
|
||||
|
||||
reg pc[@pc];
|
||||
reg X[<=];
|
||||
// ANCHOR: declaration
|
||||
reg A;
|
||||
// ANCHOR_END: declaration
|
||||
reg B;
|
||||
|
||||
function main {
|
||||
// ANCHOR: component
|
||||
// write to A
|
||||
A <=X= 1;
|
||||
// A is 1
|
||||
|
||||
// read from A
|
||||
B <=X= A;
|
||||
// A is still 1
|
||||
// ANCHOR_END: component
|
||||
|
||||
loop;
|
||||
}
|
||||
|
||||
instr loop {
|
||||
pc' = pc
|
||||
}
|
||||
}
|
||||
@@ -4,24 +4,31 @@ namespace Fibonacci(%N);
|
||||
constant %last_row = %N - 1;
|
||||
|
||||
macro bool(X) { X * (1 - X) = 0; };
|
||||
macro is_nonzero(X) { match X { 0 => 0, _ => 1, } };
|
||||
macro is_zero(X) { 1 - is_nonzero(X) };
|
||||
macro is_equal(A, B) { is_zero(A - B) };
|
||||
macro is_one(X) { is_equal(X, 1) };
|
||||
macro ite(C, A, B) { is_nonzero(C) * A + is_zero(C) * B};
|
||||
// ANCHOR: expression_macro_definitions
|
||||
macro is_nonzero(X) { match X { 0 => 0, _ => 1, } };
|
||||
macro is_zero(X) { 1 - is_nonzero(X) };
|
||||
macro is_equal(A, B) { is_zero(A - B) };
|
||||
macro is_one(X) { is_equal(X, 1) };
|
||||
macro ite(C, A, B) { is_nonzero(C) * A + is_zero(C) * B};
|
||||
macro one_hot(i, index) { ite(is_equal(i, index), 1, 0) };
|
||||
// ANCHOR_END: expression_macro_definitions
|
||||
|
||||
macro one_hot(i, index) { ite(is_equal(i, index), 1, 0) };
|
||||
|
||||
pol constant ISLAST(i) { one_hot(i, %last_row) };
|
||||
// ANCHOR: expression_macro_usage
|
||||
pol constant ISLAST(i) { one_hot(i, %last_row) };
|
||||
// ANCHOR_END: expression_macro_usage
|
||||
pol commit x, y;
|
||||
|
||||
macro constrain_equal_expr(A, B) { A - B };
|
||||
macro force_equal_on_last_row(poly, value) { ISLAST * constrain_equal_expr(poly, value) = 0; };
|
||||
// ANCHOR: constraint_macro_definitions
|
||||
macro constrain_equal_expr(A, B) { A - B };
|
||||
macro force_equal_on_last_row(poly, value) { ISLAST * constrain_equal_expr(poly, value) = 0; };
|
||||
// ANCHOR_END: constraint_macro_definitions
|
||||
|
||||
// TODO would be easier if we could use "'" as an operator,
|
||||
// then we could write a "force_equal_on_first_row" macro,
|
||||
// and the macro would add a "'" to the parameter.
|
||||
force_equal_on_last_row(x', 1);
|
||||
// ANCHOR: constraint_macro_usage
|
||||
force_equal_on_last_row(x', 1);
|
||||
// ANCHOR_END: constraint_macro_usage
|
||||
force_equal_on_last_row(y', 1);
|
||||
|
||||
macro on_regular_row(cond) { (1 - ISLAST) * cond = 0; };
|
||||
|
||||
23
test_data/pil/fixed_columns.pil
Normal file
23
test_data/pil/fixed_columns.pil
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Main(8);
|
||||
|
||||
// ANCHOR: declare_and_define
|
||||
col fixed ONES = [1]*; // this is valid
|
||||
// col fixed ONES; // this is invalid
|
||||
// ANCHOR_END: declare_and_define
|
||||
|
||||
// ANCHOR: repetitions
|
||||
// valid, as for a given total length, only one column fits this definition for a given `N`
|
||||
col fixed A = [1, 2] + [3, 4]* + [5];
|
||||
|
||||
// invalid, as many columns fit this definition
|
||||
// col fixed A = [1, 2]* + [3, 4]*
|
||||
// ANCHOR_END: repetitions
|
||||
|
||||
// ANCHOR: mapping
|
||||
col fixed B(i) { i + 1 };
|
||||
|
||||
col fixed C(i) {match i {
|
||||
0 => 1,
|
||||
_ => 0
|
||||
}};
|
||||
// ANCHOR_END: mapping
|
||||
Reference in New Issue
Block a user