Skip to main content

miden_processor/trace/chiplets/
mod.rs

1use alloc::vec::Vec;
2
3use miden_air::trace::{CHIPLETS_WIDTH, chiplets::hasher::HasherState};
4use miden_core::{mast::OpBatch, program::Kernel};
5
6use crate::{
7    Felt, ONE, Word, ZERO,
8    crypto::merkle::MerklePath,
9    trace::{RowIndex, TraceFragment, range::RangeChecker},
10};
11
12mod bitwise;
13use bitwise::Bitwise;
14
15mod hasher;
16use hasher::Hasher;
17
18mod memory;
19use memory::Memory;
20
21mod ace;
22pub use ace::{Ace, CircuitEvaluation, MAX_NUM_ACE_WIRES, PTR_OFFSET_ELEM, PTR_OFFSET_WORD};
23
24mod kernel_rom;
25use kernel_rom::KernelRom;
26
27#[cfg(test)]
28#[allow(clippy::needless_range_loop)]
29mod tests;
30
31// TRACE
32// ================================================================================================
33
34pub struct ChipletsTrace {
35    pub(crate) trace: Vec<Felt>,
36}
37
38// CHIPLETS MODULE OF HASHER, BITWISE, MEMORY, ACE, AND KERNEL ROM CHIPLETS
39// ================================================================================================
40
41/// This module manages the VM's hasher, bitwise, memory, arithmetic circuit evaluation (ACE)
42/// and kernel ROM chiplets and is responsible for building a final execution trace from their
43/// stacked execution traces and chiplet selectors.
44///
45/// The module's trace can be thought of as 6 stacked segments in the following form.
46///
47/// The chiplet system uses two physical selector columns (`s_ctrl = column 0` and
48/// `s_perm = column 20`) plus the virtual `s0 = 1 - (s_ctrl + s_perm)` to partition
49/// rows into three top-level regions. Columns 1-4 (`s1..s4`) subdivide the `s0` region.
50///
51/// * Hasher segment: fills the first rows of the trace up to the hasher `trace_len`. Split into
52///   controller (s_ctrl=1, s_perm=0) and permutation (s_ctrl=0, s_perm=1) sub-regions.
53///   - column 0 (s_ctrl): 1 on controller rows, 0 on permutation rows
54///   - columns 1-19: execution trace of hash chiplet
55///   - column 20 (s_perm): 0 on controller rows, 1 on permutation rows
56///
57/// * Bitwise segment: begins at the end of the hasher segment.
58///   - column 0 (s_ctrl): ZERO
59///   - column 1 (s1): ZERO
60///   - columns 2-14: execution trace of bitwise chiplet
61///   - columns 15-20: unused columns padded with ZERO
62///
63/// * Memory segment: begins at the end of the bitwise segment.
64///   - column 0 (s_ctrl): ZERO
65///   - column 1 (s1): ONE
66///   - column 2 (s2): ZERO
67///   - columns 3-19: execution trace of memory chiplet
68///   - column 20: unused column padded with ZERO
69///
70/// * ACE segment: begins at the end of the memory segment.
71///   - column 0 (s_ctrl): ZERO
72///   - column 1-2 (s1, s2): ONE
73///   - column 3 (s3): ZERO
74///   - columns 4-20: execution trace of ACE chiplet
75///
76/// * Kernel ROM segment: begins at the end of the ACE segment.
77///   - column 0 (s_ctrl): ZERO
78///   - columns 1-3 (s1, s2, s3): ONE
79///   - column 4 (s4): ZERO
80///   - columns 5-9: execution trace of kernel ROM chiplet
81///   - columns 10-20: unused columns padded with ZERO
82///
83/// * Padding segment: fills the rest of the trace.
84///   - column 0 (s_ctrl): ZERO
85///   - columns 1-4 (s1..s4): ONE
86///   - columns 5-20: unused columns padded with ZERO
87///
88///
89/// The following is a pictorial representation of the chiplet module:
90///
91/// ```text
92///        s_ctrl s1  s2  s3  s4  s_perm
93///          [0] [1] [2] [3] [4]   [20]
94///         +---+----------------------------------------------------------+---+
95///  ctrl   | 1 |       Hash chiplet (controller rows)                     | 0 |
96///         | . |       20 columns                                         | . |
97///         | 1 |       constraint degree 9                                | 0 |
98///         +---+                                                          +---+
99///  perm   | 0 |       Hash chiplet (permutation rows)                    | 1 |
100///         | . |                                                          | . |
101///         | 0 |                                                          | 1 |
102///         +---+---+------------------------------------------------------+---+
103///         | 0 | 0 |                                                      |---|
104///         | . | . |                Bitwise chiplet                       |---|
105///         | . | . |                  13 columns                          |---|
106///         | 0 | 0 |             constraint degree 5                      |---|
107///         | . +---+---+--------------------------------------------------+---+
108///         | . | 1 | 0 |                                                  |---|
109///         | . | . | . |          Memory chiplet                          |---|
110///         | . | . | . |            17 columns                            |---|
111///         | . | . | 0 |        constraint degree 9                       |---|
112///         | . + . +---+---+----------------------------------------------+---+
113///         | . | . | 1 | 0 |                                              |---|
114///         | . | . | . | . |        ACE chiplet                           |---|
115///         | . | . | . | . |          16 columns                          |---|
116///         | . | . | . | 0 |      constraint degree 5                     |---|
117///         | . + . | . +---+---+-------------------------+--------------------+
118///         | . | . | . | 1 | 0 |                         |--------------------|
119///         | . | . | . | . | . |   Kernel ROM chiplet    |--------------------|
120///         | . | . | . | . | . |   5 columns             |--------------------|
121///         | . | . | . | . | 0 |   constraint degree 9   |--------------------|
122///         | . + . | . | . +---+-------------------------+--------------------+
123///         | . | . | . | . | 1 |-------- Padding ---------|                   |
124///         | . | . | . | . | . |                          |                   |
125///         | 0 | 1 | 1 | 1 | 1 |                          | 0                |
126///         +---+---+---+---+---+--------------------------+-------------------+
127/// ```
128#[derive(Debug)]
129pub struct Chiplets {
130    pub hasher: Hasher,
131    pub bitwise: Bitwise,
132    pub memory: Memory,
133    pub ace: Ace,
134    pub kernel_rom: KernelRom,
135}
136
137impl Chiplets {
138    // CONSTRUCTOR
139    // --------------------------------------------------------------------------------------------
140    /// Returns a new [Chiplets] component instantiated with the provided Kernel.
141    pub fn new(kernel: Kernel) -> Self {
142        Self {
143            hasher: Hasher::default(),
144            bitwise: Bitwise::default(),
145            memory: Memory::default(),
146            kernel_rom: KernelRom::new(kernel),
147            ace: Ace::default(),
148        }
149    }
150
151    // PUBLIC ACCESSORS
152    // --------------------------------------------------------------------------------------------
153
154    /// Returns the length of the trace required to accommodate chiplet components and 1
155    /// mandatory padding row required for ensuring sufficient trace length for auxiliary connector
156    /// columns that rely on the memory chiplet.
157    pub fn trace_len(&self) -> usize {
158        self.hasher.trace_len()
159            + self.bitwise.trace_len()
160            + self.memory.trace_len()
161            + self.kernel_rom.trace_len()
162            + self.ace.trace_len()
163            + 1
164    }
165
166    /// Returns the index of the first row of `Bitwise` execution trace.
167    pub fn bitwise_start(&self) -> RowIndex {
168        self.hasher.trace_len().into()
169    }
170
171    /// Returns the index of the first row of the `Memory` execution trace.
172    pub fn memory_start(&self) -> RowIndex {
173        self.bitwise_start() + self.bitwise.trace_len()
174    }
175
176    /// Returns the index of the first row of `KernelRom` execution trace.
177    pub fn ace_start(&self) -> RowIndex {
178        self.memory_start() + self.memory.trace_len()
179    }
180
181    /// Returns the index of the first row of `KernelRom` execution trace.
182    pub fn kernel_rom_start(&self) -> RowIndex {
183        self.ace_start() + self.ace.trace_len()
184    }
185
186    /// Returns the index of the first row of the padding section of the execution trace.
187    pub fn padding_start(&self) -> RowIndex {
188        self.kernel_rom_start() + self.kernel_rom.trace_len()
189    }
190
191    // EXECUTION TRACE
192    // --------------------------------------------------------------------------------------------
193
194    /// Adds all range checks required by the memory chiplet to the provided `RangeChecker``
195    /// instance.
196    pub fn append_range_checks(&self, range_checker: &mut RangeChecker) {
197        self.memory.append_range_checks(self.memory_start(), range_checker);
198    }
199
200    /// Returns an execution trace of the chiplets containing the stacked traces of the
201    /// Hasher, Bitwise, ACE, Memory chiplets, and kernel ROM chiplet.
202    pub fn into_trace(self, trace_len: usize) -> ChipletsTrace {
203        assert!(self.trace_len() <= trace_len, "target trace length too small");
204
205        // Allocate columns for the trace of the chiplets.
206        let mut trace = (0..CHIPLETS_WIDTH)
207            .map(|_| vec![Felt::ZERO; trace_len])
208            .collect::<Vec<_>>()
209            .try_into()
210            .expect("failed to convert vector to array");
211        self.fill_trace(&mut trace);
212
213        let mut row_flat = Vec::with_capacity(CHIPLETS_WIDTH * trace_len);
214        for row_idx in 0..trace_len {
215            row_flat.extend(trace.iter().map(|column| column[row_idx]));
216        }
217
218        ChipletsTrace { trace: row_flat }
219    }
220
221    // HELPER METHODS
222    // --------------------------------------------------------------------------------------------
223
224    /// Fills the provided trace for the chiplets module with the stacked execution traces of the
225    /// Hasher, Bitwise, Memory, ACE, and kernel ROM chiplets along with selector columns
226    /// to identify each individual chiplet trace in addition to padding to fill the rest of
227    /// the trace.
228    fn fill_trace(self, trace: &mut [Vec<Felt>; CHIPLETS_WIDTH]) {
229        // s_ctrl (trace[0]) is 1 on the hasher's controller rows and 0 elsewhere.
230        // The controller region is the padded prefix of the hasher region; `region_lengths`
231        // returns the same padded length that `finalize_trace` will materialize later.
232        let (hasher_ctrl_len, _hasher_perm_len) = self.hasher.region_lengths();
233        let memory_start: usize = self.memory_start().into();
234        let ace_start: usize = self.ace_start().into();
235        let kernel_rom_start: usize = self.kernel_rom_start().into();
236        let padding_start: usize = self.padding_start().into();
237
238        let Chiplets { hasher, bitwise, memory, kernel_rom, ace } = self;
239
240        // Populate external selector columns. Each is a contiguous 0/1 indicator; the trace
241        // is zero-initialized, so we only memset the regions where the selector is ONE.
242        // s_perm (trace[20]) is written row-by-row by the hasher itself during fragment fill.
243        trace[0][..hasher_ctrl_len].fill(ONE); // s_ctrl: hasher controller rows
244        trace[1][memory_start..].fill(ONE);
245        trace[2][ace_start..].fill(ONE);
246        trace[3][kernel_rom_start..].fill(ONE);
247        trace[4][padding_start..].fill(ONE);
248
249        // Fill chiplet traces via fragments. The block scopes the fragment borrows.
250        {
251            // allocate fragments to be filled with the respective execution traces of each chiplet
252            let mut hasher_fragment = TraceFragment::new(CHIPLETS_WIDTH, hasher.trace_len());
253            let mut bitwise_fragment = TraceFragment::new(CHIPLETS_WIDTH, bitwise.trace_len());
254            let mut memory_fragment = TraceFragment::new(CHIPLETS_WIDTH, memory.trace_len());
255            let mut ace_fragment = TraceFragment::new(CHIPLETS_WIDTH, ace.trace_len());
256            let mut kernel_rom_fragment =
257                TraceFragment::new(CHIPLETS_WIDTH, kernel_rom.trace_len());
258
259            // add the hasher, bitwise, memory, ACE, and kernel ROM segments to their respective
260            // fragments so they can be filled with the chiplet traces
261            for (column_num, column) in trace.iter_mut().enumerate().skip(1) {
262                match column_num {
263                    1 => {
264                        // column 1 is relevant only for the hasher
265                        hasher_fragment.push_column_slice(column);
266                    },
267                    2 => {
268                        // column 2 is relevant to the hasher and to bitwise chiplet
269                        let rest = hasher_fragment.push_column_slice(column);
270                        bitwise_fragment.push_column_slice(rest);
271                    },
272                    3 => {
273                        // column 3 is relevant for hasher, bitwise, and memory chiplets
274                        let rest = hasher_fragment.push_column_slice(column);
275                        let rest = bitwise_fragment.push_column_slice(rest);
276                        memory_fragment.push_column_slice(rest);
277                    },
278                    4 | 10..=14 => {
279                        // columns 4 - 10 to 14 are relevant for hasher, bitwise, memory chiplets
280                        // and ace chiplet
281                        let rest = hasher_fragment.push_column_slice(column);
282                        let rest = bitwise_fragment.push_column_slice(rest);
283                        let rest = memory_fragment.push_column_slice(rest);
284                        ace_fragment.push_column_slice(rest);
285                    },
286                    5..=9 => {
287                        // columns 5 - 9 are relevant to all chiplets
288                        let rest = hasher_fragment.push_column_slice(column);
289                        let rest = bitwise_fragment.push_column_slice(rest);
290                        let rest = memory_fragment.push_column_slice(rest);
291                        let rest = ace_fragment.push_column_slice(rest);
292                        kernel_rom_fragment.push_column_slice(rest);
293                    },
294                    15 | 16 => {
295                        // columns 15 and 16 are relevant for the hasher, memory and ace chiplets
296                        let rest = hasher_fragment.push_column_slice(column);
297                        // skip bitwise chiplet
298                        let (_, rest) = rest.split_at_mut(bitwise.trace_len());
299                        let rest = memory_fragment.push_column_slice(rest);
300                        ace_fragment.push_column_slice(rest);
301                    },
302                    17..=19 => {
303                        // columns 17-19 are relevant for hasher, memory, and ace chiplets
304                        // (hasher: mrupdate_id/is_start/is_final; memory: f_scw/w0/w1; ace cols)
305                        let rest = hasher_fragment.push_column_slice(column);
306                        // skip bitwise chiplet
307                        let (_, rest) = rest.split_at_mut(bitwise.trace_len());
308                        let rest = memory_fragment.push_column_slice(rest);
309                        ace_fragment.push_column_slice(rest);
310                    },
311                    20 => {
312                        // column 20 is relevant only for the hasher chiplet (s_perm)
313                        hasher_fragment.push_column_slice(column);
314                    },
315                    _ => panic!("invalid column index"),
316                }
317            }
318
319            // Fill all chiplets in parallel.
320            rayon::scope(|s| {
321                s.spawn(move |_| {
322                    hasher.fill_trace(&mut hasher_fragment);
323                });
324                s.spawn(move |_| {
325                    bitwise.fill_trace(&mut bitwise_fragment);
326                });
327                s.spawn(move |_| {
328                    memory.fill_trace(&mut memory_fragment);
329                });
330                s.spawn(move |_| {
331                    kernel_rom.fill_trace(&mut kernel_rom_fragment);
332                });
333                s.spawn(move |_| {
334                    ace.fill_trace(&mut ace_fragment);
335                });
336            });
337        }
338    }
339}
340
341// HELPER STRUCTS
342// ================================================================================================
343
344/// Result of a Merkle tree node update. The result contains the old Merkle_root, which
345/// corresponding to the old_value, and the new merkle_root, for the updated value. As well as the
346/// row address of the execution trace at which the computation started.
347#[derive(Debug, Copy, Clone)]
348pub struct MerkleRootUpdate {
349    address: Felt,
350    old_root: Word,
351    new_root: Word,
352}
353
354impl MerkleRootUpdate {
355    pub fn get_address(&self) -> Felt {
356        self.address
357    }
358    pub fn get_old_root(&self) -> Word {
359        self.old_root
360    }
361    pub fn get_new_root(&self) -> Word {
362        self.new_root
363    }
364}