Skip to main content

miden_ace_codegen/layout/
plan.rs

1use super::InputKey;
2use crate::EXT_DEGREE;
3
4/// A contiguous region of inputs within the ACE READ layout.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub(crate) struct InputRegion {
7    pub offset: usize,
8    pub width: usize,
9}
10
11impl InputRegion {
12    /// Map a region-local index to a global input index.
13    pub fn index(&self, local: usize) -> Option<usize> {
14        (local < self.width).then(|| self.offset + local)
15    }
16}
17
18/// Counts needed to build the ACE input layout.
19#[derive(Debug, Clone, Copy)]
20pub struct InputCounts {
21    /// Width of the main trace.
22    pub width: usize,
23    /// Width of the aux trace.
24    pub aux_width: usize,
25    /// Number of committed boundary values (accumulator column finals).
26    pub num_aux_boundary: usize,
27    /// Number of public inputs.
28    pub num_public: usize,
29    /// Number of variable-length public input (VLPI) reduction slots (in EF elements).
30    /// This is derived from `AceConfig::num_vlpi_groups` by the layout policy:
31    /// MASM expands each group to 2 EF slots (word-aligned); Native uses 1 per group.
32    pub num_vlpi: usize,
33    /// Number of randomness challenges used by the AIR.
34    pub num_randomness: usize,
35    /// Number of periodic columns.
36    pub num_periodic: usize,
37    /// Number of quotient chunks.
38    pub num_quotient_chunks: usize,
39}
40
41/// Grouped regions for the ACE input layout.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub(crate) struct LayoutRegions {
44    /// Region containing fixed-length public values.
45    pub public_values: InputRegion,
46    /// Region containing variable-length public input reductions.
47    pub vlpi_reductions: InputRegion,
48    /// Region containing randomness inputs (alpha, beta).
49    pub randomness: InputRegion,
50    /// Main trace OOD values at `zeta`.
51    pub main_curr: InputRegion,
52    /// Aux trace OOD coordinates at `zeta`.
53    pub aux_curr: InputRegion,
54    /// Quotient chunk OOD coordinates at `zeta`.
55    pub quotient_curr: InputRegion,
56    /// Main trace OOD values at `g * zeta`.
57    pub main_next: InputRegion,
58    /// Aux trace OOD coordinates at `g * zeta`.
59    pub aux_next: InputRegion,
60    /// Quotient chunk OOD coordinates at `g * zeta`.
61    pub quotient_next: InputRegion,
62    /// Aux bus boundary values.
63    pub aux_bus_boundary: InputRegion,
64    /// Stark variables (selectors, powers, weights).
65    pub stark_vars: InputRegion,
66}
67
68/// Indexes of canonical verifier scalars inside the stark-vars block.
69///
70/// Every slot in the ACE input array is an extension-field (EF) element --
71/// the circuit operates entirely in the extension field. However, some of
72/// these scalars are inherently base-field values that the MASM verifier
73/// stores as `(val, 0)` in the EF slot.
74///
75/// See the module documentation on [`super::super::dag::lower`] for how each
76/// variable enters the verifier expression.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub(crate) struct StarkVarIndices {
79    // -- Extension-field values (slots 0-5) --
80    /// Composition challenge `alpha` for folding constraints.
81    pub alpha: usize,
82    /// `zeta^N` where N is the trace length.
83    pub z_pow_n: usize,
84    /// `zeta^(N / max_cycle_len)` for periodic column evaluation.
85    pub z_k: usize,
86    /// Precomputed first-row selector: `(z^N - 1) / (z - 1)`.
87    pub is_first: usize,
88    /// Precomputed last-row selector: `(z^N - 1) / (z - g^{-1})`.
89    pub is_last: usize,
90    /// Precomputed transition selector: `z - g^{-1}`.
91    pub is_transition: usize,
92    /// Batching challenge `gamma` for reduced_aux_values.
93    pub gamma: usize,
94
95    // -- Base-field values stored as (val, 0) in EF slots --
96    /// First barycentric weight `1 / (k * s0^{k-1})`.
97    pub weight0: usize,
98    /// `f = h^N` (chunk shift ratio between cosets).
99    pub f: usize,
100    /// `s0 = offset^N` (first chunk shift).
101    pub s0: usize,
102
103    // -- Multi-AIR additions (only present when `AceConfig::is_multi_air`) --
104    /// β coefficient for Core in `combined = mab_core · core_acc + mab_chip · chip_acc`.
105    /// The verifier sets `(mab_core, mab_chip) = (β, 1)` or `(1, β)` per proof_order.
106    pub multi_air_beta_core: Option<usize>,
107    pub multi_air_beta_chip: Option<usize>,
108    /// Per-AIR lifted selectors for Core (at `z^{r_core}`).
109    pub is_first_core: Option<usize>,
110    pub is_last_core: Option<usize>,
111    pub is_transition_core: Option<usize>,
112    /// Per-AIR lifted selectors for Chiplets (at `z^{r_chip}`).
113    pub is_first_chip: Option<usize>,
114    pub is_last_chip: Option<usize>,
115    pub is_transition_chip: Option<usize>,
116}
117
118/// ACE input layout for Plonky3-based verifier logic.
119///
120/// This describes the exact ordering and alignment of inputs consumed by the
121/// ACE chiplet (READ section).
122#[derive(Debug, Clone)]
123pub struct InputLayout {
124    /// Grouped regions for the ACE input layout.
125    pub(crate) regions: LayoutRegions,
126    /// Input index for aux randomness alpha.
127    pub(crate) aux_rand_alpha: usize,
128    /// Input index for aux randomness beta.
129    pub(crate) aux_rand_beta: usize,
130    /// Stride between logical VLPI groups (2 for MASM word-aligned, 1 for native).
131    pub(crate) vlpi_stride: usize,
132    /// Indexes into the stark-vars region.
133    pub(crate) stark: StarkVarIndices,
134    /// Total number of inputs (length of the READ section).
135    pub total_inputs: usize,
136    /// Counts used to derive the layout.
137    pub counts: InputCounts,
138}
139
140impl InputLayout {
141    pub(crate) fn mapper(&self) -> super::InputKeyMapper<'_> {
142        super::InputKeyMapper { layout: self }
143    }
144
145    /// Map a logical `InputKey` into the flat input index, if present.
146    pub fn index(&self, key: InputKey) -> Option<usize> {
147        self.mapper().index_of(key)
148    }
149
150    /// Validate internal invariants for this layout (region sizes, key ranges, randomness inputs).
151    pub(crate) fn validate(&self) {
152        let mut max_end = 0usize;
153        for region in [
154            self.regions.public_values,
155            self.regions.vlpi_reductions,
156            self.regions.randomness,
157            self.regions.main_curr,
158            self.regions.aux_curr,
159            self.regions.quotient_curr,
160            self.regions.main_next,
161            self.regions.aux_next,
162            self.regions.quotient_next,
163            self.regions.aux_bus_boundary,
164            self.regions.stark_vars,
165        ] {
166            max_end = max_end.max(region.offset.saturating_add(region.width));
167        }
168
169        assert!(max_end <= self.total_inputs, "regions exceed total_inputs");
170
171        let aux_coord_width = self.counts.aux_width * EXT_DEGREE;
172        assert_eq!(self.regions.aux_curr.width, aux_coord_width, "aux_curr width mismatch");
173        assert_eq!(self.regions.aux_next.width, aux_coord_width, "aux_next width mismatch");
174
175        let quotient_width = self.counts.num_quotient_chunks * EXT_DEGREE;
176        assert_eq!(
177            self.regions.quotient_curr.width, quotient_width,
178            "quotient_curr width mismatch"
179        );
180        assert_eq!(
181            self.regions.quotient_next.width, quotient_width,
182            "quotient_next width mismatch"
183        );
184        assert_eq!(
185            self.regions.aux_bus_boundary.width, self.counts.num_aux_boundary,
186            "aux bus boundary width mismatch"
187        );
188
189        let stark_start = self.regions.stark_vars.offset;
190        let stark_end = stark_start + self.regions.stark_vars.width;
191        let check = |name: &str, idx: usize| {
192            assert!(idx >= stark_start && idx < stark_end, "stark var {name} out of range");
193        };
194        // Extension-field slots.
195        check("alpha", self.stark.alpha);
196        check("z_pow_n", self.stark.z_pow_n);
197        check("z_k", self.stark.z_k);
198        check("is_first", self.stark.is_first);
199        check("is_last", self.stark.is_last);
200        check("is_transition", self.stark.is_transition);
201        check("gamma", self.stark.gamma);
202        // Base-field slots (stored as (val, 0) in the EF slot).
203        check("weight0", self.stark.weight0);
204        check("f", self.stark.f);
205        check("s0", self.stark.s0);
206        if let Some(idx) = self.stark.multi_air_beta_core {
207            check("multi_air_beta_core", idx);
208        }
209        if let Some(idx) = self.stark.multi_air_beta_chip {
210            check("multi_air_beta_chip", idx);
211        }
212        if let Some(idx) = self.stark.is_first_core {
213            check("is_first_core", idx);
214        }
215        if let Some(idx) = self.stark.is_last_core {
216            check("is_last_core", idx);
217        }
218        if let Some(idx) = self.stark.is_transition_core {
219            check("is_transition_core", idx);
220        }
221        if let Some(idx) = self.stark.is_first_chip {
222            check("is_first_chip", idx);
223        }
224        if let Some(idx) = self.stark.is_last_chip {
225            check("is_last_chip", idx);
226        }
227        if let Some(idx) = self.stark.is_transition_chip {
228            check("is_transition_chip", idx);
229        }
230
231        let rand_start = self.regions.randomness.offset;
232        let rand_end = rand_start + self.regions.randomness.width;
233        assert!(
234            self.aux_rand_alpha >= rand_start && self.aux_rand_alpha < rand_end,
235            "aux_rand_alpha out of randomness region"
236        );
237        assert!(
238            self.aux_rand_beta >= rand_start && self.aux_rand_beta < rand_end,
239            "aux_rand_beta out of randomness region"
240        );
241    }
242}