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