sp1_core_machine/cpu/air/
mod.rs

1pub mod register;
2
3use core::borrow::Borrow;
4use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, BaseAir};
5use p3_field::AbstractField;
6use p3_matrix::Matrix;
7use sp1_core_executor::{ByteOpcode, DEFAULT_PC_INC};
8use sp1_stark::{
9    air::{BaseAirBuilder, PublicValues, SP1AirBuilder, SP1_PROOF_NUM_PV_ELTS},
10    Word,
11};
12
13use crate::{
14    air::{MemoryAirBuilder, SP1CoreAirBuilder},
15    cpu::{
16        columns::{CpuCols, NUM_CPU_COLS},
17        CpuChip,
18    },
19};
20
21impl<AB> Air<AB> for CpuChip
22where
23    AB: SP1CoreAirBuilder + AirBuilderWithPublicValues,
24    AB::Var: Sized,
25{
26    #[inline(never)]
27    fn eval(&self, builder: &mut AB) {
28        let main = builder.main();
29        let (local, next) = (main.row_slice(0), main.row_slice(1));
30        let local: &CpuCols<AB::Var> = (*local).borrow();
31        let next: &CpuCols<AB::Var> = (*next).borrow();
32
33        let public_values_slice: [AB::PublicVar; SP1_PROOF_NUM_PV_ELTS] =
34            core::array::from_fn(|i| builder.public_values()[i]);
35        let public_values: &PublicValues<Word<AB::PublicVar>, AB::PublicVar> =
36            public_values_slice.as_slice().borrow();
37
38        // We represent the `clk` with a 16 bit limb and a 8 bit limb.
39        // The range checks for these limbs are done in `eval_shard_clk`.
40        let clk =
41            AB::Expr::from_canonical_u32(1u32 << 16) * local.clk_8bit_limb + local.clk_16bit_limb;
42
43        // Program constraints.
44        // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
45        // The `pc` and `instruction` is taken from the `ProgramChip`, where these are preprocessed.
46        builder.send_program(local.pc, local.instruction, local.is_real);
47
48        // Register constraints.
49        self.eval_registers::<AB>(builder, local, clk.clone());
50
51        // Assert the shard and clk to send.  Only the memory and syscall instructions need the
52        // actual shard and clk values for memory access evals.
53        // SAFETY: The usage of `builder.if_else` requires `is_memory + is_syscall` to be boolean.
54        // The correctness of `is_memory` and `is_syscall` will be checked in the opcode specific
55        // chips. In these correct cases, `is_memory + is_syscall` will be always boolean.
56        let expected_shard_to_send =
57            builder.if_else(local.is_memory + local.is_syscall, local.shard, AB::Expr::zero());
58        let expected_clk_to_send =
59            builder.if_else(local.is_memory + local.is_syscall, clk.clone(), AB::Expr::zero());
60        builder.when(local.is_real).assert_eq(local.shard_to_send, expected_shard_to_send);
61        builder.when(local.is_real).assert_eq(local.clk_to_send, expected_clk_to_send);
62
63        // Send the instruction.
64        // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
65        // The `shard`, `clk`, `pc` are constrained throughout the CpuChip.
66        // The `local.instruction.opcode`, `local.instruction.op_a_0` are from the ProgramChip.
67        // The `local.op_b_val()` and `local.op_c_val()` are constrained in `eval_registers` in the
68        // CpuChip. Therefore, opcode specific chips that will receive this instruction need
69        // to the following.
70        // - For an instruction with a valid opcode, exactly one opcode specific chip can receive
71        //   the instruction.
72        // - The `next_pc`, `num_extra_cycles`, `op_a_val`, `op_a_immutable`, `is_memory`,
73        //   `is_syscall`, `is_halt` are constrained correctly.
74        // Note that in this case, `shard_to_send` and `clk_to_send` will be correctly constrained
75        // as well. If `instruction.op_a_0 == 1`, then `eval_registers` enforces `op_a_val()
76        // == 0`. Therefore, in this case, `op_a_val` doesn't need to be constrained in the
77        // opcode specific chips.
78        builder.send_instruction(
79            local.shard_to_send,
80            local.clk_to_send,
81            local.pc,
82            local.next_pc,
83            local.num_extra_cycles,
84            local.instruction.opcode,
85            local.op_a_val(),
86            local.op_b_val(),
87            local.op_c_val(),
88            local.instruction.op_a_0,
89            local.op_a_immutable,
90            local.is_memory,
91            local.is_syscall,
92            local.is_halt,
93            local.is_real,
94        );
95
96        // Check that the shard and clk is updated correctly.
97        self.eval_shard_clk(builder, local, next, public_values, clk.clone());
98
99        // Check that the pc is updated correctly.
100        self.eval_pc(builder, local, next, public_values);
101
102        // Check that the is_real flag is correct.
103        self.eval_is_real(builder, local, next);
104
105        // Check that when `is_real=0` that all flags that send interactions are zero.
106        let not_real = AB::Expr::one() - local.is_real;
107        builder.when(not_real.clone()).assert_zero(AB::Expr::one() - local.instruction.imm_b);
108        builder.when(not_real.clone()).assert_zero(AB::Expr::one() - local.instruction.imm_c);
109        builder.when(not_real.clone()).assert_zero(AB::Expr::one() - local.is_syscall);
110    }
111}
112
113impl CpuChip {
114    /// Constraints related to the shard and clk.
115    ///
116    /// This method ensures that all of the shard values are the same and that the clk starts at 0
117    /// and is transitioned appropriately.  It will also check that shard values are within 16 bits
118    /// and clk values are within 24 bits.  Those range checks are needed for the memory access
119    /// timestamp check, which assumes those values are within 2^24.  See
120    /// [`MemoryAirBuilder::verify_mem_access_ts`].
121    pub(crate) fn eval_shard_clk<AB: SP1AirBuilder>(
122        &self,
123        builder: &mut AB,
124        local: &CpuCols<AB::Var>,
125        next: &CpuCols<AB::Var>,
126        public_values: &PublicValues<Word<AB::PublicVar>, AB::PublicVar>,
127        clk: AB::Expr,
128    ) {
129        // Verify the public value's shard.
130        builder.when(local.is_real).assert_eq(public_values.execution_shard, local.shard);
131
132        // Verify that all shard values are the same.
133        builder.when_transition().when(next.is_real).assert_eq(local.shard, next.shard);
134
135        // Verify that the shard value is within 16 bits.
136        // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
137        builder.send_byte(
138            AB::Expr::from_canonical_u8(ByteOpcode::U16Range as u8),
139            local.shard,
140            AB::Expr::zero(),
141            AB::Expr::zero(),
142            local.is_real,
143        );
144
145        // Verify that the first row has a clk value of 0.
146        builder.when_first_row().assert_zero(clk.clone());
147
148        // We already assert that `local.clk < 2^24`. `num_extra_cycles` is an entry of a word and
149        // therefore less than `2^8`, this means that the sum cannot overflow in a 31 bit field.
150        // The default clk increment is also `4`, equal to `DEFAULT_PC_INC`.
151        let expected_next_clk =
152            clk.clone() + AB::Expr::from_canonical_u32(DEFAULT_PC_INC) + local.num_extra_cycles;
153
154        let next_clk =
155            AB::Expr::from_canonical_u32(1u32 << 16) * next.clk_8bit_limb + next.clk_16bit_limb;
156        builder.when_transition().when(next.is_real).assert_eq(expected_next_clk, next_clk);
157
158        // Range check that the clk is within 24 bits using it's limb values.
159        // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
160        builder.eval_range_check_24bits(
161            clk,
162            local.clk_16bit_limb,
163            local.clk_8bit_limb,
164            local.is_real,
165        );
166    }
167
168    /// Constraints related to the pc for non jump, branch, and halt instructions.
169    ///
170    /// The function will verify that the pc increments by 4 for all instructions except branch,
171    /// jump and halt instructions. Also, it ensures that the pc is carried down to the last row
172    /// for non-real rows.
173    pub(crate) fn eval_pc<AB: SP1AirBuilder>(
174        &self,
175        builder: &mut AB,
176        local: &CpuCols<AB::Var>,
177        next: &CpuCols<AB::Var>,
178        public_values: &PublicValues<Word<AB::PublicVar>, AB::PublicVar>,
179    ) {
180        // Verify the public value's start pc.
181        builder.when_first_row().assert_eq(public_values.start_pc, local.pc);
182
183        // Verify that the next row's `pc` is the current row's `next_pc`.
184        builder.when_transition().when(next.is_real).assert_eq(local.next_pc, next.pc);
185
186        // Verify the public value's next pc.  We need to handle two cases:
187        // 1. The last real row is a transition row.
188        // 2. The last real row is the last row.
189
190        // If the last real row is a transition row, verify the public value's next pc.
191        builder
192            .when_transition()
193            .when(local.is_real - next.is_real)
194            .assert_eq(public_values.next_pc, local.next_pc);
195
196        // If the last real row is the last row, verify the public value's next pc.
197        builder.when_last_row().when(local.is_real).assert_eq(public_values.next_pc, local.next_pc);
198    }
199
200    /// Constraints related to the is_real column.
201    ///
202    /// This method checks that the is_real column is a boolean.  It also checks that the first row
203    /// is 1 and once its 0, it never changes value.
204    pub(crate) fn eval_is_real<AB: SP1AirBuilder>(
205        &self,
206        builder: &mut AB,
207        local: &CpuCols<AB::Var>,
208        next: &CpuCols<AB::Var>,
209    ) {
210        // Check the is_real flag.  It should be 1 for the first row.  Once its 0, it should never
211        // change value.
212        builder.assert_bool(local.is_real);
213        builder.when_first_row().assert_one(local.is_real);
214        builder.when_transition().when_not(local.is_real).assert_zero(next.is_real);
215
216        // If we're halting and it's a transition, then the next.is_real should be 0.
217        builder.when_transition().when(local.is_halt).assert_zero(next.is_real);
218    }
219}
220
221impl<F> BaseAir<F> for CpuChip {
222    fn width(&self) -> usize {
223        NUM_CPU_COLS
224    }
225}