Skip to main content

miden_processor/trace/chiplets/ace/
mod.rs

1use alloc::{collections::BTreeMap, vec::Vec};
2
3use miden_air::trace::{MainTrace, RowIndex, chiplets::ace::ACE_CHIPLET_NUM_COLS};
4use miden_core::{Felt, ZERO, field::ExtensionField};
5
6use crate::trace::TraceFragment;
7
8mod trace;
9pub use trace::{CircuitEvaluation, NUM_ACE_LOGUP_FRACTIONS_EVAL, NUM_ACE_LOGUP_FRACTIONS_READ};
10
11mod instruction;
12#[cfg(test)]
13mod tests;
14
15pub const PTR_OFFSET_ELEM: Felt = Felt::ONE;
16pub const PTR_OFFSET_WORD: Felt = Felt::new(4);
17pub const MAX_NUM_ACE_WIRES: u32 = instruction::MAX_ID;
18
19/// Arithmetic circuit evaluation (ACE) chiplet.
20///
21/// This is a VM chiplet used to evaluate arithmetic circuits given some input, which is equivalent
22/// to evaluating some multi-variate polynomial at a tuple representing the input.
23///
24/// During the course of the VM execution, we keep track of all calls to the ACE chiplet in an
25/// [`CircuitEvaluation`] per call. This is then used to generate the full trace of the ACE chiplet.
26#[derive(Debug, Default)]
27pub struct Ace {
28    circuit_evaluations: BTreeMap<RowIndex, CircuitEvaluation>,
29}
30
31impl Ace {
32    /// Gets the total trace length of the ACE chiplet.
33    pub(crate) fn trace_len(&self) -> usize {
34        self.circuit_evaluations.values().map(|eval_ctx| eval_ctx.num_rows()).sum()
35    }
36
37    /// Fills the portion of the main trace allocated to the ACE chiplet.
38    ///
39    /// This also returns helper data needed for generating the part of the auxiliary trace
40    /// associated with the ACE chiplet.
41    pub(crate) fn fill_trace(self, trace: &mut TraceFragment) -> Vec<EvaluatedCircuitsMetadata> {
42        // make sure fragment dimensions are consistent with the dimensions of this trace
43        debug_assert_eq!(self.trace_len(), trace.len(), "inconsistent trace lengths");
44        debug_assert_eq!(ACE_CHIPLET_NUM_COLS, trace.width(), "inconsistent trace widths");
45
46        let mut gen_trace: [Vec<Felt>; ACE_CHIPLET_NUM_COLS] = (0..ACE_CHIPLET_NUM_COLS)
47            .map(|_| vec![ZERO; self.trace_len()])
48            .collect::<Vec<_>>()
49            .try_into()
50            .expect("failed to convert vector to array");
51
52        let mut sections_info = Vec::with_capacity(self.circuit_evaluations.keys().count());
53
54        let mut offset = 0;
55        for eval_ctx in self.circuit_evaluations.into_values() {
56            eval_ctx.fill(offset, &mut gen_trace);
57            offset += eval_ctx.num_rows();
58            let section = EvaluatedCircuitsMetadata::from_evaluation_context(&eval_ctx);
59            sections_info.push(section);
60        }
61
62        for (out_column, column) in trace.columns().zip(gen_trace) {
63            out_column.copy_from_slice(&column);
64        }
65
66        sections_info
67    }
68
69    /// Adds an entry resulting from a call to the ACE chiplet.
70    pub(crate) fn add_circuit_evaluation(
71        &mut self,
72        clk: RowIndex,
73        circuit_eval: CircuitEvaluation,
74    ) {
75        self.circuit_evaluations.insert(clk, circuit_eval);
76    }
77}
78
79/// Stores metadata associated to an evaluated circuit needed for building the portion of the
80/// auxiliary trace segment relevant for the ACE chiplet.
81#[derive(Debug, Default, Clone)]
82pub struct EvaluatedCircuitsMetadata {
83    ctx: u32,
84    clk: u32,
85    num_vars: u32,
86    num_evals: u32,
87}
88
89impl EvaluatedCircuitsMetadata {
90    pub fn clk(&self) -> u32 {
91        self.clk
92    }
93
94    pub fn ctx(&self) -> u32 {
95        self.ctx
96    }
97
98    pub fn num_vars(&self) -> u32 {
99        self.num_vars
100    }
101
102    pub fn num_evals(&self) -> u32 {
103        self.num_evals
104    }
105
106    fn from_evaluation_context(eval_ctx: &CircuitEvaluation) -> EvaluatedCircuitsMetadata {
107        EvaluatedCircuitsMetadata {
108            ctx: eval_ctx.ctx(),
109            clk: eval_ctx.clk(),
110            num_vars: eval_ctx.num_read_rows(),
111            num_evals: eval_ctx.num_eval_rows(),
112        }
113    }
114}
115
116/// Stores metadata for the ACE chiplet useful when building the portion of the auxiliary
117/// trace segment relevant for the ACE chiplet.
118///
119/// This data is already present in the main trace but collecting it here allows us to simplify
120/// the logic for building the auxiliary segment portion for the ACE chiplet.
121/// For example, we know that `clk` and `ctx` are constant throughout each circuit evaluation
122/// and we also know the exact number of ACE chiplet rows per circuit evaluation and the exact
123/// number of rows per `READ` and `EVAL` portions, which allows us to avoid the need to compute
124/// selectors as part of the logic of auxiliary trace generation.
125#[derive(Clone, Debug, Default)]
126pub struct AceHints {
127    offset_chiplet_trace: usize,
128    pub sections: Vec<EvaluatedCircuitsMetadata>,
129}
130
131impl AceHints {
132    pub fn new(offset_chiplet_trace: usize, sections: Vec<EvaluatedCircuitsMetadata>) -> Self {
133        Self { offset_chiplet_trace, sections }
134    }
135
136    pub(crate) fn offset(&self) -> usize {
137        self.offset_chiplet_trace
138    }
139
140    pub(crate) fn build_divisors<E: ExtensionField<Felt>>(
141        &self,
142        main_trace: &MainTrace,
143        alphas: &[E],
144    ) -> Vec<E> {
145        let num_fractions = self.num_fractions();
146        let mut total_values = vec![E::ZERO; num_fractions];
147        let mut total_inv_values = vec![E::ZERO; num_fractions];
148
149        let mut chiplet_offset = self.offset_chiplet_trace;
150        let mut values_offset = 0;
151        let mut acc = E::ONE;
152        for section in self.sections.iter() {
153            let clk = section.clk();
154            let ctx = section.ctx();
155
156            let values = &mut total_values[values_offset
157                ..values_offset + NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize];
158            let inv_values = &mut total_inv_values[values_offset
159                ..values_offset + NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize];
160
161            // read section
162            for (i, (value, inv_value)) in values
163                .chunks_mut(NUM_ACE_LOGUP_FRACTIONS_READ)
164                .zip(inv_values.chunks_mut(NUM_ACE_LOGUP_FRACTIONS_READ))
165                .enumerate()
166            {
167                let trace_row = i + chiplet_offset;
168
169                let wire_0 = main_trace.chiplet_ace_wire_0(trace_row.into());
170                let wire_1 = main_trace.chiplet_ace_wire_1(trace_row.into());
171
172                let value_0 = alphas[0]
173                    + alphas[1] * Felt::from_u32(clk)
174                    + alphas[2] * Felt::from_u32(ctx)
175                    + alphas[3] * wire_0[0]
176                    + alphas[4] * wire_0[1]
177                    + alphas[5] * wire_0[2];
178                let value_1 = alphas[0]
179                    + alphas[1] * Felt::from_u32(clk)
180                    + alphas[2] * Felt::from_u32(ctx)
181                    + alphas[3] * wire_1[0]
182                    + alphas[4] * wire_1[1]
183                    + alphas[5] * wire_1[2];
184
185                value[0] = value_0;
186                value[1] = value_1;
187                inv_value[0] = acc;
188                acc *= value_0;
189                inv_value[1] = acc;
190                acc *= value_1;
191            }
192
193            chiplet_offset += section.num_vars() as usize;
194            values_offset += NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize;
195
196            // eval section
197            let values = &mut total_values[values_offset
198                ..values_offset + NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize];
199            let inv_values = &mut total_inv_values[values_offset
200                ..values_offset + NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize];
201            for (i, (value, inv_value)) in values
202                .chunks_mut(NUM_ACE_LOGUP_FRACTIONS_EVAL)
203                .zip(inv_values.chunks_mut(NUM_ACE_LOGUP_FRACTIONS_EVAL))
204                .enumerate()
205            {
206                let trace_row = i + chiplet_offset;
207
208                let wire_0 = main_trace.chiplet_ace_wire_0(trace_row.into());
209                let wire_1 = main_trace.chiplet_ace_wire_1(trace_row.into());
210                let wire_2 = main_trace.chiplet_ace_wire_2(trace_row.into());
211
212                let value_0 = alphas[0]
213                    + alphas[1] * Felt::from_u32(clk)
214                    + alphas[2] * Felt::from_u32(ctx)
215                    + alphas[3] * wire_0[0]
216                    + alphas[4] * wire_0[1]
217                    + alphas[5] * wire_0[2];
218
219                let value_1 = alphas[0]
220                    + alphas[1] * Felt::from_u32(clk)
221                    + alphas[2] * Felt::from_u32(ctx)
222                    + alphas[3] * wire_1[0]
223                    + alphas[4] * wire_1[1]
224                    + alphas[5] * wire_1[2];
225
226                let value_2 = alphas[0]
227                    + alphas[1] * Felt::from_u32(clk)
228                    + alphas[2] * Felt::from_u32(ctx)
229                    + alphas[3] * wire_2[0]
230                    + alphas[4] * wire_2[1]
231                    + alphas[5] * wire_2[2];
232
233                value[0] = value_0;
234                value[1] = value_1;
235                value[2] = value_2;
236                inv_value[0] = acc;
237                acc *= value_0;
238                inv_value[1] = acc;
239                acc *= value_1;
240                inv_value[2] = acc;
241                acc *= value_2;
242            }
243
244            chiplet_offset += section.num_evals() as usize;
245            values_offset += NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize;
246        }
247
248        // invert the accumulated product
249        acc = acc.inverse();
250
251        for i in (0..total_values.len()).rev() {
252            total_inv_values[i] *= acc;
253            acc *= total_values[i];
254        }
255
256        total_inv_values
257    }
258
259    fn num_fractions(&self) -> usize {
260        self.sections
261            .iter()
262            .map(|section| {
263                NUM_ACE_LOGUP_FRACTIONS_READ * (section.num_vars as usize)
264                    + NUM_ACE_LOGUP_FRACTIONS_EVAL * (section.num_evals as usize)
265            })
266            .sum()
267    }
268}