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