Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cb1e8cc
Add shrink-cpu buses, decode layout, EQ chip
jotabulacios May 27, 2026
e5f7b2e
Add BYTEWISE ALU chip
jotabulacios May 27, 2026
7aaff8f
Add BYTEWISE, STORE and CPU32 chips
jotabulacios May 27, 2026
d3fb018
Add CPU32 buses; fix EQ ALU output width
jotabulacios May 27, 2026
8a7df1c
Register EQ/BYTEWISE/STORE/CPU32 as empty tables
jotabulacios May 27, 2026
0e38025
Migrate CPU + ALU/memory chips to unified ALU bus
jotabulacios May 27, 2026
dabff74
Delegate word instructions to the CPU32 table
jotabulacios May 27, 2026
68e49d7
Re-enable and rewrite CPU/decode/constraint tests for the shrink-cpu …
jotabulacios May 27, 2026
44ae067
remove unnecesary files
jotabulacios May 28, 2026
816917a
Unify LT/MUL/memw/dvrm onto the ALU bus
jotabulacios May 28, 2026
e9d93c5
Pin JALR rvd to pc+len
jotabulacios May 28, 2026
a35222c
Align SHIFT shift-amount layout with the spec
jotabulacios May 29, 2026
c8ef5f7
reconcile prover with shrink-cpu spec
jotabulacios May 29, 2026
aa73c31
Add spec assumption range checks
jotabulacios May 29, 2026
cd637dc
Add explicit IS_BYTE[shift[0]] range check
jotabulacios May 29, 2026
71319a5
Sync CPU with merged shrink-cpu spec
jotabulacios Jun 2, 2026
00ec7a0
solve conflicts
jotabulacios Jun 2, 2026
56adb56
remove old comments
jotabulacios Jun 2, 2026
5e90da2
use constants
jotabulacios Jun 2, 2026
0d95579
solve conflicts
jotabulacios Jun 3, 2026
949a847
Propagate carry in branch rvd constraint
jotabulacios Jun 3, 2026
0a10f13
prevent register side effects in CPU32 padding
jotabulacios Jun 3, 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
1,128 changes: 339 additions & 789 deletions prover/src/constraints/cpu.rs

Large diffs are not rendered by default.

69 changes: 64 additions & 5 deletions prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ use crate::tables::trace_builder::Traces;
use crate::tables::trace_builder::count_table_lengths;
use crate::tables::types::BusId;
use crate::test_utils::{
E, F, VmAir, create_bitwise_air, create_branch_air, create_commit_air, create_cpu_air,
create_decode_air, create_dvrm_air, create_halt_air, create_keccak_air, create_keccak_rc_air,
create_keccak_rnd_air, create_load_air, create_lt_air, create_memw_air,
create_memw_aligned_air, create_memw_register_air, create_mul_air, create_page_air,
create_register_air, create_shift_air,
E, F, VmAir, create_bitwise_air, create_branch_air, create_bytewise_air, create_commit_air,
create_cpu_air, create_cpu32_air, create_decode_air, create_dvrm_air, create_eq_air,
create_halt_air, create_keccak_air, create_keccak_rc_air, create_keccak_rnd_air,
create_load_air, create_lt_air, create_memw_air, create_memw_aligned_air,
create_memw_register_air, create_mul_air, create_page_air, create_register_air,
create_shift_air, create_store_air,
};

use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions};
Expand Down Expand Up @@ -84,6 +85,11 @@ pub struct TableCounts {
pub shift: usize,
pub branch: usize,
pub memw_register: usize,
// Auxiliary ALU / memory / CPU32 dispatch chips
pub eq: usize,
pub bytewise: usize,
pub store: usize,
pub cpu32: usize,
}

