Skip to main content

miden_processor/trace/chiplets/ace/
trace.rs

1use alloc::vec::Vec;
2use core::ops::Range;
3
4use miden_air::trace::{
5    RowIndex,
6    chiplets::ace::{
7        CLK_IDX, CTX_IDX, EVAL_OP_IDX, ID_0_IDX, ID_1_IDX, ID_2_IDX, M_0_IDX, M_1_IDX, PTR_IDX,
8        READ_NUM_EVAL_IDX, SELECTOR_BLOCK_IDX, SELECTOR_START_IDX, V_0_0_IDX, V_0_1_IDX, V_1_0_IDX,
9        V_1_1_IDX, V_2_0_IDX, V_2_1_IDX,
10    },
11};
12use miden_core::{
13    Felt, Word,
14    field::{BasedVectorSpace, QuadFelt},
15};
16
17use super::{
18    MAX_NUM_ACE_WIRES,
19    instruction::{Op, decode_instruction},
20};
21use crate::{ContextId, errors::AceError};
22
23/// Number of LogUp fractions in the wiring bus for rows in the `READ` section.
24pub const NUM_ACE_LOGUP_FRACTIONS_READ: usize = 2;
25/// Number of LogUp fractions in the wiring bus for rows in the `EVAL` section.
26pub const NUM_ACE_LOGUP_FRACTIONS_EVAL: usize = 3;
27
28/// Contains the variable and evaluation nodes resulting from the evaluation of a circuit.
29/// The output value is checked to be equal to 0.
30///
31/// The set of nodes is used to fill the ACE chiplet trace.
32#[derive(Debug, Clone)]
33pub struct CircuitEvaluation {
34    ctx: ContextId,
35    clk: RowIndex,
36
37    wire_bus: WireBus,
38
39    col_ptr: Vec<Felt>,
40    col_op: Vec<Felt>,
41
42    col_wire_out: WireColumn,
43    col_wire_left: WireColumn,
44    col_wire_right: WireColumn,
45
46    num_read_rows: u32,
47    num_eval_rows: u32,
48}
49
50impl CircuitEvaluation {
51    /// Generates the nodes in the graph generated by evaluating the inputs and circuit
52    /// located in a contiguous memory region.
53    ///
54    /// # Panics:
55    /// This function panics if the number of rows for each section leads to more than
56    /// [`MAX_NUM_ACE_WIRES`] wires.
57    pub fn new(ctx: ContextId, clk: RowIndex, num_read_rows: u32, num_eval_rows: u32) -> Self {
58        let num_wires = 2 * (num_read_rows as u64) + (num_eval_rows as u64);
59        assert!(num_wires <= MAX_NUM_ACE_WIRES as u64, "too many wires");
60
61        let num_rows = num_read_rows + num_eval_rows;
62
63        Self {
64            ctx,
65            clk,
66            wire_bus: WireBus::new(num_wires as u32),
67            col_ptr: Vec::with_capacity(num_rows as usize),
68            col_op: Vec::with_capacity(num_eval_rows as usize),
69            col_wire_out: WireColumn::new(num_rows as usize),
70            col_wire_left: WireColumn::new(num_rows as usize),
71            col_wire_right: WireColumn::new(num_eval_rows as usize),
72            num_read_rows,
73            num_eval_rows,
74        }
75    }
76
77    pub fn num_rows(&self) -> usize {
78        (self.num_read_rows + self.num_eval_rows) as usize
79    }
80
81    pub fn clk(&self) -> u32 {
82        self.clk.into()
83    }
84
85    pub fn ctx(&self) -> u32 {
86        self.ctx.into()
87    }
88
89    pub fn num_read_rows(&self) -> u32 {
90        self.num_read_rows
91    }
92
93    pub fn num_eval_rows(&self) -> u32 {
94        self.num_eval_rows
95    }
96
97    /// Reads the word from memory at `ptr`, interpreting it as `[v_00, v_01, v_10, v_11]`, and
98    /// adds wires with values `v_0 = QuadFelt(v_00, v_01)` and `v_1 = QuadFelt(v_10, v_11)`.
99    pub fn do_read(&mut self, ptr: Felt, word: Word) {
100        // Add first variable as QuadFelt to wire bus
101        let v_0 = QuadFelt::from_basis_coefficients_fn(|i: usize| [word[0], word[1]][i]);
102        let id_0 = self.wire_bus.insert(v_0);
103        self.col_wire_out.push(id_0, v_0);
104
105        // Add second variable as QuadFelt to wire bus
106        let v_1 = QuadFelt::from_basis_coefficients_fn(|i: usize| [word[2], word[3]][i]);
107        let id_1 = self.wire_bus.insert(v_1);
108        self.col_wire_left.push(id_1, v_1);
109
110        // Add pointer to trace
111        self.col_ptr.push(ptr);
112    }
113
114    /// Reads the next instruction at `ptr`, requests the inputs from the wire bus
115    /// and inserts a new wire with the result.
116    pub fn do_eval(&mut self, ptr: Felt, instruction: Felt) -> Result<(), AceError> {
117        // Decode instruction, ensuring it is valid
118        let (id_l, id_r, op) = decode_instruction(instruction)
119            .ok_or(AceError("failed to decode instruction".into()))?;
120
121        // Read value of id_l from wire bus, increasing its multiplicity
122        let v_l = self
123            .wire_bus
124            .read_value(id_l)
125            .ok_or(AceError("failed to read from the wiring bus".into()))?;
126        let id_l = Felt::from_u32(id_l);
127        self.col_wire_left.push(id_l, v_l);
128
129        // Read value of id_r from wire bus, increasing its multiplicity
130        let v_r = self
131            .wire_bus
132            .read_value(id_r)
133            .ok_or(AceError("failed to read from the wiring bus".into()))?;
134        let id_r = Felt::from_u32(id_r);
135        self.col_wire_right.push(id_r, v_r);
136
137        // Compute v_out and insert it into the wire bus.
138        let v_out = match op {
139            Op::Sub => v_l - v_r,
140            Op::Mul => v_l * v_r,
141            Op::Add => v_l + v_r,
142        };
143        let id_out = self.wire_bus.insert(v_out);
144        self.col_wire_out.push(id_out, v_out);
145
146        // Add op to column
147        let op_sub = -Felt::ONE;
148        let op_mul = Felt::ZERO;
149        let op_add = Felt::ONE;
150        let op = match op {
151            Op::Sub => op_sub,
152            Op::Mul => op_mul,
153            Op::Add => op_add,
154        };
155        self.col_op.push(op);
156
157        // Add pointer to trace
158        self.col_ptr.push(ptr);
159
160        Ok(())
161    }
162
163    pub fn fill(&self, offset: usize, columns: &mut [Vec<Felt>]) {
164        let num_read_rows = self.num_read_rows as usize;
165        let num_eval_rows = self.num_eval_rows as usize;
166        let num_rows = num_read_rows + num_eval_rows;
167        let read_range = Range {
168            start: offset,
169            end: offset + num_read_rows,
170        };
171        let eval_range = Range {
172            start: read_range.end,
173            end: read_range.end + num_eval_rows,
174        };
175
176        // Fill start selector
177        columns[SELECTOR_START_IDX][offset] = Felt::ONE;
178        columns[SELECTOR_START_IDX][(offset + 1)..].fill(Felt::ZERO);
179
180        // Block flag column
181        let f_read = Felt::ZERO;
182        let f_eval = Felt::ONE;
183        columns[SELECTOR_BLOCK_IDX][read_range.clone()].fill(f_read);
184        columns[SELECTOR_BLOCK_IDX][eval_range.clone()].fill(f_eval);
185
186        // Fill ctx column which is constant across the section
187        let ctx_felt = self.ctx.into();
188        columns[CTX_IDX][offset..offset + num_rows].fill(ctx_felt);
189
190        // Fill ptr column.
191        columns[PTR_IDX][offset..offset + num_rows].copy_from_slice(&self.col_ptr);
192
193        // Fill clk column which is constant across the section
194        let clk_felt = self.clk.into();
195        columns[CLK_IDX][offset..offset + num_rows].fill(clk_felt);
196
197        // Fill n_eval which is constant across the read block
198        let eval_section_first_idx = Felt::from_u32(self.num_eval_rows - 1);
199        columns[READ_NUM_EVAL_IDX][read_range.clone()].fill(eval_section_first_idx);
200
201        // Fill OP column for EVAL rows
202        columns[EVAL_OP_IDX][eval_range.clone()].copy_from_slice(&self.col_op);
203
204        // Fill wire 0 columns for all rows
205        columns[ID_0_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_out.id);
206        columns[V_0_0_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_out.v_0);
207        columns[V_0_1_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_out.v_1);
208
209        // Fill wire 1 columns for all rows
210        columns[ID_1_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_left.id);
211        columns[V_1_0_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_left.v_0);
212        columns[V_1_1_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_left.v_1);
213
214        // Fill wire 2 columns for EVAL rows
215        columns[ID_2_IDX][eval_range.clone()].copy_from_slice(&self.col_wire_right.id);
216        columns[V_2_0_IDX][eval_range.clone()].copy_from_slice(&self.col_wire_right.v_0);
217        columns[V_2_1_IDX][eval_range.clone()].copy_from_slice(&self.col_wire_right.v_1);
218
219        // Fill multiplicity 0 column for all rows
220        let mut multiplicities_iter = self.wire_bus.wires.iter().map(|(_v, m)| Felt::from_u32(*m));
221        // In the READ block, we inserted wires w_0 and w_1
222        for row_index in read_range {
223            let m_0 = multiplicities_iter
224                .next()
225                .expect("the m0 multiplicities were not constructed properly");
226            let m_1 = multiplicities_iter
227                .next()
228                .expect("the m1 multiplicities were not constructed properly");
229            columns[M_0_IDX][row_index] = m_0;
230            columns[M_1_IDX][row_index] = m_1;
231        }
232        // In the EVAL block, we inserted wire w_0
233        for row_index in eval_range {
234            let m_0 = multiplicities_iter
235                .next()
236                .expect("the m0 multiplicities were not constructed properly");
237            columns[M_0_IDX][row_index] = m_0;
238        }
239
240        debug_assert!(multiplicities_iter.next().is_none());
241    }
242
243    /// Returns the output value, if the circuit has finished evaluating.
244    pub fn output_value(&self) -> Option<QuadFelt> {
245        if !self.wire_bus.is_finalized() {
246            return None;
247        }
248        self.wire_bus.wires.last().map(|(v, _m)| *v)
249    }
250}
251
252/// Set of columns for a given wire containing `[id, v_0, v_1]`, where `id` is the wire identifier
253/// and `v = (v_0, v_1)` is the quadratic extension field element value of the wire.
254#[derive(Debug, Clone)]
255struct WireColumn {
256    id: Vec<Felt>,
257    v_0: Vec<Felt>,
258    v_1: Vec<Felt>,
259}
260
261impl WireColumn {
262    fn new(num_rows: usize) -> Self {
263        Self {
264            id: Vec::with_capacity(num_rows),
265            v_0: Vec::with_capacity(num_rows),
266            v_1: Vec::with_capacity(num_rows),
267        }
268    }
269
270    /// Pushes the wire `(id, v)` to the columns.
271    fn push(&mut self, id: Felt, v: QuadFelt) {
272        self.id.push(id);
273        let v: &[Felt] = v.as_basis_coefficients_slice();
274        self.v_0.push(v[0]);
275        self.v_1.push(v[1]);
276    }
277}
278
279/// A LogUp-based bus used for wiring the gates of the circuit.
280///
281/// Gates are fan-in 2 but can have fan-out up to the field characteristic which, given the bounds
282/// on the execution trace length, means practically arbitrary fan-out.
283/// The main idea, with some slight variations between the `READ` and `EVAL` sections, is, for each
284/// gate, to "receive" the values of the input wires from the bus and to "send" the value of
285/// the value of the output wire back with multiplicity equal to the fan-out of the respective gate.
286/// Note that the messages include extra data in order to avoid collisions.
287#[derive(Debug, Clone)]
288struct WireBus {
289    // Circuit ID as Felt of the next wire to be inserted
290    id_next: Felt,
291    // Pairs of values and multiplicities
292    // The wire with index `id` is stored at `num_wires - 1 - id`
293    wires: Vec<(QuadFelt, u32)>,
294    // Total expected number of wires to be inserted.
295    num_wires: u32,
296}
297
298impl WireBus {
299    fn new(num_wires: u32) -> Self {
300        Self {
301            wires: Vec::with_capacity(num_wires as usize),
302            num_wires,
303            id_next: Felt::from_u32(num_wires - 1),
304        }
305    }
306
307    /// Inserts a new value into the bus, and returns its expected id as `Felt`
308    fn insert(&mut self, value: QuadFelt) -> Felt {
309        debug_assert!(!self.is_finalized());
310        self.wires.push((value, 0));
311        let id = self.id_next;
312        self.id_next -= Felt::ONE;
313        id
314    }
315
316    /// Reads the value of a wire with given `id`, incrementing its multiplicity.
317    /// Returns `None` if the requested wire has not been inserted yet.
318    fn read_value(&mut self, id: u32) -> Option<QuadFelt> {
319        // Ensures subtracting the id from num_wires results in a valid wire index
320        let (v, m) = self
321            .num_wires
322            .checked_sub(id + 1)
323            .and_then(|id| self.wires.get_mut(id as usize))?;
324        *m += 1;
325        Some(*v)
326    }
327
328    /// Return true if the expected number of wires have been inserted.
329    fn is_finalized(&self) -> bool {
330        self.wires.len() == self.num_wires as usize
331    }
332}