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, PrimeCharacteristicRing, 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) =
119            decode_instruction(instruction).ok_or(AceError::FailedDecodeInstruction)?;
120
121        // Read value of id_l from wire bus, increasing its multiplicity
122        let v_l = self.wire_bus.read_value(id_l).ok_or(AceError::FailedWireBusRead)?;
123        let id_l = Felt::from_u32(id_l);
124        self.col_wire_left.push(id_l, v_l);
125
126        // Read value of id_r from wire bus, increasing its multiplicity
127        let v_r = self.wire_bus.read_value(id_r).ok_or(AceError::FailedWireBusRead)?;
128        let id_r = Felt::from_u32(id_r);
129        self.col_wire_right.push(id_r, v_r);
130
131        // Compute v_out and insert it into the wire bus.
132        let v_out = match op {
133            Op::Sub => v_l - v_r,
134            Op::Mul => v_l * v_r,
135            Op::Add => v_l + v_r,
136        };
137        let id_out = self.wire_bus.insert(v_out);
138        self.col_wire_out.push(id_out, v_out);
139
140        // Add op to column
141        let op_sub = -Felt::ONE;
142        let op_mul = Felt::ZERO;
143        let op_add = Felt::ONE;
144        let op = match op {
145            Op::Sub => op_sub,
146            Op::Mul => op_mul,
147            Op::Add => op_add,
148        };
149        self.col_op.push(op);
150
151        // Add pointer to trace
152        self.col_ptr.push(ptr);
153
154        Ok(())
155    }
156
157    pub fn fill(&self, offset: usize, columns: &mut [Vec<Felt>]) {
158        let num_read_rows = self.num_read_rows as usize;
159        let num_eval_rows = self.num_eval_rows as usize;
160        let num_rows = num_read_rows + num_eval_rows;
161        let read_range = Range {
162            start: offset,
163            end: offset + num_read_rows,
164        };
165        let eval_range = Range {
166            start: read_range.end,
167            end: read_range.end + num_eval_rows,
168        };
169
170        // Fill start selector
171        columns[SELECTOR_START_IDX][offset] = Felt::ONE;
172        columns[SELECTOR_START_IDX][(offset + 1)..].fill(Felt::ZERO);
173
174        // Block flag column
175        let f_read = Felt::ZERO;
176        let f_eval = Felt::ONE;
177        columns[SELECTOR_BLOCK_IDX][read_range.clone()].fill(f_read);
178        columns[SELECTOR_BLOCK_IDX][eval_range.clone()].fill(f_eval);
179
180        // Fill ctx column which is constant across the section
181        let ctx_felt = self.ctx.into();
182        columns[CTX_IDX][offset..offset + num_rows].fill(ctx_felt);
183
184        // Fill ptr column.
185        columns[PTR_IDX][offset..offset + num_rows].copy_from_slice(&self.col_ptr);
186
187        // Fill clk column which is constant across the section
188        let clk_felt = self.clk.into();
189        columns[CLK_IDX][offset..offset + num_rows].fill(clk_felt);
190
191        // Fill n_eval which is constant across the read block
192        let eval_section_first_idx = Felt::from_u32(self.num_eval_rows - 1);
193        columns[READ_NUM_EVAL_IDX][read_range.clone()].fill(eval_section_first_idx);
194
195        // Fill OP column for EVAL rows
196        columns[EVAL_OP_IDX][eval_range.clone()].copy_from_slice(&self.col_op);
197
198        // Fill wire 0 columns for all rows
199        columns[ID_0_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_out.id);
200        columns[V_0_0_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_out.v_0);
201        columns[V_0_1_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_out.v_1);
202
203        // Fill wire 1 columns for all rows
204        columns[ID_1_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_left.id);
205        columns[V_1_0_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_left.v_0);
206        columns[V_1_1_IDX][offset..offset + num_rows].copy_from_slice(&self.col_wire_left.v_1);
207
208        // Fill wire 2 columns for EVAL rows
209        columns[ID_2_IDX][eval_range.clone()].copy_from_slice(&self.col_wire_right.id);
210        columns[V_2_0_IDX][eval_range.clone()].copy_from_slice(&self.col_wire_right.v_0);
211        columns[V_2_1_IDX][eval_range.clone()].copy_from_slice(&self.col_wire_right.v_1);
212
213        // Fill multiplicity 0 column for all rows
214        let mut multiplicities_iter = self.wire_bus.wires.iter().map(|(_v, m)| Felt::from_u32(*m));
215        // In the READ block, we inserted wires w_0 and w_1
216        for row_index in read_range {
217            let m_0 = multiplicities_iter
218                .next()
219                .expect("the m0 multiplicities were not constructed properly");
220            let m_1 = multiplicities_iter
221                .next()
222                .expect("the m1 multiplicities were not constructed properly");
223            columns[M_0_IDX][row_index] = m_0;
224            columns[M_1_IDX][row_index] = m_1;
225        }
226        // In the EVAL block, we inserted wire w_0
227        for row_index in eval_range {
228            let m_0 = multiplicities_iter
229                .next()
230                .expect("the m0 multiplicities were not constructed properly");
231            columns[M_0_IDX][row_index] = m_0;
232        }
233
234        debug_assert!(multiplicities_iter.next().is_none());
235    }
236
237    /// Returns the output value, if the circuit has finished evaluating.
238    pub fn output_value(&self) -> Option<QuadFelt> {
239        if !self.wire_bus.is_finalized() {
240            return None;
241        }
242        self.wire_bus.wires.last().map(|(v, _m)| *v)
243    }
244}
245
246/// Set of columns for a given wire containing `[id, v_0, v_1]`, where `id` is the wire identifier
247/// and `v = (v_0, v_1)` is the quadratic extension field element value of the wire.
248#[derive(Debug, Clone)]
249struct WireColumn {
250    id: Vec<Felt>,
251    v_0: Vec<Felt>,
252    v_1: Vec<Felt>,
253}
254
255impl WireColumn {
256    fn new(num_rows: usize) -> Self {
257        Self {
258            id: Vec::with_capacity(num_rows),
259            v_0: Vec::with_capacity(num_rows),
260            v_1: Vec::with_capacity(num_rows),
261        }
262    }
263
264    /// Pushes the wire `(id, v)` to the columns.
265    fn push(&mut self, id: Felt, v: QuadFelt) {
266        self.id.push(id);
267        let v: &[Felt] = v.as_basis_coefficients_slice();
268        self.v_0.push(v[0]);
269        self.v_1.push(v[1]);
270    }
271}
272
273/// A LogUp-based bus used for wiring the gates of the circuit.
274///
275/// Gates are fan-in 2 but can have fan-out up to the field characteristic which, given the bounds
276/// on the execution trace length, means practically arbitrary fan-out.
277/// The main idea, with some slight variations between the `READ` and `EVAL` sections, is, for each
278/// gate, to "receive" the values of the input wires from the bus and to "send" the value of
279/// the value of the output wire back with multiplicity equal to the fan-out of the respective gate.
280/// Note that the messages include extra data in order to avoid collisions.
281#[derive(Debug, Clone)]
282struct WireBus {
283    // Circuit ID as Felt of the next wire to be inserted
284    id_next: Felt,
285    // Pairs of values and multiplicities
286    // The wire with index `id` is stored at `num_wires - 1 - id`
287    wires: Vec<(QuadFelt, u32)>,
288    // Total expected number of wires to be inserted.
289    num_wires: u32,
290}
291
292impl WireBus {
293    fn new(num_wires: u32) -> Self {
294        Self {
295            wires: Vec::with_capacity(num_wires as usize),
296            num_wires,
297            id_next: Felt::from_u32(num_wires - 1),
298        }
299    }
300
301    /// Inserts a new value into the bus, and returns its expected id as `Felt`
302    fn insert(&mut self, value: QuadFelt) -> Felt {
303        debug_assert!(!self.is_finalized());
304        self.wires.push((value, 0));
305        let id = self.id_next;
306        self.id_next -= Felt::ONE;
307        id
308    }
309
310    /// Reads the value of a wire with given `id`, incrementing its multiplicity.
311    /// Returns `None` if the requested wire has not been inserted yet.
312    fn read_value(&mut self, id: u32) -> Option<QuadFelt> {
313        // Ensures subtracting the id from num_wires results in a valid wire index
314        let (v, m) = self
315            .num_wires
316            .checked_sub(id + 1)
317            .and_then(|id| self.wires.get_mut(id as usize))?;
318        *m += 1;
319        Some(*v)
320    }
321
322    /// Return true if the expected number of wires have been inserted.
323    fn is_finalized(&self) -> bool {
324        self.wires.len() == self.num_wires as usize
325    }
326}