impl TableCounts {
Expand All @@ -99,6 +105,10 @@ impl TableCounts {
+ self.shift
+ self.branch
+ self.memw_register
+ self.eq
+ self.bytewise
+ self.store
+ self.cpu32
}

/// Validate that all required tables have at least one chunk.
Expand All @@ -117,6 +127,10 @@ impl TableCounts {
("shift", self.shift),
("branch", self.branch),
("memw_register", self.memw_register),
("eq", self.eq),
("bytewise", self.bytewise),
("store", self.store),
("cpu32", self.cpu32),
];
for (name, count) in checks {
if count == 0 {
Expand Down Expand Up @@ -212,6 +226,11 @@ pub(crate) struct VmAirs {
pub register: VmAir,
pub pages: Vec<VmAir>,
pub memw_registers: Vec<VmAir>,
// Auxiliary ALU / memory / CPU32 dispatch chips
pub eqs: Vec<VmAir>,
pub bytewises: Vec<VmAir>,
pub stores: Vec<VmAir>,
pub cpu32s: Vec<VmAir>,
}

impl VmAirs {
Expand Down Expand Up @@ -269,6 +288,18 @@ impl VmAirs {
{
pairs.push((air, trace, &()));
}
for (air, trace) in self.eqs.iter().zip(traces.eqs.iter_mut()) {
pairs.push((air, trace, &()));
}
for (air, trace) in self.bytewises.iter().zip(traces.bytewises.iter_mut()) {
pairs.push((air, trace, &()));
}
for (air, trace) in self.stores.iter().zip(traces.stores.iter_mut()) {
pairs.push((air, trace, &()));
}
for (air, trace) in self.cpu32s.iter().zip(traces.cpu32s.iter_mut()) {
pairs.push((air, trace, &()));
}

pairs
}
Expand Down Expand Up @@ -319,6 +350,18 @@ impl VmAirs {
for air in &self.memw_registers {
refs.push(air);
}
for air in &self.eqs {
refs.push(air);
}
for air in &self.bytewises {
refs.push(air);
}
for air in &self.stores {
refs.push(air);
}
for air in &self.cpu32s {
refs.push(air);
}

refs
}
Expand Down Expand Up @@ -424,6 +467,18 @@ impl VmAirs {
let memw_registers: Vec<_> = (0..table_counts.memw_register)
.map(|i| create_memw_register_air(proof_options).with_name(&format!("MEMW_R[{}]", i)))
.collect();
let eqs: Vec<_> = (0..table_counts.eq)
.map(|i| create_eq_air(proof_options).with_name(&format!("EQ[{}]", i)))
.collect();
let bytewises: Vec<_> = (0..table_counts.bytewise)
.map(|i| create_bytewise_air(proof_options).with_name(&format!("BYTEWISE[{}]", i)))
.collect();
let stores: Vec<_> = (0..table_counts.store)
.map(|i| create_store_air(proof_options).with_name(&format!("STORE[{}]", i)))
.collect();
let cpu32s: Vec<_> = (0..table_counts.cpu32)
.map(|i| create_cpu32_air(proof_options).with_name(&format!("CPU32[{}]", i)))
.collect();

#[cfg(feature = "debug-checks")]
debug_report::print_bus_legend();
Expand All @@ -448,6 +503,10 @@ impl VmAirs {
register,
pages,
memw_registers,
eqs,
bytewises,
stores,
cpu32s,
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion prover/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::test_utils::E;
use crate::{RuntimePageRange, TableCounts};

/// Domain-separation tag. Bump the suffix (`_V2`, ...) on any encoding change.
const DOMAIN_TAG: &[u8] = b"LAMBDAVM_STARK_STATEMENT_V1";
const DOMAIN_TAG: &[u8] = b"LAMBDAVM_STARK_STATEMENT_V2";

fn elf_digest(elf: &[u8]) -> [u8; 32] {
let mut h = Keccak256::new();
Expand Down Expand Up @@ -55,6 +55,10 @@ pub(crate) fn absorb_statement(
shift,
branch,
memw_register,
eq,
bytewise,
store,
cpu32,
} = table_counts;
for count in [
cpu,
Expand All @@ -67,6 +71,10 @@ pub(crate) fn absorb_statement(
shift,
branch,
memw_register,
eq,
bytewise,
store,
cpu32,
] {
t.append_bytes(&(count as u64).to_le_bytes());
}
Expand Down
85 changes: 81 additions & 4 deletions prover/src/tables/bitwise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use stark::trace::{TraceTable, columns2rows};
#[cfg(feature = "parallel")]
use rayon::prelude::*;

use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField};
use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op};

// =========================================================================
// Column indices for BITWISE table
Expand Down Expand Up @@ -92,8 +92,14 @@ pub mod cols {
pub const MU_IS_B20: usize = 19;
/// Multiplicity for HWSL lookups
pub const MU_HWSL: usize = 20;
/// Multiplicity for `BYTE_ALU[opsel=AND]` lookups
pub const MU_BYTE_ALU_AND: usize = 21;
/// Multiplicity for `BYTE_ALU[opsel=OR]` lookups
pub const MU_BYTE_ALU_OR: usize = 22;
/// Multiplicity for `BYTE_ALU[opsel=XOR]` lookups
pub const MU_BYTE_ALU_XOR: usize = 23;
/// Total number of columns
pub const NUM_COLUMNS: usize = 21;
pub const NUM_COLUMNS: usize = 24;
}

/// Number of rows in the BITWISE table: 256 * 256 * 16 = 2^20
Expand Down Expand Up @@ -442,6 +448,9 @@ pub fn update_multiplicities(
BitwiseOperationType::IsHalf => cols::MU_IS_HALF,
BitwiseOperationType::IsB20 => cols::MU_IS_B20,
BitwiseOperationType::Hwsl => cols::MU_HWSL,
BitwiseOperationType::ByteAluAnd => cols::MU_BYTE_ALU_AND,
BitwiseOperationType::ByteAluOr => cols::MU_BYTE_ALU_OR,
BitwiseOperationType::ByteAluXor => cols::MU_BYTE_ALU_XOR,
};

// Increment multiplicity
Expand Down Expand Up @@ -477,8 +486,10 @@ pub(crate) fn trim_zero_rows(
let kept_rows: Vec<usize> = (0..num_rows)
.filter(|&row| {
let row_data = trace.main_table.get_row(row);
// Check all multiplicity columns (indices 11-20)
(cols::MU_AND..=cols::MU_HWSL).any(|col| row_data[col] != FE::zero())
// Check all multiplicity columns (MU_AND..=MU_BYTE_ALU_XOR), including
// the BYTE_ALU columns (rows used only by a BYTE_ALU lookup
// must not be trimmed).
(cols::MU_AND..=cols::MU_BYTE_ALU_XOR).any(|col| row_data[col] != FE::zero())
})
.collect();

Expand Down Expand Up @@ -519,6 +530,10 @@ pub enum BitwiseOperationType {
IsHalf,
IsB20,
Hwsl,
// Unified `BYTE_ALU` lookups, keyed by opsel.
ByteAluAnd,
ByteAluOr,
ByteAluXor,
}

/// A lookup request to the BITWISE precomputed table.
Expand Down Expand Up @@ -807,5 +822,67 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
},
],
),
// BYTE_ALU[opsel, X, Y] -> out.
// Unifies AND/OR/XOR into one bus keyed by the `alu_op` descriptor.
// Implemented as one receiver per opsel, reusing the precomputed
// AND/OR/XOR result columns (the "single 2^20 column" in bitwise.typ is
// an optimization note, not a requirement).
BusInteraction::receiver(
BusId::ByteAlu,
Multiplicity::Column(cols::MU_BYTE_ALU_AND),
vec![
BusValue::constant(alu_op::AND as u64),
BusValue::Packed {
start_column: cols::X,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::Y,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::AND,
packing: Packing::Direct,
},
],
),
BusInteraction::receiver(
BusId::ByteAlu,
Multiplicity::Column(cols::MU_BYTE_ALU_OR),
vec![
BusValue::constant(alu_op::OR as u64),
BusValue::Packed {
start_column: cols::X,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::Y,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::OR,
packing: Packing::Direct,
},
],
),
BusInteraction::receiver(
BusId::ByteAlu,
Multiplicity::Column(cols::MU_BYTE_ALU_XOR),
vec![
BusValue::constant(alu_op::XOR as u64),
BusValue::Packed {
start_column: cols::X,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::Y,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::XOR,
packing: Packing::Direct,
},
],
),
]
}
16 changes: 13 additions & 3 deletions prover/src/tables/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ pub enum BranchConstraintKind {
/// `(1 - JALR) * carry_1_pc * (1 - carry_1_pc) = 0`
/// where carry_1_pc = (pc[1] + offset[1] + carry_0_pc - next_pc_unmasked[1]) / 2^32
PcCarry1IsBit,
/// `IS_BIT<JALR>`: `JALR * (1 - JALR) = 0` (spec defense-in-depth assumption)
JalrIsBit,
/// `JALR * carry_0_reg * (1 - carry_0_reg) = 0`
/// where carry_0_reg = (register[0] + offset[0] - next_pc_unmasked[0]) / 2^32
RegCarry0IsBit,
Expand Down Expand Up @@ -494,6 +496,7 @@ impl BranchConstraint {
let one = FieldElement::<F>::one();

match self.kind {
BranchConstraintKind::JalrIsBit => &jalr * (&one - &jalr),
BranchConstraintKind::PcCarry0IsBit => {
let cond = &one - &jalr;
let c = Self::compute_carry_0_for(cols::PC_0, step);
Expand All @@ -520,8 +523,12 @@ impl BranchConstraint {

impl TransitionConstraint<GoldilocksField, GoldilocksExtension> for BranchConstraint {
fn degree(&self) -> usize {
// cond (degree 1) * carry (degree 1) * (1 - carry) (degree 1) = degree 3
3
match self.kind {
// JALR * (1 - JALR) = degree 2
BranchConstraintKind::JalrIsBit => 2,
// cond (degree 1) * carry (degree 1) * (1 - carry) (degree 1) = degree 3
_ => 3,
}
}

fn constraint_idx(&self) -> usize {
Expand All @@ -539,11 +546,13 @@ impl TransitionConstraint<GoldilocksField, GoldilocksExtension> for BranchConstr

/// Creates all constraints for the BRANCH table.
///
/// Returns 4 constraints (two conditional ADD templates × 2 carries each):
/// Returns 5 constraints (two conditional ADD templates × 2 carries each, plus
/// the `IS_BIT<JALR>` defense-in-depth assumption):
/// - PcCarry0IsBit: `(1 - JALR) * carry_0 * (1 - carry_0) = 0` (pc path)
/// - PcCarry1IsBit: `(1 - JALR) * carry_1 * (1 - carry_1) = 0` (pc path)
/// - RegCarry0IsBit: `JALR * carry_0 * (1 - carry_0) = 0` (register path)
/// - RegCarry1IsBit: `JALR * carry_1 * (1 - carry_1) = 0` (register path)
/// - JalrIsBit: `JALR * (1 - JALR) = 0`
pub fn branch_constraints(constraint_idx_start: usize) -> (Vec<BranchConstraint>, usize) {
let mut idx = constraint_idx_start;
let mut next = || {
Expand All @@ -556,6 +565,7 @@ pub fn branch_constraints(constraint_idx_start: usize) -> (Vec<BranchConstraint>
BranchConstraint::new(BranchConstraintKind::PcCarry1IsBit, next()),
BranchConstraint::new(BranchConstraintKind::RegCarry0IsBit, next()),
BranchConstraint::new(BranchConstraintKind::RegCarry1IsBit, next()),
BranchConstraint::new(BranchConstraintKind::JalrIsBit, next()),
];
(constraints, idx)
}
Expand Down
Loading
Loading