openvm_rv32im_circuit/
extension.rs

1use derive_more::derive::From;
2use openvm_circuit::{
3    arch::{
4        SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError,
5    },
6    system::phantom::PhantomChip,
7};
8use openvm_circuit_derive::{AnyEnum, InstructionExecutor, VmConfig};
9use openvm_circuit_primitives::{
10    bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
11    range_tuple::{RangeTupleCheckerBus, SharedRangeTupleCheckerChip},
12};
13use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter};
14use openvm_instructions::{program::DEFAULT_PC_STEP, LocalOpcode, PhantomDiscriminant};
15use openvm_rv32im_transpiler::{
16    BaseAluOpcode, BranchEqualOpcode, BranchLessThanOpcode, DivRemOpcode, LessThanOpcode,
17    MulHOpcode, MulOpcode, Rv32AuipcOpcode, Rv32HintStoreOpcode, Rv32JalLuiOpcode, Rv32JalrOpcode,
18    Rv32LoadStoreOpcode, Rv32Phantom, ShiftOpcode,
19};
20use openvm_stark_backend::p3_field::PrimeField32;
21use serde::{Deserialize, Serialize};
22use strum::IntoEnumIterator;
23
24use crate::{adapters::*, *};
25
26/// Config for a VM with base extension and IO extension
27#[derive(Clone, Debug, VmConfig, derive_new::new, Serialize, Deserialize)]
28pub struct Rv32IConfig {
29    #[system]
30    pub system: SystemConfig,
31    #[extension]
32    pub base: Rv32I,
33    #[extension]
34    pub io: Rv32Io,
35}
36
37/// Config for a VM with base extension, IO extension, and multiplication extension
38#[derive(Clone, Debug, Default, VmConfig, derive_new::new, Serialize, Deserialize)]
39pub struct Rv32ImConfig {
40    #[config]
41    pub rv32i: Rv32IConfig,
42    #[extension]
43    pub mul: Rv32M,
44}
45
46impl Default for Rv32IConfig {
47    fn default() -> Self {
48        let system = SystemConfig::default().with_continuations();
49        Self {
50            system,
51            base: Default::default(),
52            io: Default::default(),
53        }
54    }
55}
56
57impl Rv32IConfig {
58    pub fn with_public_values(public_values: usize) -> Self {
59        let system = SystemConfig::default()
60            .with_continuations()
61            .with_public_values(public_values);
62        Self {
63            system,
64            base: Default::default(),
65            io: Default::default(),
66        }
67    }
68
69    pub fn with_public_values_and_segment_len(public_values: usize, segment_len: usize) -> Self {
70        let system = SystemConfig::default()
71            .with_continuations()
72            .with_public_values(public_values)
73            .with_max_segment_len(segment_len);
74        Self {
75            system,
76            base: Default::default(),
77            io: Default::default(),
78        }
79    }
80}
81
82impl Rv32ImConfig {
83    pub fn with_public_values(public_values: usize) -> Self {
84        Self {
85            rv32i: Rv32IConfig::with_public_values(public_values),
86            mul: Default::default(),
87        }
88    }
89
90    pub fn with_public_values_and_segment_len(public_values: usize, segment_len: usize) -> Self {
91        Self {
92            rv32i: Rv32IConfig::with_public_values_and_segment_len(public_values, segment_len),
93            mul: Default::default(),
94        }
95    }
96}
97
98// ============ Extension Implementations ============
99
100/// RISC-V 32-bit Base (RV32I) Extension
101#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
102pub struct Rv32I;
103
104/// RISC-V Extension for handling IO (not to be confused with I base extension)
105#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
106pub struct Rv32Io;
107
108/// RISC-V 32-bit Multiplication Extension (RV32M) Extension
109#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
110pub struct Rv32M {
111    #[serde(default = "default_range_tuple_checker_sizes")]
112    pub range_tuple_checker_sizes: [u32; 2],
113}
114
115impl Default for Rv32M {
116    fn default() -> Self {
117        Self {
118            range_tuple_checker_sizes: default_range_tuple_checker_sizes(),
119        }
120    }
121}
122
123fn default_range_tuple_checker_sizes() -> [u32; 2] {
124    [1 << 8, 8 * (1 << 8)]
125}
126
127// ============ Executor and Periphery Enums for Extension ============
128
129/// RISC-V 32-bit Base (RV32I) Instruction Executors
130#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
131pub enum Rv32IExecutor<F: PrimeField32> {
132    // Rv32 (for standard 32-bit integers):
133    BaseAlu(Rv32BaseAluChip<F>),
134    LessThan(Rv32LessThanChip<F>),
135    Shift(Rv32ShiftChip<F>),
136    LoadStore(Rv32LoadStoreChip<F>),
137    LoadSignExtend(Rv32LoadSignExtendChip<F>),
138    BranchEqual(Rv32BranchEqualChip<F>),
139    BranchLessThan(Rv32BranchLessThanChip<F>),
140    JalLui(Rv32JalLuiChip<F>),
141    Jalr(Rv32JalrChip<F>),
142    Auipc(Rv32AuipcChip<F>),
143}
144
145/// RISC-V 32-bit Multiplication Extension (RV32M) Instruction Executors
146#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
147pub enum Rv32MExecutor<F: PrimeField32> {
148    Multiplication(Rv32MultiplicationChip<F>),
149    MultiplicationHigh(Rv32MulHChip<F>),
150    DivRem(Rv32DivRemChip<F>),
151}
152
153/// RISC-V 32-bit Io Instruction Executors
154#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
155pub enum Rv32IoExecutor<F: PrimeField32> {
156    HintStore(Rv32HintStoreChip<F>),
157}
158
159#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
160pub enum Rv32IPeriphery<F: PrimeField32> {
161    BitwiseOperationLookup(SharedBitwiseOperationLookupChip<8>),
162    // We put this only to get the <F> generic to work
163    Phantom(PhantomChip<F>),
164}
165
166#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
167pub enum Rv32MPeriphery<F: PrimeField32> {
168    BitwiseOperationLookup(SharedBitwiseOperationLookupChip<8>),
169    /// Only needed for multiplication extension
170    RangeTupleChecker(SharedRangeTupleCheckerChip<2>),
171    // We put this only to get the <F> generic to work
172    Phantom(PhantomChip<F>),
173}
174
175#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
176pub enum Rv32IoPeriphery<F: PrimeField32> {
177    BitwiseOperationLookup(SharedBitwiseOperationLookupChip<8>),
178    // We put this only to get the <F> generic to work
179    Phantom(PhantomChip<F>),
180}
181
182// ============ VmExtension Implementations ============
183
184impl<F: PrimeField32> VmExtension<F> for Rv32I {
185    type Executor = Rv32IExecutor<F>;
186    type Periphery = Rv32IPeriphery<F>;
187
188    fn build(
189        &self,
190        builder: &mut VmInventoryBuilder<F>,
191    ) -> Result<VmInventory<Rv32IExecutor<F>, Rv32IPeriphery<F>>, VmInventoryError> {
192        let mut inventory = VmInventory::new();
193        let SystemPort {
194            execution_bus,
195            program_bus,
196            memory_bridge,
197        } = builder.system_port();
198
199        let range_checker = builder.system_base().range_checker_chip.clone();
200        let offline_memory = builder.system_base().offline_memory();
201        let pointer_max_bits = builder.system_config().memory_config.pointer_max_bits;
202
203        let bitwise_lu_chip = if let Some(&chip) = builder
204            .find_chip::<SharedBitwiseOperationLookupChip<8>>()
205            .first()
206        {
207            chip.clone()
208        } else {
209            let bitwise_lu_bus = BitwiseOperationLookupBus::new(builder.new_bus_idx());
210            let chip = SharedBitwiseOperationLookupChip::new(bitwise_lu_bus);
211            inventory.add_periphery_chip(chip.clone());
212            chip
213        };
214
215        let base_alu_chip = Rv32BaseAluChip::new(
216            Rv32BaseAluAdapterChip::new(
217                execution_bus,
218                program_bus,
219                memory_bridge,
220                bitwise_lu_chip.clone(),
221            ),
222            BaseAluCoreChip::new(bitwise_lu_chip.clone(), BaseAluOpcode::CLASS_OFFSET),
223            offline_memory.clone(),
224        );
225        inventory.add_executor(
226            base_alu_chip,
227            BaseAluOpcode::iter().map(|x| x.global_opcode()),
228        )?;
229
230        let lt_chip = Rv32LessThanChip::new(
231            Rv32BaseAluAdapterChip::new(
232                execution_bus,
233                program_bus,
234                memory_bridge,
235                bitwise_lu_chip.clone(),
236            ),
237            LessThanCoreChip::new(bitwise_lu_chip.clone(), LessThanOpcode::CLASS_OFFSET),
238            offline_memory.clone(),
239        );
240        inventory.add_executor(lt_chip, LessThanOpcode::iter().map(|x| x.global_opcode()))?;
241
242        let shift_chip = Rv32ShiftChip::new(
243            Rv32BaseAluAdapterChip::new(
244                execution_bus,
245                program_bus,
246                memory_bridge,
247                bitwise_lu_chip.clone(),
248            ),
249            ShiftCoreChip::new(
250                bitwise_lu_chip.clone(),
251                range_checker.clone(),
252                ShiftOpcode::CLASS_OFFSET,
253            ),
254            offline_memory.clone(),
255        );
256        inventory.add_executor(shift_chip, ShiftOpcode::iter().map(|x| x.global_opcode()))?;
257
258        let load_store_chip = Rv32LoadStoreChip::new(
259            Rv32LoadStoreAdapterChip::new(
260                execution_bus,
261                program_bus,
262                memory_bridge,
263                pointer_max_bits,
264                range_checker.clone(),
265            ),
266            LoadStoreCoreChip::new(Rv32LoadStoreOpcode::CLASS_OFFSET),
267            offline_memory.clone(),
268        );
269        inventory.add_executor(
270            load_store_chip,
271            Rv32LoadStoreOpcode::iter()
272                .take(Rv32LoadStoreOpcode::STOREB as usize + 1)
273                .map(|x| x.global_opcode()),
274        )?;
275
276        let load_sign_extend_chip = Rv32LoadSignExtendChip::new(
277            Rv32LoadStoreAdapterChip::new(
278                execution_bus,
279                program_bus,
280                memory_bridge,
281                pointer_max_bits,
282                range_checker.clone(),
283            ),
284            LoadSignExtendCoreChip::new(range_checker.clone()),
285            offline_memory.clone(),
286        );
287        inventory.add_executor(
288            load_sign_extend_chip,
289            [Rv32LoadStoreOpcode::LOADB, Rv32LoadStoreOpcode::LOADH].map(|x| x.global_opcode()),
290        )?;
291
292        let beq_chip = Rv32BranchEqualChip::new(
293            Rv32BranchAdapterChip::new(execution_bus, program_bus, memory_bridge),
294            BranchEqualCoreChip::new(BranchEqualOpcode::CLASS_OFFSET, DEFAULT_PC_STEP),
295            offline_memory.clone(),
296        );
297        inventory.add_executor(
298            beq_chip,
299            BranchEqualOpcode::iter().map(|x| x.global_opcode()),
300        )?;
301
302        let blt_chip = Rv32BranchLessThanChip::new(
303            Rv32BranchAdapterChip::new(execution_bus, program_bus, memory_bridge),
304            BranchLessThanCoreChip::new(
305                bitwise_lu_chip.clone(),
306                BranchLessThanOpcode::CLASS_OFFSET,
307            ),
308            offline_memory.clone(),
309        );
310        inventory.add_executor(
311            blt_chip,
312            BranchLessThanOpcode::iter().map(|x| x.global_opcode()),
313        )?;
314
315        let jal_lui_chip = Rv32JalLuiChip::new(
316            Rv32CondRdWriteAdapterChip::new(execution_bus, program_bus, memory_bridge),
317            Rv32JalLuiCoreChip::new(bitwise_lu_chip.clone()),
318            offline_memory.clone(),
319        );
320        inventory.add_executor(
321            jal_lui_chip,
322            Rv32JalLuiOpcode::iter().map(|x| x.global_opcode()),
323        )?;
324
325        let jalr_chip = Rv32JalrChip::new(
326            Rv32JalrAdapterChip::new(execution_bus, program_bus, memory_bridge),
327            Rv32JalrCoreChip::new(bitwise_lu_chip.clone(), range_checker.clone()),
328            offline_memory.clone(),
329        );
330        inventory.add_executor(jalr_chip, Rv32JalrOpcode::iter().map(|x| x.global_opcode()))?;
331
332        let auipc_chip = Rv32AuipcChip::new(
333            Rv32RdWriteAdapterChip::new(execution_bus, program_bus, memory_bridge),
334            Rv32AuipcCoreChip::new(bitwise_lu_chip.clone()),
335            offline_memory.clone(),
336        );
337        inventory.add_executor(
338            auipc_chip,
339            Rv32AuipcOpcode::iter().map(|x| x.global_opcode()),
340        )?;
341
342        // There is no downside to adding phantom sub-executors, so we do it in the base extension.
343        builder.add_phantom_sub_executor(
344            phantom::Rv32HintInputSubEx,
345            PhantomDiscriminant(Rv32Phantom::HintInput as u16),
346        )?;
347        builder.add_phantom_sub_executor(
348            phantom::Rv32HintRandomSubEx::new(),
349            PhantomDiscriminant(Rv32Phantom::HintRandom as u16),
350        )?;
351        builder.add_phantom_sub_executor(
352            phantom::Rv32PrintStrSubEx,
353            PhantomDiscriminant(Rv32Phantom::PrintStr as u16),
354        )?;
355
356        Ok(inventory)
357    }
358}
359
360impl<F: PrimeField32> VmExtension<F> for Rv32M {
361    type Executor = Rv32MExecutor<F>;
362    type Periphery = Rv32MPeriphery<F>;
363
364    fn build(
365        &self,
366        builder: &mut VmInventoryBuilder<F>,
367    ) -> Result<VmInventory<Rv32MExecutor<F>, Rv32MPeriphery<F>>, VmInventoryError> {
368        let mut inventory = VmInventory::new();
369        let SystemPort {
370            execution_bus,
371            program_bus,
372            memory_bridge,
373        } = builder.system_port();
374        let offline_memory = builder.system_base().offline_memory();
375
376        let bitwise_lu_chip = if let Some(&chip) = builder
377            .find_chip::<SharedBitwiseOperationLookupChip<8>>()
378            .first()
379        {
380            chip.clone()
381        } else {
382            let bitwise_lu_bus = BitwiseOperationLookupBus::new(builder.new_bus_idx());
383            let chip = SharedBitwiseOperationLookupChip::new(bitwise_lu_bus);
384            inventory.add_periphery_chip(chip.clone());
385            chip
386        };
387
388        let range_tuple_checker = if let Some(chip) = builder
389            .find_chip::<SharedRangeTupleCheckerChip<2>>()
390            .into_iter()
391            .find(|c| {
392                c.bus().sizes[0] >= self.range_tuple_checker_sizes[0]
393                    && c.bus().sizes[1] >= self.range_tuple_checker_sizes[1]
394            }) {
395            chip.clone()
396        } else {
397            let range_tuple_bus =
398                RangeTupleCheckerBus::new(builder.new_bus_idx(), self.range_tuple_checker_sizes);
399            let chip = SharedRangeTupleCheckerChip::new(range_tuple_bus);
400            inventory.add_periphery_chip(chip.clone());
401            chip
402        };
403
404        let mul_chip = Rv32MultiplicationChip::new(
405            Rv32MultAdapterChip::new(execution_bus, program_bus, memory_bridge),
406            MultiplicationCoreChip::new(range_tuple_checker.clone(), MulOpcode::CLASS_OFFSET),
407            offline_memory.clone(),
408        );
409        inventory.add_executor(mul_chip, MulOpcode::iter().map(|x| x.global_opcode()))?;
410
411        let mul_h_chip = Rv32MulHChip::new(
412            Rv32MultAdapterChip::new(execution_bus, program_bus, memory_bridge),
413            MulHCoreChip::new(bitwise_lu_chip.clone(), range_tuple_checker.clone()),
414            offline_memory.clone(),
415        );
416        inventory.add_executor(mul_h_chip, MulHOpcode::iter().map(|x| x.global_opcode()))?;
417
418        let div_rem_chip = Rv32DivRemChip::new(
419            Rv32MultAdapterChip::new(execution_bus, program_bus, memory_bridge),
420            DivRemCoreChip::new(
421                bitwise_lu_chip.clone(),
422                range_tuple_checker.clone(),
423                DivRemOpcode::CLASS_OFFSET,
424            ),
425            offline_memory.clone(),
426        );
427        inventory.add_executor(
428            div_rem_chip,
429            DivRemOpcode::iter().map(|x| x.global_opcode()),
430        )?;
431
432        Ok(inventory)
433    }
434}
435
436impl<F: PrimeField32> VmExtension<F> for Rv32Io {
437    type Executor = Rv32IoExecutor<F>;
438    type Periphery = Rv32IoPeriphery<F>;
439
440    fn build(
441        &self,
442        builder: &mut VmInventoryBuilder<F>,
443    ) -> Result<VmInventory<Self::Executor, Self::Periphery>, VmInventoryError> {
444        let mut inventory = VmInventory::new();
445        let SystemPort {
446            execution_bus,
447            program_bus,
448            memory_bridge,
449        } = builder.system_port();
450        let offline_memory = builder.system_base().offline_memory();
451
452        let bitwise_lu_chip = if let Some(&chip) = builder
453            .find_chip::<SharedBitwiseOperationLookupChip<8>>()
454            .first()
455        {
456            chip.clone()
457        } else {
458            let bitwise_lu_bus = BitwiseOperationLookupBus::new(builder.new_bus_idx());
459            let chip = SharedBitwiseOperationLookupChip::new(bitwise_lu_bus);
460            inventory.add_periphery_chip(chip.clone());
461            chip
462        };
463
464        let mut hintstore_chip = Rv32HintStoreChip::new(
465            execution_bus,
466            program_bus,
467            bitwise_lu_chip.clone(),
468            memory_bridge,
469            offline_memory.clone(),
470            builder.system_config().memory_config.pointer_max_bits,
471            Rv32HintStoreOpcode::CLASS_OFFSET,
472        );
473        hintstore_chip.set_streams(builder.streams().clone());
474
475        inventory.add_executor(
476            hintstore_chip,
477            Rv32HintStoreOpcode::iter().map(|x| x.global_opcode()),
478        )?;
479
480        Ok(inventory)
481    }
482}
483
484/// Phantom sub-executors
485mod phantom {
486    use eyre::bail;
487    use openvm_circuit::{
488        arch::{PhantomSubExecutor, Streams},
489        system::memory::MemoryController,
490    };
491    use openvm_instructions::PhantomDiscriminant;
492    use openvm_stark_backend::p3_field::{Field, PrimeField32};
493    use rand::{rngs::OsRng, Rng};
494
495    use crate::adapters::unsafe_read_rv32_register;
496
497    pub struct Rv32HintInputSubEx;
498    pub struct Rv32HintRandomSubEx {
499        rng: OsRng,
500    }
501    impl Rv32HintRandomSubEx {
502        pub fn new() -> Self {
503            Self { rng: OsRng }
504        }
505    }
506    pub struct Rv32PrintStrSubEx;
507
508    impl<F: Field> PhantomSubExecutor<F> for Rv32HintInputSubEx {
509        fn phantom_execute(
510            &mut self,
511            _: &MemoryController<F>,
512            streams: &mut Streams<F>,
513            _: PhantomDiscriminant,
514            _: F,
515            _: F,
516            _: u16,
517        ) -> eyre::Result<()> {
518            let mut hint = match streams.input_stream.pop_front() {
519                Some(hint) => hint,
520                None => {
521                    bail!("EndOfInputStream");
522                }
523            };
524            streams.hint_stream.clear();
525            streams.hint_stream.extend(
526                (hint.len() as u32)
527                    .to_le_bytes()
528                    .iter()
529                    .map(|b| F::from_canonical_u8(*b)),
530            );
531            // Extend by 0 for 4 byte alignment
532            let capacity = hint.len().div_ceil(4) * 4;
533            hint.resize(capacity, F::ZERO);
534            streams.hint_stream.extend(hint);
535            Ok(())
536        }
537    }
538
539    impl<F: PrimeField32> PhantomSubExecutor<F> for Rv32HintRandomSubEx {
540        fn phantom_execute(
541            &mut self,
542            memory: &MemoryController<F>,
543            streams: &mut Streams<F>,
544            _: PhantomDiscriminant,
545            a: F,
546            _: F,
547            _: u16,
548        ) -> eyre::Result<()> {
549            let len = unsafe_read_rv32_register(memory, a) as usize;
550            streams.hint_stream.clear();
551            streams.hint_stream.extend(
552                std::iter::repeat_with(|| F::from_canonical_u8(self.rng.gen::<u8>())).take(len * 4),
553            );
554            Ok(())
555        }
556    }
557
558    impl<F: PrimeField32> PhantomSubExecutor<F> for Rv32PrintStrSubEx {
559        fn phantom_execute(
560            &mut self,
561            memory: &MemoryController<F>,
562            _: &mut Streams<F>,
563            _: PhantomDiscriminant,
564            a: F,
565            b: F,
566            _: u16,
567        ) -> eyre::Result<()> {
568            let rd = unsafe_read_rv32_register(memory, a);
569            let rs1 = unsafe_read_rv32_register(memory, b);
570            let bytes = (0..rs1)
571                .map(|i| -> eyre::Result<u8> {
572                    let val = memory.unsafe_read_cell(F::TWO, F::from_canonical_u32(rd + i));
573                    let byte: u8 = val.as_canonical_u32().try_into()?;
574                    Ok(byte)
575                })
576                .collect::<eyre::Result<Vec<u8>>>()?;
577            let peeked_str = String::from_utf8(bytes)?;
578            print!("{peeked_str}");
579            Ok(())
580        }
581    }
582}