Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 19 additions & 11 deletions prover/src/tables/bitwise.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! BITWISE precomputed lookup table.
//!
//! This table provides 11 different lookup types used by other tables:
//! This table provides 10 different lookup types used by other tables:
//!
//! ## Range Checks
//! - `IS_BYTE[X]` - X is a valid byte [0, 256)
//! - `IS_BYTE[X, Y]` - X and Y are valid bytes [0, 256)
//! - `IS_HALF[X]` - X is a valid halfword [0, 2^16)
//! - `IS_B20[X]` - X is a valid 20-bit value [0, 2^20)
//!
Expand Down Expand Up @@ -84,15 +84,15 @@ pub mod cols {
pub const MU_MSB16: usize = 15;
/// Multiplicity for ZERO lookups
pub const MU_ZERO: usize = 16;
/// Multiplicity for IS_BYTE lookups
/// Multiplicity for IS_BYTE lookups. Each lookup checks X and Y; pass Y=0
/// for a single-byte range check.
pub const MU_IS_BYTE: usize = 17;
/// Multiplicity for IS_HALF lookups
pub const MU_IS_HALF: usize = 18;
/// Multiplicity for IS_B20 lookups
pub const MU_IS_B20: usize = 19;
/// Multiplicity for HWSL lookups
pub const MU_HWSL: usize = 20;

/// Total number of columns
pub const NUM_COLUMNS: usize = 21;
}
Expand Down Expand Up @@ -417,7 +417,7 @@ 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-21)
// Check all multiplicity columns (indices 11-20)
(cols::MU_AND..=cols::MU_HWSL).any(|col| row_data[col] != FE::zero())
})
.collect();
Expand Down Expand Up @@ -476,7 +476,8 @@ pub enum BitwiseOperationType {
/// - AND/OR/XOR: `x OP y`
/// - MSB8: MSB of `x`
/// - MSB16: MSB of halfword `x + y * 256`
/// - IS_BYTE/IS_HALF: Range check on `x + y * 256`
/// - IS_BYTE: Range check both `x` and `y`; use `y = 0` for a single byte
/// - IS_HALF: Range check on `x + y * 256`
/// - HWSL: Shift `x + y * 256` by `z` bits, returning [SLL, SLLC] as a pair
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BitwiseOperation {
Expand Down Expand Up @@ -667,14 +668,21 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
},
],
),
// IS_BYTE[X] - range check, no output
// IS_BYTE[X, Y] - range check two byte values, no output.
// Single-byte checks send the second argument as 0.
BusInteraction::receiver(
BusId::IsByte,
Multiplicity::Column(cols::MU_IS_BYTE),
vec![BusValue::Packed {
start_column: cols::X,
packing: Packing::Direct,
}],
vec![
BusValue::Packed {
start_column: cols::X,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::Y,
packing: Packing::Direct,
},
],
),
// IS_HALF[X + 256*Y] - range check for halfword
BusInteraction::receiver(
Expand Down
13 changes: 8 additions & 5 deletions prover/src/tables/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,17 @@ pub fn generate_branch_trace(
/// - **Receives** BRANCH lookups from CPU table
pub fn bus_interactions() -> Vec<BusInteraction> {
vec![
// IS_BYTE[next_pc_low[1]] - range check bits 8-15
// IS_BYTE[next_pc_low[1], 0] - range check bits 8-15
BusInteraction::sender(
BusId::IsByte,
Multiplicity::Column(cols::MU),
vec![BusValue::Packed {
start_column: cols::NEXT_PC_LOW_1,
packing: Packing::Direct,
}],
vec![
BusValue::Packed {
start_column: cols::NEXT_PC_LOW_1,
packing: Packing::Direct,
},
BusValue::constant(0),
],
),
// AND_BYTE[next_pc_low[0]; unmasked_low_byte, 254]
// Verifies: next_pc_low[0] = unmasked_low_byte & 0xFE
Expand Down
145 changes: 75 additions & 70 deletions prover/src/tables/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
//!
//! ### Senders (CPU sends to other tables)
//! - DECODE: instruction fetch
//! - IS_BYTE: range checks for rs1, rs2, rd, arg1[i], arg2[i], res[i]
//! - IS_BYTE: range checks for rs1, rs2, rd, and arg1/arg2/res byte pairs
//! - IS_BIT: range checks for flags (via templates)
//! - ADD: for ADD, LOAD, JALR operations
//! - STORE ADD: for STORE (res = arg1 + imm, separate from main ADD)
Expand Down Expand Up @@ -484,51 +484,45 @@ impl CpuOperation {
}
}

/// Collects IS_BYTE lookups for CPU byte range checks.
/// Collects CPU range-check lookups for register indices and byte pairs.
///
/// The CPU has 27 byte columns that must be range-checked: RS1, RS2, RD,
/// ARG1[0..7], ARG2[0..7], RES[0..7]. Each generates one IS_BYTE lookup.
/// The CPU sends:
/// - 1 IS_BYTE lookup for (RS1, RS2) batched as a pair
/// - 1 IS_BYTE lookup for RD encoded as (RD, 0)
/// - 12 IS_BYTE lookups for adjacent byte pairs in ARG1, ARG2, and RES
pub fn collect_byte_check_ops(&self) -> Vec<super::bitwise::BitwiseOperation> {
use super::bitwise::{BitwiseOperation, BitwiseOperationType};

let arg1 = self.compute_arg1();
let arg2 = self.compute_arg2();
let res = self.compute_res();

let mut ops = Vec::with_capacity(27);
let mut ops = Vec::with_capacity(14);

// Register indices
ops.push(BitwiseOperation::single_byte(
// Batch RS1+RS2 as a pair; RD stays single with Y=0.
ops.push(BitwiseOperation::byte_op(
BitwiseOperationType::IsByte,
self.decode.rs1,
));
ops.push(BitwiseOperation::single_byte(
BitwiseOperationType::IsByte,
self.decode.rs2,
));
ops.push(BitwiseOperation::single_byte(
BitwiseOperationType::IsByte,
self.decode.rd,
));

// ARG1[0..7], ARG2[0..7], RES[0..7]
for i in 0..8 {
ops.push(BitwiseOperation::single_byte(
BitwiseOperationType::IsByte,
((arg1 >> (i * 8)) & 0xFF) as u8,
));
}
for i in 0..8 {
ops.push(BitwiseOperation::single_byte(
BitwiseOperationType::IsByte,
((arg2 >> (i * 8)) & 0xFF) as u8,
));
}
for i in 0..8 {
ops.push(BitwiseOperation::single_byte(
BitwiseOperationType::IsByte,
((res >> (i * 8)) & 0xFF) as u8,
));
// 12 IS_BYTE lookups for ARG1/ARG2/RES byte pairs
// Each pair sends [lo, hi] as two separate bus values, so the LogUp
// fingerprint forces each byte to match individually against BITWISE X, Y.
for value in [arg1, arg2, res] {
for i in 0..4 {
let lo = ((value >> (i * 16)) & 0xFF) as u8;
let hi = ((value >> (i * 16 + 8)) & 0xFF) as u8;
ops.push(BitwiseOperation::byte_op(
BitwiseOperationType::IsByte,
lo,
hi,
));
}
}

ops
Expand All @@ -539,7 +533,8 @@ impl CpuOperation {
use super::bitwise::{BitwiseOperation, BitwiseOperationType};
let mut lookups = Vec::new();

// Byte range checks: 27 IS_BYTE
// Range checks: 14 IS_BYTE ops (RS1+RS2 paired, RD single with Y=0,
// plus 12 ARG1/ARG2/RES byte pairs).
lookups.extend(self.collect_byte_check_ops());

// MSB16 lookups for sign bit extraction (when word_instr=1)
Expand Down Expand Up @@ -1949,50 +1944,60 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
));

// -------------------------------------------------------------------------
// IS_BYTE interactions (byte range checks, 27 total)
// CPU-CR29: IS_BYTE[rs1], CPU-CR30: IS_BYTE[rs2], CPU-CR31: IS_BYTE[rd]
// CPU-CR32.i: IS_BYTE[arg1[i]], CPU-CR33.i: IS_BYTE[arg2[i]], CPU-CR34.i: IS_BYTE[res[i]]
// Range checks (14 total):
// CPU-CR29: IS_BYTE[rs1, rs2], CPU-CR30: IS_BYTE[rd, 0]
// CPU-CR31.i: IS_BYTE[arg1[2i], arg1[2i+1]] (i=0..3)
// CPU-CR32.i: IS_BYTE[arg2[2i], arg2[2i+1]] (i=0..3)
// CPU-CR33.i: IS_BYTE[res[2i], res[2i+1]] (i=0..3)
// -------------------------------------------------------------------------
// Range-check all 27 byte columns: RS1, RS2, RD, ARG1[0..7], ARG2[0..7], RES[0..7].
// RS1 and RS2 share one IS_BYTE check; RD uses 0 as the second argument.
// ARG1/ARG2/RES are 8-byte little-endian values — adjacent byte pairs are
// batched into IS_BYTE checks. Each pair sends two separate bus values
// [lo, hi], so the LogUp fingerprint forces each byte to match individually
// against the BITWISE table's X in [0,255] and Y in [0,255].
// Every CPU row (including padding) sends with Multiplicity::One.
let byte_columns: [usize; 27] = [
cols::RS1,
cols::RS2,
cols::RD,
cols::ARG1[0],
cols::ARG1[1],
cols::ARG1[2],
cols::ARG1[3],
cols::ARG1[4],
cols::ARG1[5],
cols::ARG1[6],
cols::ARG1[7],
cols::ARG2[0],
cols::ARG2[1],
cols::ARG2[2],
cols::ARG2[3],
cols::ARG2[4],
cols::ARG2[5],
cols::ARG2[6],
cols::ARG2[7],
cols::RES[0],
cols::RES[1],
cols::RES[2],
cols::RES[3],
cols::RES[4],
cols::RES[5],
cols::RES[6],
cols::RES[7],
];
for col in byte_columns {
interactions.push(BusInteraction::sender(
BusId::IsByte,
Multiplicity::One,
vec![BusValue::Packed {
start_column: col,
interactions.push(BusInteraction::sender(
BusId::IsByte,
Multiplicity::One,
vec![
BusValue::Packed {
start_column: cols::RS1,
packing: Packing::Direct,
}],
));
},
BusValue::Packed {
start_column: cols::RS2,
packing: Packing::Direct,
},
],
));
interactions.push(BusInteraction::sender(
BusId::IsByte,
Multiplicity::One,
vec![
BusValue::Packed {
start_column: cols::RD,
packing: Packing::Direct,
},
BusValue::constant(0),
],
));
for arr in [&cols::ARG1, &cols::ARG2, &cols::RES] {
for i in 0..4 {
interactions.push(BusInteraction::sender(
BusId::IsByte,
Multiplicity::One,
vec![
BusValue::Packed {
start_column: arr[2 * i],
packing: Packing::Direct,
},
BusValue::Packed {
start_column: arr[2 * i + 1],
packing: Packing::Direct,
},
],
));
}
}

// ECALL interaction (single shared bus for HALT and COMMIT)
Expand Down
41 changes: 18 additions & 23 deletions prover/src/tables/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
//!
//! | Tag | Bus | Signature | Multiplicity |
//! |-----|-----|-----------|--------------|
//! | PAGE-C1 | IS_BYTE | `[init]` | 1 (sender) |
//! | PAGE-C2 | IS_BYTE | `[fini]` | 1 (sender) |
//! | PAGE-C3 | Memory | `[0, address, 0, init]` | -1 (receiver) |
//! | PAGE-C4 | Memory | `[0, address, timestamp, fini]` | 1 (sender) |
//! | PAGE-C1+C2 | IS_BYTE | `[init, fini]` | 1 (sender) |
//! | PAGE-C3 | Memory | `[0, address, 0, init]` | -1 (receiver) |
//! | PAGE-C4 | Memory | `[0, address, timestamp, fini]` | 1 (sender) |

use std::collections::HashMap;
use std::sync::OnceLock;
Expand Down Expand Up @@ -297,8 +296,7 @@ pub fn precomputed_commitment_cached(config: &PageConfig, options: &ProofOptions
///
/// ## Bus Interactions
///
/// - PAGE-C1: IS_BYTE[init] - sender, multiplicity 1 (range check)
/// - PAGE-C2: IS_BYTE[fini] - sender, multiplicity 1 (range check)
/// - PAGE-C1+C2: IS_BYTE[init, fini] - sender, multiplicity 1 (batched range check)
/// - PAGE-C3: memory[0, address, 0, init] - receiver, multiplicity -1
/// - PAGE-C4: memory[0, address, timestamp, fini] - sender, multiplicity 1
///
Expand All @@ -322,25 +320,22 @@ pub fn bus_interactions(page_base: u64) -> Vec<BusInteraction> {
let address_hi = BusValue::constant(page_base_hi);

vec![
// PAGE-C1: IS_BYTE[init] - range check initial value
// PAGE-C1+C2: IS_BYTE[init, fini] - range check both byte values in one interaction
BusInteraction::sender(
BusId::IsByte,
Multiplicity::One,
vec![BusValue::Packed {
start_column: cols::INIT,
packing: Packing::Direct,
}],
),
// PAGE-C2: IS_BYTE[fini] - range check final value
BusInteraction::sender(
BusId::IsByte,
Multiplicity::One,
vec![BusValue::Packed {
start_column: cols::FINI,
packing: Packing::Direct,
}],
vec![
BusValue::Packed {
start_column: cols::INIT,
packing: Packing::Direct,
},
BusValue::Packed {
start_column: cols::FINI,
packing: Packing::Direct,
},
],
),
// PAGE-C3: memory[0, address, 0, init] - receive initial token
// PAGE-C3: memory[0, address, 0, init] - receive initial memory token
BusInteraction::receiver(
BusId::Memory,
Multiplicity::One,
Expand Down Expand Up @@ -511,14 +506,14 @@ mod tests {
#[test]
fn test_bus_interactions() {
let interactions = bus_interactions(0x1000); // page_base
assert_eq!(interactions.len(), 4); // C1, C2, C3, C4
assert_eq!(interactions.len(), 3); // C1+C2 (batched IS_BYTE), C3, C4
}

#[test]
fn test_bus_interactions_high_address() {
// Test with high address like stack region
let stack_page = STACK_TOP & !(DEFAULT_PAGE_SIZE as u64 - 1);
let interactions = bus_interactions(stack_page);
assert_eq!(interactions.len(), 4);
assert_eq!(interactions.len(), 3);
}
}
Loading
Loading