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}