Skip to main content

miden_processor/trace/chiplets/
mod.rs

1use alloc::vec::Vec;
2
3use miden_air::trace::{
4    CHIPLETS_WIDTH,
5    chiplets::{
6        KERNEL_ROM_TRACE_WIDTH,
7        ace::ACE_CHIPLET_NUM_COLS,
8        bitwise::TRACE_WIDTH as BITWISE_WIDTH,
9        hasher::{HasherState, TRACE_WIDTH as HASHER_WIDTH},
10        memory::TRACE_WIDTH as MEMORY_WIDTH,
11    },
12    poseidon2_permutation::NUM_POSEIDON2_PERMUTATION_COLS,
13};
14use miden_core::{field::PrimeCharacteristicRing, program::KernelDescriptor};
15
16use crate::{
17    Felt, ONE, Word, ZERO,
18    crypto::merkle::MerklePath,
19    trace::{ChipletTraceFragment, RowIndex, range::RangeChecker},
20};
21
22mod bitwise;
23pub(crate) use bitwise::Bitwise;
24
25mod hasher;
26pub(crate) use hasher::Hasher;
27
28mod memory;
29pub(crate) use memory::Memory;
30
31mod ace;
32pub use ace::{
33    Ace, CircuitEvaluation, MAX_EVAL_CIRCUIT_INVOCATIONS, MAX_EVAL_CIRCUIT_WIRES,
34    MAX_NUM_ACE_WIRES, PTR_OFFSET_ELEM, PTR_OFFSET_WORD,
35};
36
37mod kernel_rom;
38pub(crate) use kernel_rom::KernelRom;
39
40#[cfg(test)]
41mod tests;
42
43// TRACE
44// ================================================================================================
45
46pub struct ChipletsTrace {
47    pub(crate) trace: Vec<Felt>,
48}
49
50pub struct Poseidon2PermutationTrace {
51    pub(crate) trace: Vec<Felt>,
52}
53
54// CHIPLETS MODULE OF HASHER, BITWISE, MEMORY, ACE, AND KERNEL ROM CHIPLETS
55// ================================================================================================
56
57/// This module manages the VM's hasher, bitwise, memory, arithmetic circuit evaluation (ACE)
58/// and kernel ROM chiplets and is responsible for building a final execution trace from their
59/// stacked execution traces and chiplet selectors.
60///
61/// The chiplets trace is five stacked chiplet segments followed by padding.
62///
63/// The chiplets trace has 22 columns. Columns 0-4 (`s0..s4`) form a selector prefix chain.
64/// The hasher controller is selected by `s0=0`; the remaining regions are selected by the first
65/// zero after an active prefix. Column 21 holds `chip_clk`, the chiplet-trace row counter.
66///
67/// ```text
68/// column:   0..20                                      21
69///          selector prefix / chiplet payload          chip_clk
70///          ----------------------------------------    --------
71/// hasher   s0=0, controller payload in columns 1..20   clk
72/// bitwise  s0=1, s1=0, payload in columns 2..14        clk
73/// memory   s0=s1=1, s2=0, payload in columns 3..19     clk
74/// ACE      s0=s1=s2=1, s3=0, payload in columns 4..19  clk
75/// kernel   s0=s1=s2=s3=1, s4=0, payload in columns 5..9 clk
76/// padding  s0=s1=s2=s3=s4=1, zero payload              clk
77/// ```
78///
79/// * Hasher segment: fills the first rows of the trace up to the hasher `trace_len`.
80///   - column 0 (s0): ZERO
81///   - columns 1-20: execution trace of the hasher controller
82///
83/// * Bitwise segment: begins at the end of the hasher segment.
84///   - column 0 (s0): ONE
85///   - column 1 (s1): ZERO
86///   - columns 2-14: execution trace of bitwise chiplet
87///   - columns 15-20: unused columns padded with ZERO
88///
89/// * Memory segment: begins at the end of the bitwise segment.
90///   - column 0 (s0): ONE
91///   - column 1 (s1): ONE
92///   - column 2 (s2): ZERO
93///   - columns 3-19: execution trace of memory chiplet
94///   - column 20: unused, padded with ZERO
95///
96/// * ACE segment: begins at the end of the memory segment.
97///   - columns 0-2 (s0, s1, s2): ONE
98///   - column 3 (s3): ZERO
99///   - columns 4-19: execution trace of ACE chiplet
100///   - column 20: unused, padded with ZERO
101///
102/// * Kernel ROM segment: begins at the end of the ACE segment.
103///   - columns 0-3 (s0, s1, s2, s3): ONE
104///   - column 4 (s4): ZERO
105///   - columns 5-9: execution trace of kernel ROM chiplet
106///   - columns 10-20: unused columns padded with ZERO
107///
108/// * Padding segment: fills the rest of the trace.
109///   - columns 0-4 (s0..s4): ONE
110///   - columns 5-20: unused columns padded with ZERO
111#[derive(Debug)]
112pub struct Chiplets {
113    pub hasher: Hasher,
114    pub bitwise: Bitwise,
115    pub memory: Memory,
116    pub ace: Ace,
117    pub kernel_rom: KernelRom,
118}
119
120impl Chiplets {
121    // PUBLIC ACCESSORS
122    // --------------------------------------------------------------------------------------------
123
124    /// Returns the chiplets trace length, including the mandatory padding row used by auxiliary
125    /// connector columns that read the memory chiplet.
126    pub fn trace_len(&self) -> usize {
127        self.hasher.trace_len()
128            + self.bitwise.trace_len()
129            + self.memory.trace_len()
130            + self.ace.trace_len()
131            + self.kernel_rom.trace_len()
132            + 1
133    }
134
135    /// Returns the unpadded trace length of the Poseidon2 permutation AIR.
136    pub fn poseidon2_permutation_trace_len(&self) -> usize {
137        self.hasher.poseidon2_permutation_trace_len()
138    }
139
140    /// Returns the index of the first row of `Bitwise` execution trace.
141    pub fn bitwise_start(&self) -> RowIndex {
142        self.hasher.trace_len().into()
143    }
144
145    /// Returns the index of the first row of the `Memory` execution trace.
146    pub fn memory_start(&self) -> RowIndex {
147        self.bitwise_start() + self.bitwise.trace_len()
148    }
149
150    /// Returns the index of the first row of the `ACE` execution trace.
151    pub fn ace_start(&self) -> RowIndex {
152        self.memory_start() + self.memory.trace_len()
153    }
154
155    /// Returns the index of the first row of `KernelRom` execution trace.
156    pub fn kernel_rom_start(&self) -> RowIndex {
157        self.ace_start() + self.ace.trace_len()
158    }
159
160    /// Returns the index of the first row of the padding section of the execution trace.
161    pub fn padding_start(&self) -> RowIndex {
162        self.kernel_rom_start() + self.kernel_rom.trace_len()
163    }
164
165    // EXECUTION TRACE
166    // --------------------------------------------------------------------------------------------
167
168    /// Adds all range checks required by the hasher and memory chiplets to the provided
169    /// `RangeChecker` instance.
170    pub fn append_range_checks(&self, range_checker: &mut RangeChecker) {
171        self.hasher.append_range_checks(range_checker);
172        self.memory.append_range_checks(self.memory_start(), range_checker);
173    }
174
175    /// Returns execution traces for `ChipletsAir` and `Poseidon2PermutationAir`.
176    pub fn into_traces(
177        self,
178        trace_len: usize,
179        poseidon2_trace_len: usize,
180    ) -> (ChipletsTrace, Poseidon2PermutationTrace) {
181        assert!(self.trace_len() <= trace_len, "target trace length too small");
182        assert!(
183            self.poseidon2_permutation_trace_len() <= poseidon2_trace_len,
184            "target Poseidon2 trace length too small"
185        );
186
187        let mut trace = vec![Felt::ZERO; CHIPLETS_WIDTH * trace_len];
188        let mut poseidon2_trace =
189            Felt::zero_vec(NUM_POSEIDON2_PERMUTATION_COLS * poseidon2_trace_len);
190        self.fill_trace(&mut trace, trace_len, &mut poseidon2_trace);
191
192        (ChipletsTrace { trace }, Poseidon2PermutationTrace { trace: poseidon2_trace })
193    }
194
195    // HELPER METHODS
196    // --------------------------------------------------------------------------------------------
197
198    /// Fills the chiplets trace with the stacked hasher-controller, bitwise, memory, ACE, and
199    /// kernel ROM regions.
200    ///
201    /// Selector columns and `chip_clk` are written by each `ChipletTraceFragment`; the padding
202    /// region is filled directly below. Poseidon2 permutation rows are materialized into
203    /// `poseidon2_trace`.
204    fn fill_trace(self, trace: &mut [Felt], trace_len: usize, poseidon2_trace: &mut [Felt]) {
205        const W: usize = CHIPLETS_WIDTH;
206        debug_assert_eq!(trace.len(), W * trace_len);
207
208        let memory_start: usize = self.memory_start().into();
209        let ace_start: usize = self.ace_start().into();
210        let kernel_rom_start: usize = self.kernel_rom_start().into();
211        let padding_start: usize = self.padding_start().into();
212
213        let Chiplets { hasher, bitwise, memory, ace, kernel_rom } = self;
214
215        // Per-chiplet row counts. Chiplets are stacked vertically, so each one's region is a
216        // contiguous band of rows: hasher [0, h), bitwise [h, h+b), and so on.
217        let hasher_len = hasher.trace_len();
218        let bitwise_len = bitwise.trace_len();
219        let memory_len = memory.trace_len();
220        let ace_len = ace.trace_len();
221        let kernel_rom_len = kernel_rom.trace_len();
222
223        // Chiplets are stacked as hasher, bitwise, memory, ACE, then kernel ROM. Each region writes
224        // its payload after the selector prefix that identifies it.
225        const _: () = assert!(1 + HASHER_WIDTH == CHIPLETS_WIDTH - 1);
226
227        // Carve `trace` into the per-chiplet contiguous row bands.
228        let (hasher_band, rest) = trace.split_at_mut(hasher_len * W);
229        let (bitwise_band, rest) = rest.split_at_mut(bitwise_len * W);
230        let (memory_band, rest) = rest.split_at_mut(memory_len * W);
231        let (ace_band, rest) = rest.split_at_mut(ace_len * W);
232        let (kernel_band, padding_band) = rest.split_at_mut(kernel_rom_len * W);
233
234        let mut hasher_fragment =
235            ChipletTraceFragment::with_overheads(hasher_band, W, 1, HASHER_WIDTH, 0, &[]);
236        let mut bitwise_fragment = ChipletTraceFragment::with_overheads(
237            bitwise_band,
238            W,
239            2,
240            BITWISE_WIDTH,
241            hasher_len,
242            &[0],
243        );
244        let mut memory_fragment = ChipletTraceFragment::with_overheads(
245            memory_band,
246            W,
247            3,
248            MEMORY_WIDTH,
249            memory_start,
250            &[0, 1],
251        );
252        let mut ace_fragment = ChipletTraceFragment::with_overheads(
253            ace_band,
254            W,
255            4,
256            ACE_CHIPLET_NUM_COLS,
257            ace_start,
258            &[0, 1, 2],
259        );
260        let mut kernel_rom_fragment = ChipletTraceFragment::with_overheads(
261            kernel_band,
262            W,
263            5,
264            KERNEL_ROM_TRACE_WIDTH,
265            kernel_rom_start,
266            &[0, 1, 2, 3],
267        );
268
269        rayon::scope(|s| {
270            s.spawn(move |_| {
271                hasher.fill_trace(&mut hasher_fragment, poseidon2_trace);
272            });
273            s.spawn(move |_| {
274                bitwise.fill_trace(&mut bitwise_fragment);
275            });
276            s.spawn(move |_| {
277                memory.fill_trace(&mut memory_fragment);
278            });
279            s.spawn(move |_| {
280                ace.fill_trace(&mut ace_fragment);
281            });
282            s.spawn(move |_| {
283                kernel_rom.fill_trace(&mut kernel_rom_fragment);
284            });
285            s.spawn(move |_| {
286                fill_padding_rows(padding_band, padding_start);
287            });
288        });
289    }
290}
291
292/// Fills padding rows after the kernel ROM region: cols 0..=4 = ONE, chip_clk = row + 1.
293fn fill_padding_rows(band: &mut [Felt], row_offset: usize) {
294    const W: usize = CHIPLETS_WIDTH;
295    let (rows, _) = band.as_chunks_mut::<W>();
296    for (i, row) in rows.iter_mut().enumerate() {
297        row[..5].fill(ONE);
298        row[W - 1] = Felt::from_u32((row_offset + i + 1) as u32);
299    }
300}
301
302// HELPER STRUCTS
303// ================================================================================================
304
305/// Result of a Merkle tree node update.
306///
307/// Contains the old root, the new root, and the trace row where the computation started.
308#[derive(Debug, Copy, Clone)]
309pub struct MerkleRootUpdate {
310    address: Felt,
311    old_root: Word,
312    new_root: Word,
313}
314
315impl MerkleRootUpdate {
316    pub fn get_address(&self) -> Felt {
317        self.address
318    }
319    pub fn get_old_root(&self) -> Word {
320        self.old_root
321    }
322    pub fn get_new_root(&self) -> Word {
323        self.new_root
324    }
325}