├── README.md ├── .gitignore ├── rom └── zkhack │ ├── zkhack.ch8 │ └── zkhack.8o ├── chip0-tui ├── src │ ├── drivers │ │ ├── mod.rs │ │ ├── audio.rs │ │ ├── display.rs │ │ └── input.rs │ ├── args.rs │ ├── terminal.rs │ └── main.rs └── Cargo.toml ├── chip0-core ├── src │ ├── airs │ │ ├── counter │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ └── air.rs │ │ ├── is_equal │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ └── air.rs │ │ ├── loop_counter │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ └── air.rs │ │ ├── selector │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ └── air.rs │ │ ├── is_equal_constant │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ └── air.rs │ │ ├── modulo_counter │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ └── air.rs │ │ └── mod.rs │ ├── lib.rs │ ├── chips │ │ ├── hash │ │ │ ├── columns.rs │ │ │ ├── interaction.rs │ │ │ ├── mod.rs │ │ │ └── air.rs │ │ ├── range │ │ │ ├── columns.rs │ │ │ ├── mod.rs │ │ │ ├── air.rs │ │ │ └── interaction.rs │ │ ├── clear │ │ │ ├── columns.rs │ │ │ ├── mod.rs │ │ │ ├── air.rs │ │ │ └── interaction.rs │ │ ├── keypad │ │ │ ├── columns.rs │ │ │ ├── air.rs │ │ │ ├── mod.rs │ │ │ └── interaction.rs │ │ ├── memory_start │ │ │ ├── columns.rs │ │ │ ├── mod.rs │ │ │ ├── interaction.rs │ │ │ └── air.rs │ │ ├── memory │ │ │ ├── columns.rs │ │ │ ├── mod.rs │ │ │ ├── air.rs │ │ │ └── interaction.rs │ │ ├── frame_buffer │ │ │ ├── columns.rs │ │ │ ├── mod.rs │ │ │ ├── air.rs │ │ │ └── interaction.rs │ │ ├── draw │ │ │ ├── columns.rs │ │ │ ├── mod.rs │ │ │ ├── air.rs │ │ │ └── interaction.rs │ │ ├── mod.rs │ │ └── cpu │ │ │ ├── mod.rs │ │ │ ├── columns.rs │ │ │ ├── interaction.rs │ │ │ └── air.rs │ ├── bus.rs │ ├── prover.rs │ ├── config.rs │ ├── machine.rs │ ├── cpu.rs │ └── trace.rs └── Cargo.toml ├── chip8-core ├── src │ ├── drivers │ │ ├── mod.rs │ │ ├── audio.rs │ │ ├── display.rs │ │ └── input.rs │ ├── lib.rs │ ├── cpu │ │ ├── simple.rs │ │ └── mod.rs │ ├── error.rs │ ├── rwlock.rs │ ├── instruction.rs │ ├── util.rs │ ├── input.rs │ ├── state │ │ ├── mod.rs │ │ └── simple.rs │ ├── keypad.rs │ ├── constants.rs │ └── chip8.rs └── Cargo.toml ├── .gitmodules └── Cargo.toml /README.md: -------------------------------------------------------------------------------- 1 | # chip0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | *.csv 4 | *.xlsx 5 | -------------------------------------------------------------------------------- /rom/zkhack/zkhack.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuklaayush/chip0/HEAD/rom/zkhack/zkhack.ch8 -------------------------------------------------------------------------------- /chip0-tui/src/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod audio; 2 | pub(crate) mod display; 3 | pub(crate) mod input; 4 | -------------------------------------------------------------------------------- /chip0-core/src/airs/counter/mod.rs: -------------------------------------------------------------------------------- 1 | mod air; 2 | mod columns; 3 | 4 | pub use air::*; 5 | pub use columns::*; 6 | -------------------------------------------------------------------------------- /chip0-core/src/airs/is_equal/mod.rs: -------------------------------------------------------------------------------- 1 | mod air; 2 | mod columns; 3 | 4 | pub use air::*; 5 | pub use columns::*; 6 | -------------------------------------------------------------------------------- /chip0-core/src/airs/loop_counter/mod.rs: -------------------------------------------------------------------------------- 1 | mod air; 2 | mod columns; 3 | 4 | pub use air::*; 5 | pub use columns::*; 6 | -------------------------------------------------------------------------------- /chip0-core/src/airs/selector/mod.rs: -------------------------------------------------------------------------------- 1 | mod air; 2 | mod columns; 3 | 4 | pub use air::*; 5 | pub use columns::*; 6 | -------------------------------------------------------------------------------- /chip0-core/src/airs/is_equal_constant/mod.rs: -------------------------------------------------------------------------------- 1 | mod air; 2 | mod columns; 3 | 4 | pub use air::*; 5 | pub use columns::*; 6 | -------------------------------------------------------------------------------- /chip0-core/src/airs/modulo_counter/mod.rs: -------------------------------------------------------------------------------- 1 | mod air; 2 | mod columns; 3 | 4 | pub use air::*; 5 | pub use columns::*; 6 | -------------------------------------------------------------------------------- /chip8-core/src/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | mod audio; 2 | mod display; 3 | mod input; 4 | 5 | pub use audio::*; 6 | pub use display::*; 7 | pub use input::*; 8 | -------------------------------------------------------------------------------- /chip0-core/src/airs/counter/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar)] 5 | pub struct CounterCols { 6 | pub counter: T, 7 | } 8 | -------------------------------------------------------------------------------- /chip0-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod airs; 2 | pub mod bus; 3 | pub mod chips; 4 | pub mod config; 5 | pub mod cpu; 6 | pub mod machine; 7 | pub mod prover; 8 | pub mod trace; 9 | -------------------------------------------------------------------------------- /chip0-core/src/airs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod counter; 2 | pub mod is_equal; 3 | pub mod is_equal_constant; 4 | pub mod loop_counter; 5 | pub mod modulo_counter; 6 | pub mod selector; 7 | -------------------------------------------------------------------------------- /chip0-core/src/chips/hash/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct HashCols { 6 | pub is_real: T, 7 | } 8 | -------------------------------------------------------------------------------- /chip0-core/src/airs/selector/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar)] 5 | pub struct SelectorCols { 6 | pub selectors: [T; N], 7 | } 8 | -------------------------------------------------------------------------------- /chip0-core/src/airs/loop_counter/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar)] 5 | pub struct LoopCounterCols { 6 | pub is_start: T, 7 | pub counter: T, 8 | } 9 | -------------------------------------------------------------------------------- /chip0-core/src/chips/range/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct RangeCols { 6 | pub value: T, 7 | pub mult: T, 8 | } 9 | -------------------------------------------------------------------------------- /chip0-core/src/airs/is_equal/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar)] 5 | pub struct IsEqualCols { 6 | pub x: T, 7 | pub y: T, 8 | 9 | pub diff_inv: T, 10 | pub is_equal: T, 11 | } 12 | -------------------------------------------------------------------------------- /chip0-core/src/airs/is_equal_constant/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar)] 5 | pub struct IsEqualConstantCols { 6 | pub x: T, 7 | 8 | pub diff_inv: T, 9 | pub is_equal: T, 10 | } 11 | -------------------------------------------------------------------------------- /chip0-core/src/airs/modulo_counter/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct ModuloCounterCols { 6 | pub counter: T, 7 | pub diff_inv: T, 8 | pub is_max: T, 9 | } 10 | -------------------------------------------------------------------------------- /chip8-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod chip8; 2 | pub mod constants; 3 | pub mod cpu; 4 | pub mod drivers; 5 | pub mod error; 6 | pub mod input; 7 | pub mod instruction; 8 | pub mod keypad; 9 | pub mod rwlock; 10 | pub mod state; 11 | pub mod util; 12 | 13 | pub use chip8::*; 14 | -------------------------------------------------------------------------------- /chip0-core/src/chips/clear/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | // TODO: Use preprocessed? 4 | #[repr(C)] 5 | #[derive(Columnar, Default, Clone)] 6 | pub struct ClearCols { 7 | pub is_real: T, 8 | pub clk: T, 9 | pub is_start: T, 10 | pub addr: T, 11 | } 12 | -------------------------------------------------------------------------------- /chip0-core/src/bus.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Bus; 2 | 3 | #[derive(Bus)] 4 | pub enum Chip0MachineBus { 5 | ClearBus = 0, 6 | DrawBus = 1, 7 | KeypadBus = 2, 8 | MemoryBus = 3, 9 | FrameBufferBus = 4, 10 | RangeBus = 5, 11 | MemoryStartBus = 6, 12 | // HashBus = 7, 13 | } 14 | -------------------------------------------------------------------------------- /chip0-core/src/chips/keypad/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct KeypadCols { 6 | pub is_real: T, 7 | pub clk: T, 8 | pub index: T, 9 | pub value: T, 10 | pub input_hash: T, 11 | pub output_hash: T, 12 | } 13 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory_start/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct MemoryStartPreprocessedCols { 6 | pub addr: T, 7 | pub value: T, 8 | } 9 | 10 | #[repr(C)] 11 | #[derive(Columnar, Default, Clone)] 12 | pub struct MemoryStartCols { 13 | pub mult: T, 14 | } 15 | -------------------------------------------------------------------------------- /chip8-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chip8-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rand = { workspace = true } 8 | thiserror = { version = "1.0.60" } 9 | tokio = { version = "1.37.0", features = ["rt"] } 10 | 11 | p3-field = { workspace = true } 12 | p3-uni-stark = { workspace = true } 13 | p3-matrix = { workspace = true } 14 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct MemoryCols { 6 | pub addr: T, 7 | pub clk: T, 8 | pub value: T, 9 | pub is_read: T, 10 | pub is_write: T, 11 | pub addr_unchanged: T, 12 | pub diff_limb_lo: T, 13 | pub diff_limb_hi: T, 14 | pub is_first_read: T, 15 | pub is_last_write: T, 16 | } 17 | -------------------------------------------------------------------------------- /chip0-core/src/chips/frame_buffer/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | #[repr(C)] 4 | #[derive(Columnar, Default, Clone)] 5 | pub struct FrameBufferCols { 6 | pub addr: T, 7 | pub clk: T, 8 | pub value: T, 9 | pub is_read: T, 10 | pub is_write: T, 11 | pub addr_unchanged: T, 12 | pub diff_limb_lo: T, 13 | pub diff_limb_hi: T, 14 | pub is_first_read: T, 15 | pub is_last_write: T, 16 | } 17 | -------------------------------------------------------------------------------- /chip0-core/src/chips/hash/interaction.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use p3_air::VirtualPairCol; 3 | use p3_field::Field; 4 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 5 | 6 | use super::{columns::HASH_COL_MAP, HashChip}; 7 | 8 | impl BaseInteractionAir for HashChip {} 9 | 10 | impl InteractionAir for HashChip {} 11 | 12 | impl Rap for HashChip {} 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rom/chip8-test-suite"] 2 | path = rom/chip8-test-suite 3 | url = git@github.com:Timendus/chip8-test-suite.git 4 | [submodule "rom/chip8Archive"] 5 | path = rom/chip8Archive 6 | url = git@github.com:JohnEarnest/chip8Archive.git 7 | [submodule "rom/chip8-roms"] 8 | path = rom/chip8-roms 9 | url = git@github.com:kripod/chip8-roms.git 10 | [submodule "rom/chip8-test-rom"] 11 | path = rom/chip8-test-rom 12 | url = git@github.com:corax89/chip8-test-rom.git 13 | -------------------------------------------------------------------------------- /chip8-core/src/drivers/audio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use crate::{error::Chip8Error, rwlock::CheckedRead, util::run_loop}; 4 | 5 | pub trait AudioDriver: Send { 6 | fn frequency(&self) -> u64; 7 | 8 | fn beep(&mut self) -> Result<(), Chip8Error>; 9 | 10 | fn run(&mut self, status: Arc>>, sound_timer: Arc>) { 11 | run_loop(status.clone(), self.frequency(), move |_| { 12 | if *sound_timer.checked_read()? > 0 { 13 | self.beep()?; 14 | } 15 | Ok(()) 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chip0-tui/src/drivers/audio.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::{drivers::AudioDriver, error::Chip8Error}; 2 | use std::io::{stdout, Write}; 3 | 4 | const FREQUENCY: u64 = 60; 5 | 6 | #[derive(Default)] 7 | pub struct TerminalAudio {} 8 | 9 | impl AudioDriver for TerminalAudio { 10 | fn frequency(&self) -> u64 { 11 | FREQUENCY 12 | } 13 | 14 | fn beep(&mut self) -> Result<(), Chip8Error> { 15 | let mut stdout = stdout(); 16 | write!(stdout, "\x07").map_err(|e| Chip8Error::AudioError(e.to_string()))?; 17 | stdout 18 | .flush() 19 | .map_err(|e| Chip8Error::AudioError(e.to_string())) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chip0-core/src/chips/keypad/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_matrix::Matrix; 4 | 5 | use super::columns::KeypadCols; 6 | use super::KeypadChip; 7 | 8 | impl BaseAir for KeypadChip { 9 | fn width(&self) -> usize { 10 | KeypadCols::::num_cols() 11 | } 12 | } 13 | 14 | impl Air for KeypadChip { 15 | fn eval(&self, builder: &mut AB) { 16 | let main = builder.main(); 17 | let local = main.row_slice(0); 18 | let next = main.row_slice(1); 19 | let local: &KeypadCols = (*local).borrow(); 20 | let next: &KeypadCols = (*next).borrow(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chip0-core/src/chips/hash/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::HashCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct HashChip {} 15 | 16 | impl HashChip { 17 | pub fn new() -> Self { 18 | Self {} 19 | } 20 | } 21 | 22 | impl> TraceWriter for HashChip { 23 | #[cfg(feature = "trace-writer")] 24 | fn main_headers(&self) -> Vec { 25 | HashCols::::headers() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chip0-core/src/chips/hash/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::HashCols; 7 | use super::HashChip; 8 | 9 | impl BaseAir for HashChip { 10 | fn width(&self) -> usize { 11 | HashCols::num_cols() 12 | } 13 | } 14 | 15 | impl Air for HashChip { 16 | fn eval(&self, builder: &mut AB) { 17 | let main = builder.main(); 18 | let local = main.row_slice(0); 19 | let next = main.row_slice(1); 20 | let local: &HashCols = (*local).borrow(); 21 | let next: &HashCols = (*next).borrow(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chip0-core/src/chips/range/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::RangeCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct RangeChip { 15 | bus_range: usize, 16 | } 17 | 18 | impl RangeChip { 19 | pub fn new(bus_range: usize) -> Self { 20 | Self { bus_range } 21 | } 22 | } 23 | 24 | #[cfg(feature = "trace-writer")] 25 | impl> TraceWriter for RangeChip { 26 | fn main_headers(&self) -> Vec { 27 | RangeCols::::headers() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chip0-core/src/chips/keypad/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::KeypadCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct KeypadChip { 15 | bus_keypad: usize, 16 | } 17 | 18 | impl KeypadChip { 19 | pub fn new(bus_keypad: usize) -> Self { 20 | Self { bus_keypad } 21 | } 22 | } 23 | 24 | #[cfg(feature = "trace-writer")] 25 | impl> TraceWriter for KeypadChip { 26 | fn main_headers(&self) -> Vec { 27 | KeypadCols::::headers() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chip0-core/src/chips/draw/columns.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::Columnar; 2 | 3 | pub const WORD_BITS: usize = 8; 4 | 5 | #[repr(C)] 6 | #[derive(Columnar, Default, Clone)] 7 | pub struct DrawCols { 8 | pub is_real: T, 9 | // TODO: start and end 10 | pub is_first: T, 11 | pub is_last: T, 12 | 13 | // TODO: Replace with is_xs_0 14 | pub is_first_inner: T, 15 | 16 | pub clk: T, 17 | pub register_x: T, 18 | pub register_y: T, 19 | pub index_register: T, 20 | pub ys: T, 21 | pub y: T, 22 | pub pixels: T, 23 | pub xs: T, 24 | pub x: T, 25 | pub pixel: T, 26 | pub frame_buffer_y_x: T, 27 | pub flipped: T, 28 | pub register_flag: T, 29 | 30 | pub pixels_bits: [T; WORD_BITS], 31 | pub sel_7_minus_xs: [T; WORD_BITS], 32 | } 33 | -------------------------------------------------------------------------------- /chip0-core/src/chips/mod.rs: -------------------------------------------------------------------------------- 1 | use p3_derive::EnumDispatch; 2 | use std::fmt::Debug; 3 | 4 | pub mod clear; 5 | pub mod cpu; 6 | pub mod draw; 7 | pub mod frame_buffer; 8 | // pub mod hash; 9 | pub mod keypad; 10 | pub mod memory; 11 | pub mod memory_start; 12 | pub mod range; 13 | 14 | use self::{ 15 | clear::ClearChip, cpu::CpuChip, draw::DrawChip, frame_buffer::FrameBufferChip, 16 | keypad::KeypadChip, memory::MemoryChip, memory_start::MemoryStartChip, range::RangeChip, 17 | }; 18 | 19 | #[derive(Clone, Debug, EnumDispatch)] 20 | pub enum Chip0MachineChip { 21 | Cpu(CpuChip), 22 | Clear(ClearChip), 23 | Draw(DrawChip), 24 | Keypad(KeypadChip), 25 | Memory(MemoryChip), 26 | FrameBuffer(FrameBufferChip), 27 | Range(RangeChip), 28 | MemoryStart(MemoryStartChip), 29 | } 30 | -------------------------------------------------------------------------------- /chip0-core/src/airs/is_equal/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::IsEqualCols; 7 | 8 | pub struct IsEqualAir; 9 | 10 | impl BaseAir for IsEqualAir { 11 | fn width(&self) -> usize { 12 | IsEqualCols::::num_cols() 13 | } 14 | } 15 | 16 | impl Air for IsEqualAir { 17 | fn eval(&self, builder: &mut AB) { 18 | let main = builder.main(); 19 | let local = main.row_slice(0); 20 | let local: &IsEqualCols = (*local).borrow(); 21 | 22 | let diff = local.x - local.y; 23 | builder.assert_eq( 24 | AB::Expr::one() - diff.clone() * local.diff_inv, 25 | local.is_equal, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chip8-core/src/cpu/simple.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | use super::Cpu; 4 | use crate::state::{SimpleState, Word}; 5 | 6 | pub struct SimpleCpu { 7 | // TODO: Make private 8 | pub state: SimpleState, 9 | pub clk_freq: u64, 10 | pub rng: R, 11 | } 12 | 13 | impl SimpleCpu { 14 | pub fn new(clk_freq: u64, rng: R) -> Self { 15 | Self { 16 | state: SimpleState::default(), 17 | clk_freq, 18 | rng, 19 | } 20 | } 21 | } 22 | 23 | impl Cpu for SimpleCpu { 24 | type State = SimpleState; 25 | 26 | fn state(&mut self) -> &mut Self::State { 27 | &mut self.state 28 | } 29 | 30 | fn random(&mut self) -> Word { 31 | self.rng.gen() 32 | } 33 | 34 | fn frequency(&self) -> u64 { 35 | self.clk_freq 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chip0-core/src/chips/clear/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::ClearCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct ClearChip { 15 | bus_clear: usize, 16 | bus_frame_buffer: usize, 17 | } 18 | 19 | impl ClearChip { 20 | pub fn new(bus_clear: usize, bus_frame_buffer: usize) -> Self { 21 | Self { 22 | bus_clear, 23 | bus_frame_buffer, 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "trace-writer")] 29 | impl> TraceWriter for ClearChip { 30 | fn main_headers(&self) -> Vec { 31 | ClearCols::::headers() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chip0-tui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chip0-tui" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.4", features = ["derive"] } 8 | crossterm = { version = "0.27.0" } 9 | csv = { version = "1.3.0" } 10 | eyre = { version = "0.6.12" } 11 | ratatui = { version = "0.26.2" } 12 | serde = { version = "1.0.200", features = ["derive"] } 13 | tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] } 14 | 15 | chip0-core = { path = "../chip0-core" } 16 | chip8-core = { path = "../chip8-core" } 17 | 18 | p3-uni-stark = { workspace = true } 19 | p3-baby-bear = { workspace = true } 20 | 21 | tracing = { workspace = true } 22 | rand = { workspace = true } 23 | tracing-subscriber = { workspace = true } 24 | tracing-forest = { workspace = true } 25 | 26 | p3-machine = { workspace = true } 27 | 28 | [[bin]] 29 | name = "chip0" 30 | path = "src/main.rs" 31 | -------------------------------------------------------------------------------- /chip0-core/src/chips/frame_buffer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::FrameBufferCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct FrameBufferChip { 15 | bus_frame_buffer: usize, 16 | bus_range: usize, 17 | } 18 | 19 | impl FrameBufferChip { 20 | pub fn new(bus_frame_buffer: usize, bus_range: usize) -> Self { 21 | Self { 22 | bus_frame_buffer, 23 | bus_range, 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "trace-writer")] 29 | impl> TraceWriter for FrameBufferChip { 30 | fn main_headers(&self) -> Vec { 31 | FrameBufferCols::::headers() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chip0-core/src/airs/is_equal_constant/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::IsEqualConstantCols; 7 | 8 | pub struct IsEqualConstantAir(pub u32); 9 | 10 | impl BaseAir for IsEqualConstantAir { 11 | fn width(&self) -> usize { 12 | IsEqualConstantCols::::num_cols() 13 | } 14 | } 15 | 16 | impl Air for IsEqualConstantAir { 17 | fn eval(&self, builder: &mut AB) { 18 | let main = builder.main(); 19 | let local = main.row_slice(0); 20 | let local: &IsEqualConstantCols = (*local).borrow(); 21 | 22 | let diff = local.x - AB::Expr::from_canonical_u32(self.0); 23 | builder.assert_eq( 24 | AB::Expr::one() - diff.clone() * local.diff_inv, 25 | local.is_equal, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chip8-core/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::state::Address; 4 | 5 | #[derive(Error, Debug, Clone)] 6 | pub enum Chip8Error { 7 | #[error("Memory access out of bounds: 0x{0:04X}")] 8 | MemoryAccessOutOfBounds(Address), 9 | #[error("Unimplemented opcode: 0x{0:04X}")] 10 | UnimplementedOpcode(u16), 11 | #[error("ROM size too big: {0}bytes")] 12 | RomTooBig(usize), 13 | #[error("Display Error: {0}")] 14 | DisplayError(String), 15 | #[error("Input Error: {0}")] 16 | InputError(String), 17 | #[error("Audio Error: {0}")] 18 | AudioError(String), 19 | #[error("Async/Await Error: {0}")] 20 | AsyncAwaitError(String), 21 | #[error("Mutex read error: {0}")] 22 | MutexReadError(String), 23 | #[error("Mutex write error: {0}")] 24 | MutexWriteError(String), 25 | #[error("Interrupted")] 26 | Interrupt, 27 | #[error("Terminated")] 28 | Terminated, 29 | } 30 | -------------------------------------------------------------------------------- /chip0-core/src/chips/draw/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::DrawCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct DrawChip { 15 | bus_draw: usize, 16 | bus_frame_buffer: usize, 17 | bus_memory: usize, 18 | } 19 | 20 | impl DrawChip { 21 | pub fn new(bus_draw: usize, bus_frame_buffer: usize, bus_memory: usize) -> Self { 22 | Self { 23 | bus_draw, 24 | bus_frame_buffer, 25 | bus_memory, 26 | } 27 | } 28 | } 29 | 30 | #[cfg(feature = "trace-writer")] 31 | impl> TraceWriter for DrawChip { 32 | fn main_headers(&self) -> Vec { 33 | DrawCols::::headers() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::MemoryCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct MemoryChip { 15 | bus_memory_start: usize, 16 | bus_memory: usize, 17 | bus_range: usize, 18 | } 19 | 20 | impl MemoryChip { 21 | pub fn new(bus_memory_start: usize, bus_memory: usize, bus_range: usize) -> Self { 22 | Self { 23 | bus_memory_start, 24 | bus_memory, 25 | bus_range, 26 | } 27 | } 28 | } 29 | 30 | #[cfg(feature = "trace-writer")] 31 | impl> TraceWriter for MemoryChip { 32 | fn main_headers(&self) -> Vec { 33 | MemoryCols::::headers() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chip0-core/src/chips/range/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::RangeCols; 7 | use super::RangeChip; 8 | 9 | impl BaseAir for RangeChip { 10 | fn width(&self) -> usize { 11 | RangeCols::::num_cols() 12 | } 13 | } 14 | 15 | impl Air for RangeChip { 16 | fn eval(&self, builder: &mut AB) { 17 | let main = builder.main(); 18 | let local = main.row_slice(0); 19 | let next = main.row_slice(1); 20 | let local: &RangeCols = (*local).borrow(); 21 | let next: &RangeCols = (*next).borrow(); 22 | 23 | builder 24 | .when_first_row() 25 | .assert_eq(local.value, AB::Expr::zero()); 26 | builder 27 | .when_transition() 28 | .assert_eq(next.value, local.value + AB::Expr::one()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chip0-core/src/chips/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::CpuCols; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct CpuChip { 15 | bus_clear: usize, 16 | bus_draw: usize, 17 | bus_memory: usize, 18 | bus_keypad: usize, 19 | } 20 | 21 | impl CpuChip { 22 | pub fn new(bus_clear: usize, bus_draw: usize, bus_memory: usize, bus_keypad: usize) -> Self { 23 | Self { 24 | bus_clear, 25 | bus_draw, 26 | bus_memory, 27 | bus_keypad, 28 | } 29 | } 30 | } 31 | 32 | #[cfg(feature = "trace-writer")] 33 | impl> TraceWriter for CpuChip { 34 | fn main_headers(&self) -> Vec { 35 | CpuCols::::headers() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chip0-core/src/airs/counter/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::CounterCols; 7 | 8 | pub struct CounterAir; 9 | 10 | impl BaseAir for CounterAir { 11 | fn width(&self) -> usize { 12 | CounterCols::::num_cols() 13 | } 14 | } 15 | 16 | impl Air for CounterAir { 17 | fn eval(&self, builder: &mut AB) { 18 | let main = builder.main(); 19 | let local = main.row_slice(0); 20 | let next = main.row_slice(1); 21 | let local: &CounterCols = (*local).borrow(); 22 | let next: &CounterCols = (*next).borrow(); 23 | 24 | builder 25 | .when_first_row() 26 | .assert_eq(local.counter, AB::Expr::zero()); 27 | 28 | builder 29 | .when_transition() 30 | .assert_eq(next.counter, local.counter + AB::Expr::one()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chip0-core/src/airs/loop_counter/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::LoopCounterCols; 7 | 8 | pub struct LoopCounterAir; 9 | 10 | impl BaseAir for LoopCounterAir { 11 | fn width(&self) -> usize { 12 | LoopCounterCols::::num_cols() 13 | } 14 | } 15 | 16 | impl Air for LoopCounterAir { 17 | fn eval(&self, builder: &mut AB) { 18 | let main = builder.main(); 19 | let local = main.row_slice(0); 20 | let next = main.row_slice(1); 21 | let local: &LoopCounterCols = (*local).borrow(); 22 | let next: &LoopCounterCols = (*next).borrow(); 23 | 24 | builder.assert_bool(local.is_start); 25 | builder.when(local.is_start).assert_zero(local.counter); 26 | builder 27 | .when_ne(next.is_start, AB::Expr::one()) 28 | .assert_eq(next.counter, local.counter + AB::Expr::one()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory_start/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod air; 2 | pub mod columns; 3 | pub mod interaction; 4 | 5 | #[cfg(feature = "trace-writer")] 6 | use p3_air_util::TraceWriter; 7 | #[cfg(feature = "trace-writer")] 8 | use p3_field::{ExtensionField, Field}; 9 | 10 | #[cfg(feature = "trace-writer")] 11 | use self::columns::{MemoryStartCols, MemoryStartPreprocessedCols}; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct MemoryStartChip { 15 | rom: Vec, 16 | bus_memory_start: usize, 17 | } 18 | 19 | impl MemoryStartChip { 20 | pub fn new(rom: Vec, bus_memory_start: usize) -> Self { 21 | Self { 22 | rom, 23 | bus_memory_start, 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "trace-writer")] 29 | impl> TraceWriter for MemoryStartChip { 30 | fn preprocessed_headers(&self) -> Vec { 31 | MemoryStartPreprocessedCols::::headers() 32 | } 33 | 34 | fn main_headers(&self) -> Vec { 35 | MemoryStartCols::::headers() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chip0-core/src/airs/selector/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::SelectorCols; 7 | 8 | pub struct SelectorAir; 9 | 10 | impl BaseAir for SelectorAir { 11 | fn width(&self) -> usize { 12 | SelectorCols::::num_cols() 13 | } 14 | } 15 | 16 | impl Air for SelectorAir { 17 | fn eval(&self, builder: &mut AB) { 18 | let main = builder.main(); 19 | let local = main.row_slice(0); 20 | let local: &SelectorCols = (*local).borrow(); 21 | 22 | // Selectors are boolean 23 | for i in 0..N { 24 | builder.assert_bool(local.selectors[i]); 25 | } 26 | 27 | // Only one selector is active 28 | let sum = local 29 | .selectors 30 | .iter() 31 | .fold(AB::Expr::zero(), |acc, x| acc + *x); 32 | builder.assert_one(sum); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chip0-core/src/chips/clear/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::ClearCols; 7 | use super::ClearChip; 8 | 9 | impl BaseAir for ClearChip { 10 | fn width(&self) -> usize { 11 | ClearCols::::num_cols() 12 | } 13 | } 14 | 15 | impl Air for ClearChip { 16 | fn eval(&self, builder: &mut AB) { 17 | let main = builder.main(); 18 | let local = main.row_slice(0); 19 | let next = main.row_slice(1); 20 | let local: &ClearCols = (*local).borrow(); 21 | let next: &ClearCols = (*next).borrow(); 22 | 23 | builder.assert_bool(local.is_real); 24 | // builder.assert_bool(local.is_start); 25 | 26 | builder.when(local.is_start).assert_zero(local.addr); 27 | 28 | builder 29 | .when(next.is_real) 30 | .when_ne(next.is_start, AB::Expr::one()) 31 | .assert_eq(next.addr, local.addr + AB::Expr::one()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chip0-core/src/chips/range/interaction.rs: -------------------------------------------------------------------------------- 1 | use p3_air::VirtualPairCol; 2 | use p3_field::Field; 3 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 4 | 5 | use super::{columns::RangeCols, RangeChip}; 6 | 7 | impl BaseInteractionAir for RangeChip { 8 | fn receives_from_indices( 9 | &self, 10 | _preprocessed_indices: &[usize], 11 | main_indices: &[usize], 12 | ) -> Vec> { 13 | let col_map = RangeCols::from_slice(main_indices); 14 | vec![Interaction { 15 | fields: vec![VirtualPairCol::single_main(col_map.value)], 16 | count: VirtualPairCol::single_main(col_map.mult), 17 | argument_index: self.bus_range, 18 | }] 19 | } 20 | } 21 | 22 | impl InteractionAir for RangeChip { 23 | fn receives(&self) -> Vec> { 24 | let col_map = RangeCols::::col_map(); 25 | self.receives_from_main_indices(col_map.as_slice()) 26 | } 27 | } 28 | 29 | impl Rap for RangeChip {} 30 | -------------------------------------------------------------------------------- /chip0-tui/src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use ratatui::style::Color; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Parser)] 6 | #[command(author, version, about, long_about = None)] 7 | pub struct CmdArgs { 8 | #[arg(required = true, value_parser)] 9 | pub rom: PathBuf, 10 | 11 | #[arg(long = "clock-frequency", default_value_t = 560)] 12 | pub clk_freq: u64, 13 | #[arg(long, default_value_t = 60)] 14 | pub refresh_rate: u64, 15 | 16 | #[arg(long)] 17 | pub num_cycles: Option, 18 | 19 | #[arg(long, default_value_t = false)] 20 | pub headless: bool, 21 | 22 | #[arg(long)] 23 | pub random_seed: Option, 24 | 25 | #[arg(long = "inputs")] 26 | pub input_file: Option, 27 | 28 | #[arg(long, default_value_t = false, requires = "input_file")] 29 | pub overwrite: bool, 30 | 31 | #[arg(long = "background", default_value_t = Color::Black, conflicts_with="headless")] 32 | pub bg_color: Color, 33 | #[arg(long = "foreground", default_value_t = Color::White, conflicts_with="headless")] 34 | pub fg_color: Color, 35 | #[arg(long = "border", default_value_t = Color::White, conflicts_with="headless")] 36 | pub border_color: Color, 37 | } 38 | -------------------------------------------------------------------------------- /chip8-core/src/drivers/display.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use crate::{ 4 | constants::{DISPLAY_HEIGHT, DISPLAY_WIDTH}, 5 | error::Chip8Error, 6 | rwlock::CheckedRead, 7 | util::run_loop, 8 | }; 9 | 10 | pub trait DisplayDriver: Send { 11 | fn frequency(&self) -> u64; 12 | 13 | fn draw( 14 | &mut self, 15 | frame_buffer: [[bool; DISPLAY_WIDTH]; DISPLAY_HEIGHT], 16 | cpu_freq: Option, 17 | ) -> Result<(), Chip8Error>; 18 | 19 | fn run( 20 | &mut self, 21 | status: Arc>>, 22 | frame_buffer: Arc>, 23 | clk: Arc>, 24 | ) { 25 | let mut prev_clk = 0; 26 | run_loop(status.clone(), self.frequency(), move |elapsed| { 27 | // TODO: Put behind feature flag 28 | let curr_clk = *clk.checked_read()?; 29 | let freq = (curr_clk - prev_clk) as f64 / elapsed.as_secs_f64(); 30 | let freq = freq.round() as u64; 31 | 32 | self.draw(*frame_buffer.checked_read()?, Some(freq))?; 33 | prev_clk = curr_clk; 34 | 35 | Ok(()) 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chip8-core/src/drivers/input.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | sync::{Arc, RwLock}, 4 | }; 5 | 6 | use crate::{ 7 | error::Chip8Error, 8 | input::{InputEvent, InputQueue}, 9 | rwlock::{CheckedRead, CheckedWrite}, 10 | util::run_loop, 11 | }; 12 | 13 | pub trait InputDriver: Send { 14 | fn frequency(&self) -> u64; 15 | 16 | fn poll(&mut self) -> Result, Chip8Error>; 17 | 18 | fn log_input(&mut self, _clk: u64, _input: InputEvent) -> Result<(), Chip8Error> { 19 | Ok(()) 20 | } 21 | 22 | fn run( 23 | &mut self, 24 | status: Arc>>, 25 | queue: Arc>>, 26 | clk: Arc>, 27 | ) { 28 | run_loop(status.clone(), self.frequency(), move |_| { 29 | let maybe_event = self.poll()?; 30 | 31 | let clk = *clk.checked_read()?; 32 | let queue_clk = (*queue.checked_read()?).back_clk(); 33 | if clk >= queue_clk.unwrap_or_default() { 34 | if let Some(event) = maybe_event { 35 | self.log_input(clk, event)?; 36 | (*queue.checked_write()?).enqueue(clk, event); 37 | } 38 | } 39 | Ok(()) 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chip8-core/src/rwlock.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; 2 | 3 | use crate::error::Chip8Error; 4 | 5 | pub trait CheckedRead { 6 | fn checked_read(&self) -> Result, Chip8Error>; 7 | } 8 | 9 | impl CheckedRead for RwLock { 10 | fn checked_read(&self) -> Result, Chip8Error> { 11 | self.read() 12 | .map_err(|e| Chip8Error::MutexReadError(e.to_string())) 13 | } 14 | } 15 | 16 | impl CheckedRead for Arc> { 17 | fn checked_read(&self) -> Result, Chip8Error> { 18 | self.read() 19 | .map_err(|e| Chip8Error::MutexReadError(e.to_string())) 20 | } 21 | } 22 | 23 | pub trait CheckedWrite { 24 | fn checked_write(&self) -> Result, Chip8Error>; 25 | } 26 | 27 | impl CheckedWrite for RwLock { 28 | fn checked_write(&self) -> Result, Chip8Error> { 29 | self.write() 30 | .map_err(|e| Chip8Error::MutexWriteError(e.to_string())) 31 | } 32 | } 33 | 34 | impl CheckedWrite for Arc> { 35 | fn checked_write(&self) -> Result, Chip8Error> { 36 | self.write() 37 | .map_err(|e| Chip8Error::MutexWriteError(e.to_string())) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chip0-core/src/chips/keypad/interaction.rs: -------------------------------------------------------------------------------- 1 | use p3_air::VirtualPairCol; 2 | use p3_field::Field; 3 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 4 | 5 | use super::{columns::KeypadCols, KeypadChip}; 6 | 7 | impl BaseInteractionAir for KeypadChip { 8 | fn sends_from_indices( 9 | &self, 10 | _preprocessed_indices: &[usize], 11 | main_indices: &[usize], 12 | ) -> Vec> { 13 | let col_map = KeypadCols::from_slice(main_indices); 14 | vec![ 15 | // Interaction { 16 | // fields: vec![ 17 | // VirtualPairCol::single_main(col_map.clk), 18 | // VirtualPairCol::single_main(col_map.index), 19 | // VirtualPairCol::single_main(col_map.value), 20 | // ], 21 | // count: VirtualPairCol::single_main(col_map.is_real), 22 | // argument_index: self.bus_keypad, 23 | // } 24 | ] 25 | } 26 | } 27 | 28 | impl InteractionAir for KeypadChip { 29 | fn sends(&self) -> Vec> { 30 | let col_map = KeypadCols::::col_map(); 31 | self.sends_from_main_indices(col_map.as_slice()) 32 | } 33 | } 34 | 35 | impl Rap for KeypadChip {} 36 | -------------------------------------------------------------------------------- /rom/zkhack/zkhack.8o: -------------------------------------------------------------------------------- 1 | : main 2 | clear # Address 512 / 0x200 3 | 4 | i := zkhack-z # Address 514 / 0x202 5 | v0 := 12 # Address 516 / 0x204 6 | v1 := 12 7 | sprite v0 v1 7 # Address 520 / 0x208 8 | 9 | v0 += 6 # Address 522 / 0x20A 10 | i := zkhack-k 11 | sprite v0 v1 7 # Address 520 / 0x208 12 | 13 | v0 += 9 14 | i := zkhack-h 15 | sprite v0 v1 7 16 | 17 | v0 += 6 18 | i := zkhack-a 19 | sprite v0 v1 7 20 | 21 | v0 += 6 22 | i := zkhack-c 23 | sprite v0 v1 7 24 | 25 | v0 += 6 26 | i := zkhack-k 27 | sprite v0 v1 7 28 | 29 | v2 := key 30 | 31 | loop again # Address 552 / 0x228 32 | 33 | : zkhack-z 34 | 0b11111000 35 | 0b00001000 36 | 0b00010000 37 | 0b00100000 38 | 0b01000000 39 | 0b10000000 40 | 0b11111000 41 | 42 | : zkhack-k 43 | 0b10001000 44 | 0b10010000 45 | 0b11100000 46 | 0b10010000 47 | 0b10001000 48 | 0b10001000 49 | 0b10001000 50 | 51 | : zkhack-h 52 | 0b10001000 53 | 0b10001000 54 | 0b11111000 55 | 0b10001000 56 | 0b10001000 57 | 0b10001000 58 | 0b10001000 59 | 60 | : zkhack-a 61 | 0b01110000 62 | 0b10001000 63 | 0b11111000 64 | 0b10001000 65 | 0b10001000 66 | 0b10001000 67 | 0b10001000 68 | 69 | : zkhack-c 70 | 0b01110000 71 | 0b10001000 72 | 0b10000000 73 | 0b10000000 74 | 0b10000000 75 | 0b10001000 76 | 0b01110000 77 | -------------------------------------------------------------------------------- /chip8-core/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use crate::state::{Address, Word}; 2 | 3 | type Nibble = u8; // ideally u4 4 | type RegisterIndex = u8; // ideally u4 5 | 6 | #[derive(Debug)] 7 | pub enum Instruction { 8 | ClearDisplay, 9 | Return, 10 | Jump(Address), 11 | Call(Address), 12 | SkipEqual(RegisterIndex, Word), 13 | SkipNotEqual(RegisterIndex, Word), 14 | SkipEqualXY(RegisterIndex, RegisterIndex), 15 | Load(RegisterIndex, Word), 16 | Add(RegisterIndex, Word), 17 | 18 | Move(RegisterIndex, RegisterIndex), 19 | Or(RegisterIndex, RegisterIndex), 20 | And(RegisterIndex, RegisterIndex), 21 | Xor(RegisterIndex, RegisterIndex), 22 | AddXY(RegisterIndex, RegisterIndex), 23 | SubXY(RegisterIndex, RegisterIndex), 24 | ShiftRight(RegisterIndex), 25 | SubYX(RegisterIndex, RegisterIndex), 26 | ShiftLeft(RegisterIndex), 27 | 28 | SkipNotEqualXY(RegisterIndex, RegisterIndex), 29 | LoadI(Address), 30 | JumpV0(Address), 31 | Random(RegisterIndex, Word), 32 | Draw(RegisterIndex, RegisterIndex, Nibble), 33 | 34 | SkipKeyPressed(RegisterIndex), 35 | SkipKeyNotPressed(RegisterIndex), 36 | 37 | LoadDelay(RegisterIndex), 38 | WaitKeyPress(RegisterIndex), 39 | SetDelay(RegisterIndex), 40 | SetSound(RegisterIndex), 41 | AddI(RegisterIndex), 42 | LoadFont(RegisterIndex), 43 | StoreBCD(RegisterIndex), 44 | StoreRegisters(RegisterIndex), 45 | LoadMemory(RegisterIndex), 46 | } 47 | -------------------------------------------------------------------------------- /chip0-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chip0-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | p3-air = { workspace = true } 8 | p3-baby-bear = { workspace = true } 9 | p3-challenger = { workspace = true } 10 | p3-commit = { workspace = true } 11 | p3-dft = { workspace = true } 12 | p3-field = { workspace = true } 13 | p3-fri = { workspace = true } 14 | p3-keccak = { workspace = true } 15 | p3-keccak-air = { workspace = true } 16 | p3-matrix = { workspace = true } 17 | p3-maybe-rayon = { workspace = true } 18 | p3-mds = { workspace = true } 19 | p3-merkle-tree = { workspace = true } 20 | p3-poseidon2 = { workspace = true } 21 | p3-symmetric = { workspace = true } 22 | p3-uni-stark = { workspace = true } 23 | p3-util = { workspace = true } 24 | 25 | p3-derive = { workspace = true } 26 | p3-interaction = { workspace = true } 27 | p3-air-util = { workspace = true } 28 | p3-machine = { workspace = true } 29 | 30 | serde = { workspace = true } 31 | tracing = { workspace = true } 32 | 33 | chip8-core = { path = "../chip8-core" } 34 | 35 | itertools = "0.12.1" 36 | rand = "0.8.5" 37 | tokio = { version = "1.37.0", features = ["rt"] } 38 | tracing-subscriber = { version = "0.3.17", features = ["std", "env-filter"] } 39 | tracing-forest = { version = "0.1.6", features = ["ansi", "smallvec"] } 40 | tiny-keccak = "2.0.2" 41 | 42 | [features] 43 | default = [] 44 | trace-writer = [ 45 | "p3-air-util/trace-writer", 46 | "p3-derive/trace-writer", 47 | "p3-machine/trace-writer", 48 | ] 49 | -------------------------------------------------------------------------------- /chip0-core/src/chips/clear/interaction.rs: -------------------------------------------------------------------------------- 1 | use p3_air::VirtualPairCol; 2 | use p3_field::Field; 3 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 4 | 5 | use super::{columns::ClearCols, ClearChip}; 6 | 7 | impl BaseInteractionAir for ClearChip { 8 | fn receives_from_indices( 9 | &self, 10 | _preprocessed_indices: &[usize], 11 | main_indices: &[usize], 12 | ) -> Vec> { 13 | let col_map = ClearCols::from_slice(main_indices); 14 | vec![ 15 | Interaction { 16 | fields: vec![VirtualPairCol::single_main(col_map.clk)], 17 | count: VirtualPairCol::single_main(col_map.is_start), 18 | argument_index: self.bus_clear, 19 | }, 20 | Interaction { 21 | fields: vec![ 22 | VirtualPairCol::single_main(col_map.addr), 23 | VirtualPairCol::single_main(col_map.clk), 24 | VirtualPairCol::constant(F::zero()), 25 | ], 26 | count: VirtualPairCol::single_main(col_map.is_real), 27 | argument_index: self.bus_frame_buffer, 28 | }, 29 | ] 30 | } 31 | } 32 | 33 | impl InteractionAir for ClearChip { 34 | fn receives(&self) -> Vec> { 35 | let col_map = ClearCols::::col_map(); 36 | self.receives_from_main_indices(col_map.as_slice()) 37 | } 38 | } 39 | 40 | impl Rap for ClearChip {} 41 | -------------------------------------------------------------------------------- /chip8-core/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::{Arc, RwLock}, 3 | thread::sleep, 4 | time::{Duration, SystemTime}, 5 | }; 6 | 7 | use crate::{ 8 | error::Chip8Error, 9 | rwlock::{CheckedRead, CheckedWrite}, 10 | }; 11 | 12 | pub fn run_loop_inner( 13 | status: Arc>>, 14 | frequency: u64, 15 | mut fn_tick: impl FnMut(Duration) -> Result<(), Chip8Error>, 16 | ) -> Result<(), Chip8Error> { 17 | let interval = if frequency > 0 { 18 | Duration::from_secs_f64(1.0 / frequency as f64) 19 | } else { 20 | Duration::ZERO 21 | }; 22 | 23 | let mut prev_time = SystemTime::now(); 24 | while status.checked_read()?.is_ok() { 25 | let curr_time = SystemTime::now(); 26 | let elapsed = curr_time.duration_since(prev_time).unwrap_or_default(); 27 | 28 | if elapsed >= interval { 29 | fn_tick(elapsed)?; 30 | prev_time = curr_time; 31 | } else { 32 | sleep( 33 | interval 34 | .checked_sub(elapsed) 35 | .unwrap_or_default() 36 | .mul_f64(0.8), 37 | ); 38 | } 39 | } 40 | 41 | Ok(()) 42 | } 43 | 44 | pub fn run_loop( 45 | status: Arc>>, 46 | frequency: u64, 47 | fn_tick: impl FnMut(Duration) -> Result<(), Chip8Error>, 48 | ) { 49 | let res = run_loop_inner(status.clone(), frequency, fn_tick); 50 | 51 | if let Err(err) = res { 52 | *status.checked_write().unwrap() = Err(err); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chip8-core/src/input.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use crate::{error::Chip8Error, keypad::Key}; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 | pub enum InputKind { 7 | Release, 8 | Press, 9 | } 10 | 11 | impl TryFrom for InputKind { 12 | type Error = Chip8Error; 13 | 14 | fn try_from(value: u8) -> Result { 15 | match value { 16 | 0 => Ok(Self::Release), 17 | 1 => Ok(Self::Press), 18 | _ => Err(Chip8Error::InputError("Unsupported input kind".to_string())), 19 | } 20 | } 21 | } 22 | 23 | #[derive(Debug, Clone, Copy)] 24 | pub struct InputEvent { 25 | pub key: Key, 26 | pub kind: InputKind, 27 | } 28 | 29 | pub trait InputQueue { 30 | fn back_clk(&self) -> Option; 31 | fn enqueue(&mut self, clk: u64, event: InputEvent); 32 | fn dequeue(&mut self, current_clk: u64) -> Option; 33 | } 34 | 35 | impl InputQueue for VecDeque<(u64, InputEvent)> { 36 | fn back_clk(&self) -> Option { 37 | self.back().map(|(clk, _)| *clk) 38 | } 39 | 40 | fn enqueue(&mut self, clk: u64, event: InputEvent) { 41 | self.push_back((clk, event)); 42 | } 43 | 44 | fn dequeue(&mut self, current_clk: u64) -> Option { 45 | if let Some((clk, _)) = self.front() { 46 | if *clk <= current_clk { 47 | let (_, event) = self.pop_front().unwrap(); 48 | Some(event) 49 | } else { 50 | None 51 | } 52 | } else { 53 | None 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory_start/interaction.rs: -------------------------------------------------------------------------------- 1 | use p3_air::VirtualPairCol; 2 | use p3_field::Field; 3 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 4 | 5 | use super::{ 6 | columns::{MemoryStartCols, MemoryStartPreprocessedCols}, 7 | MemoryStartChip, 8 | }; 9 | 10 | impl BaseInteractionAir for MemoryStartChip { 11 | fn sends_from_indices( 12 | &self, 13 | preprocessed_indices: &[usize], 14 | main_indices: &[usize], 15 | ) -> Vec> { 16 | let preprocessed_col_map = MemoryStartPreprocessedCols::from_slice(preprocessed_indices); 17 | let col_map = MemoryStartCols::from_slice(main_indices); 18 | vec![Interaction { 19 | fields: vec![ 20 | VirtualPairCol::single_preprocessed(preprocessed_col_map.addr), 21 | VirtualPairCol::single_preprocessed(preprocessed_col_map.value), 22 | ], 23 | count: VirtualPairCol::single_main(col_map.mult), 24 | argument_index: self.bus_memory_start, 25 | }] 26 | } 27 | } 28 | 29 | impl InteractionAir for MemoryStartChip { 30 | fn sends(&self) -> Vec> { 31 | let preprocessed_col_map = MemoryStartPreprocessedCols::::col_map(); 32 | let main_col_map = MemoryStartCols::::col_map(); 33 | 34 | self.sends_from_indices(preprocessed_col_map.as_slice(), main_col_map.as_slice()) 35 | } 36 | } 37 | 38 | impl Rap for MemoryStartChip { 39 | fn preprocessed_width(&self) -> usize { 40 | MemoryStartPreprocessedCols::::num_cols() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chip0-tui/src/terminal.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::constants::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; 2 | use crossterm::{ 3 | cursor::{Hide, Show}, 4 | event::{KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, 5 | execute, 6 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 7 | }; 8 | use eyre::{bail, Result}; 9 | use ratatui::{backend::CrosstermBackend, layout::Rect, Terminal}; 10 | use std::io::{stdout, Error, Stdout}; 11 | 12 | pub fn setup_terminal(headless: bool) -> Result>> { 13 | let backend = CrosstermBackend::new(stdout()); 14 | let terminal = Terminal::new(backend)?; 15 | 16 | if !headless { 17 | enable_raw_mode()?; 18 | execute!(stdout(), EnterAlternateScreen, Hide)?; 19 | execute!( 20 | stdout(), 21 | PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES), 22 | )?; 23 | 24 | // Check terminal size 25 | let Rect { width, height, .. } = terminal.size()?; 26 | if width < 2 * DISPLAY_WIDTH as u16 { 27 | bail!( 28 | "Error: Terminal width {width} less than minimum width {}", 29 | 2 * DISPLAY_WIDTH, 30 | ); 31 | } else if height < DISPLAY_HEIGHT as u16 { 32 | bail!("Error: Terminal height {height} less than minimum height {DISPLAY_HEIGHT}"); 33 | } 34 | } 35 | 36 | Ok(terminal) 37 | } 38 | 39 | pub fn restore_terminal(headless: bool) -> Result<(), Error> { 40 | if !headless { 41 | execute!(stdout(), PopKeyboardEnhancementFlags)?; 42 | execute!(stdout(), Show, LeaveAlternateScreen)?; 43 | disable_raw_mode()?; 44 | } 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /chip0-core/src/prover.rs: -------------------------------------------------------------------------------- 1 | use p3_field::PrimeField32; 2 | use p3_machine::machine::Machine; 3 | use p3_uni_stark::{StarkGenericConfig, Val}; 4 | 5 | use super::config::{default_challenger, default_config, Challenger, MyConfig}; 6 | use super::machine::Chip0Machine; 7 | use super::trace::PartialMachineTrace; 8 | 9 | #[derive(Clone)] 10 | pub struct DefaultProver 11 | where 12 | SC: StarkGenericConfig, 13 | Val: PrimeField32, 14 | { 15 | machine: Chip0Machine, 16 | config: SC, 17 | } 18 | 19 | impl DefaultProver { 20 | pub fn new(rom: Vec) -> Self { 21 | Self { 22 | machine: Chip0Machine::new(rom), 23 | config: default_config(), 24 | } 25 | } 26 | } 27 | 28 | pub trait Prover 29 | where 30 | SC: StarkGenericConfig, 31 | Val: PrimeField32, 32 | { 33 | fn prove(&self, partial_trace: PartialMachineTrace>); 34 | 35 | fn new_challenger(&self) -> Challenger; 36 | } 37 | 38 | impl Prover for DefaultProver { 39 | fn new_challenger(&self) -> Challenger { 40 | default_challenger() 41 | } 42 | 43 | fn prove(&self, partial_trace: PartialMachineTrace>) { 44 | let (pk, vk) = self.machine.setup(&self.config); 45 | 46 | let traces = partial_trace.get_trace_matrices(); 47 | let public_values = vec![]; 48 | 49 | let mut challenger = self.new_challenger(); 50 | let proof = self 51 | .machine 52 | .prove(&self.config, &mut challenger, &pk, traces, &public_values); 53 | 54 | // TODO: Avoid clone 55 | let mut challenger = self.new_challenger(); 56 | self.machine 57 | .verify(&self.config, &mut challenger, &vk, &proof, &public_values) 58 | .unwrap(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::MemoryCols; 7 | use super::MemoryChip; 8 | 9 | impl BaseAir for MemoryChip { 10 | fn width(&self) -> usize { 11 | MemoryCols::::num_cols() 12 | } 13 | } 14 | 15 | impl Air for MemoryChip { 16 | fn eval(&self, builder: &mut AB) { 17 | let main = builder.main(); 18 | let local = main.row_slice(0); 19 | let next = main.row_slice(1); 20 | let local: &MemoryCols = (*local).borrow(); 21 | let next: &MemoryCols = (*next).borrow(); 22 | 23 | builder.assert_bool(local.is_read); 24 | builder.assert_bool(local.is_write); 25 | 26 | builder.assert_zero(local.is_read * local.is_write); 27 | builder.assert_bool(local.addr_unchanged); 28 | 29 | builder 30 | .when_transition() 31 | .when(next.addr_unchanged) 32 | .assert_eq(local.addr, next.addr); 33 | 34 | let diff = next.diff_limb_lo + next.diff_limb_hi * AB::Expr::from_canonical_u32(1 << 8); 35 | builder 36 | .when_transition() 37 | .when(next.addr_unchanged) 38 | .assert_eq(diff.clone(), next.clk - local.clk); 39 | builder 40 | .when_transition() 41 | .when(next.is_read + next.is_write) 42 | .when_ne(next.addr_unchanged, AB::Expr::one()) 43 | .assert_eq(diff, next.addr - local.addr - AB::Expr::one()); 44 | 45 | // TODO: Do I need this? 46 | builder 47 | .when_transition() 48 | .when(next.addr_unchanged) 49 | .when(next.is_read) 50 | .assert_eq(local.value, next.value); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /chip0-core/src/airs/modulo_counter/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_air_util::builders::SubAirBuilder; 4 | use p3_field::AbstractField; 5 | use p3_matrix::Matrix; 6 | 7 | use crate::airs::is_equal_constant::IsEqualConstantAir; 8 | 9 | use super::columns::ModuloCounterCols; 10 | 11 | pub struct ModuloCounterAir(pub u32); 12 | 13 | impl BaseAir for ModuloCounterAir { 14 | fn width(&self) -> usize { 15 | ModuloCounterCols::::num_cols() 16 | } 17 | } 18 | 19 | impl Air for ModuloCounterAir { 20 | fn eval(&self, builder: &mut AB) { 21 | let main = builder.main(); 22 | let local = main.row_slice(0); 23 | let next = main.row_slice(1); 24 | let local: &ModuloCounterCols = (*local).borrow(); 25 | let next: &ModuloCounterCols = (*next).borrow(); 26 | 27 | let col_map = ModuloCounterCols::::col_map(); 28 | 29 | // Initialize the counter to 0 30 | builder 31 | .when_first_row() 32 | .assert_eq(local.counter, AB::Expr::zero()); 33 | 34 | // Check if reached the max value 35 | let is_equal = IsEqualConstantAir(self.0 - 1); 36 | let mut sub_builder = SubAirBuilder::new_main( 37 | builder, 38 | vec![col_map.counter, col_map.diff_inv, col_map.is_max], 39 | ); 40 | is_equal.eval(&mut sub_builder); 41 | 42 | // Increment the counter 43 | builder 44 | .when_transition() 45 | .when_ne(local.is_max, AB::Expr::one()) 46 | .assert_eq(next.counter, local.counter + AB::Expr::one()); 47 | // Reset the counter 48 | builder 49 | .when_transition() 50 | .when(local.is_max) 51 | .assert_zero(next.counter); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /chip0-core/src/chips/frame_buffer/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use p3_air::{Air, AirBuilder, BaseAir}; 3 | use p3_field::AbstractField; 4 | use p3_matrix::Matrix; 5 | 6 | use super::columns::FrameBufferCols; 7 | use super::FrameBufferChip; 8 | 9 | impl BaseAir for FrameBufferChip { 10 | fn width(&self) -> usize { 11 | FrameBufferCols::::num_cols() 12 | } 13 | } 14 | 15 | impl Air for FrameBufferChip { 16 | fn eval(&self, builder: &mut AB) { 17 | let main = builder.main(); 18 | let local = main.row_slice(0); 19 | let next = main.row_slice(1); 20 | let local: &FrameBufferCols = (*local).borrow(); 21 | let next: &FrameBufferCols = (*next).borrow(); 22 | 23 | builder.assert_bool(local.is_read); 24 | builder.assert_bool(local.is_write); 25 | 26 | builder.assert_zero(local.is_read * local.is_write); 27 | builder.assert_bool(local.addr_unchanged); 28 | 29 | builder 30 | .when_transition() 31 | .when(next.addr_unchanged) 32 | .assert_eq(local.addr, next.addr); 33 | 34 | let diff = next.diff_limb_lo + next.diff_limb_hi * AB::Expr::from_canonical_u32(1 << 8); 35 | builder 36 | .when_transition() 37 | .when(next.addr_unchanged) 38 | .assert_eq(diff.clone(), next.clk - local.clk); 39 | builder 40 | .when_transition() 41 | .when(next.is_read + next.is_write) 42 | .when_ne(next.addr_unchanged, AB::Expr::one()) 43 | .assert_eq(diff, next.addr - local.addr - AB::Expr::one()); 44 | 45 | // TODO: Do I need this? 46 | builder 47 | .when_transition() 48 | .when(next.addr_unchanged) 49 | .when(next.is_read) 50 | .assert_eq(local.value, next.value); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /chip0-core/src/chips/frame_buffer/interaction.rs: -------------------------------------------------------------------------------- 1 | use p3_air::VirtualPairCol; 2 | use p3_field::Field; 3 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 4 | 5 | use super::{columns::FrameBufferCols, FrameBufferChip}; 6 | 7 | impl BaseInteractionAir for FrameBufferChip { 8 | fn sends_from_indices( 9 | &self, 10 | _preprocessed_indices: &[usize], 11 | main_indices: &[usize], 12 | ) -> Vec> { 13 | let col_map = FrameBufferCols::from_slice(main_indices); 14 | vec![ 15 | Interaction { 16 | fields: vec![ 17 | VirtualPairCol::single_main(col_map.addr), 18 | VirtualPairCol::single_main(col_map.clk), 19 | VirtualPairCol::single_main(col_map.value), 20 | ], 21 | count: VirtualPairCol::sum_main(vec![col_map.is_read, col_map.is_write]), 22 | argument_index: self.bus_frame_buffer, 23 | }, 24 | Interaction { 25 | fields: vec![VirtualPairCol::single_main(col_map.diff_limb_lo)], 26 | count: VirtualPairCol::sum_main(vec![col_map.is_read, col_map.is_write]), 27 | argument_index: self.bus_range, 28 | }, 29 | Interaction { 30 | fields: vec![VirtualPairCol::single_main(col_map.diff_limb_hi)], 31 | count: VirtualPairCol::sum_main(vec![col_map.is_read, col_map.is_write]), 32 | argument_index: self.bus_range, 33 | }, 34 | ] 35 | } 36 | } 37 | 38 | impl InteractionAir for FrameBufferChip { 39 | fn sends(&self) -> Vec> { 40 | let col_map = FrameBufferCols::::col_map(); 41 | self.sends_from_main_indices(col_map.as_slice()) 42 | } 43 | } 44 | 45 | impl Rap for FrameBufferChip {} 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["chip0-core", "chip0-tui", "chip8-core"] 3 | resolver = "2" 4 | 5 | [workspace.dependencies] 6 | p3-air = { git = "https://github.com/Plonky3/Plonky3.git" } 7 | p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3.git" } 8 | p3-challenger = { git = "https://github.com/Plonky3/Plonky3.git" } 9 | p3-commit = { git = "https://github.com/Plonky3/Plonky3.git" } 10 | p3-dft = { git = "https://github.com/Plonky3/Plonky3.git" } 11 | p3-field = { git = "https://github.com/Plonky3/Plonky3.git" } 12 | p3-fri = { git = "https://github.com/Plonky3/Plonky3.git" } 13 | p3-goldilocks = { git = "https://github.com/Plonky3/Plonky3.git" } 14 | p3-keccak = { git = "https://github.com/Plonky3/Plonky3.git" } 15 | p3-keccak-air = { git = "https://github.com/Plonky3/Plonky3.git" } 16 | p3-matrix = { git = "https://github.com/Plonky3/Plonky3.git" } 17 | p3-maybe-rayon = { git = "https://github.com/Plonky3/Plonky3.git" } 18 | p3-mds = { git = "https://github.com/Plonky3/Plonky3.git" } 19 | p3-merkle-tree = { git = "https://github.com/Plonky3/Plonky3.git" } 20 | p3-poseidon2 = { git = "https://github.com/Plonky3/Plonky3.git" } 21 | p3-symmetric = { git = "https://github.com/Plonky3/Plonky3.git" } 22 | p3-uni-stark = { git = "https://github.com/Plonky3/Plonky3.git" } 23 | p3-util = { git = "https://github.com/Plonky3/Plonky3.git" } 24 | 25 | p3-derive = { git = "https://github.com/shuklaayush/p3-utils.git" } 26 | p3-interaction = { git = "https://github.com/shuklaayush/p3-utils.git" } 27 | p3-air-util = { git = "https://github.com/shuklaayush/p3-utils.git" } 28 | p3-machine = { git = "https://github.com/shuklaayush/p3-utils.git" } 29 | 30 | serde = { version = "1.0", default-features = false, features = [ 31 | "derive", 32 | "alloc", 33 | ] } 34 | tracing = { version = "0.1.37" } 35 | tracing-subscriber = { version = "0.3.17", features = ["std", "env-filter"] } 36 | tracing-forest = { version = "0.1.6", features = ["ansi", "smallvec"] } 37 | rand = { version = "0.8.5" } 38 | -------------------------------------------------------------------------------- /chip0-core/src/config.rs: -------------------------------------------------------------------------------- 1 | use p3_baby_bear::BabyBear; 2 | use p3_challenger::{HashChallenger, SerializingChallenger32}; 3 | use p3_commit::ExtensionMmcs; 4 | use p3_dft::Radix2DitParallel; 5 | use p3_field::extension::BinomialExtensionField; 6 | use p3_fri::{FriConfig, TwoAdicFriPcs}; 7 | use p3_keccak::Keccak256Hash; 8 | use p3_merkle_tree::FieldMerkleTreeMmcs; 9 | use p3_symmetric::{CompressionFunctionFromHasher, SerializingHasher32}; 10 | use p3_uni_stark::StarkConfig; 11 | 12 | pub type Val = BabyBear; 13 | pub type Challenge = BinomialExtensionField; 14 | pub type ByteHash = Keccak256Hash; 15 | pub type FieldHash = SerializingHasher32; 16 | pub type MyCompress = CompressionFunctionFromHasher; 17 | pub type ValMmcs = FieldMerkleTreeMmcs; 18 | pub type ChallengeMmcs = ExtensionMmcs; 19 | pub type Dft = Radix2DitParallel; 20 | pub type Challenger = SerializingChallenger32>; 21 | pub type Pcs = TwoAdicFriPcs; 22 | pub type MyConfig = StarkConfig; 23 | 24 | pub fn default_config() -> MyConfig { 25 | let byte_hash = ByteHash {}; 26 | let field_hash = FieldHash::new(Keccak256Hash {}); 27 | 28 | let compress = MyCompress::new(byte_hash); 29 | 30 | let val_mmcs = ValMmcs::new(field_hash, compress.clone()); 31 | 32 | let challenge_mmcs = ChallengeMmcs::new(val_mmcs.clone()); 33 | 34 | let dft = Dft {}; 35 | 36 | let fri_config = FriConfig { 37 | log_blowup: 2, 38 | num_queries: 42, 39 | proof_of_work_bits: 16, 40 | mmcs: challenge_mmcs, 41 | }; 42 | let pcs = Pcs::new(dft, val_mmcs, fri_config); 43 | 44 | MyConfig::new(pcs) 45 | } 46 | 47 | pub fn default_challenger() -> Challenger { 48 | let byte_hash = ByteHash {}; 49 | type Challenger = SerializingChallenger32>; 50 | 51 | Challenger::from_hasher(vec![], byte_hash) 52 | } 53 | -------------------------------------------------------------------------------- /chip8-core/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use crate::{ 4 | constants::{DISPLAY_HEIGHT, DISPLAY_WIDTH}, 5 | error::Chip8Error, 6 | input::InputKind, 7 | keypad::Key, 8 | }; 9 | 10 | mod simple; 11 | pub use simple::SimpleState; 12 | 13 | pub type Address = u16; 14 | pub type Word = u8; 15 | 16 | pub trait State: Default { 17 | fn load_rom(&mut self, bytes: &[u8]) -> Result<(), Chip8Error>; 18 | 19 | fn clk(&self) -> Result; 20 | fn program_counter(&self) -> Address; 21 | fn delay_timer(&self) -> Word; 22 | fn sound_timer(&self) -> Result; 23 | fn memory(&mut self, addr: Address) -> Result; 24 | fn register(&self, index: Word) -> Word; 25 | fn index_register(&self) -> Address; 26 | fn key(&self, index: Word) -> bool; 27 | fn frame_buffer(&mut self, y: usize, x: usize) -> Result; 28 | 29 | fn set_frame_buffer(&mut self, y: usize, x: usize, bit: bool) -> Result<(), Chip8Error>; 30 | fn set_program_counter(&mut self, pc: Address); 31 | fn set_delay_timer(&mut self, value: Word); 32 | fn set_sound_timer(&mut self, value: Word) -> Result<(), Chip8Error>; 33 | fn set_index_register(&mut self, addr: Address); 34 | fn set_register(&mut self, index: Word, value: Word); 35 | fn set_flag_register(&mut self, flag: bool); 36 | fn set_memory(&mut self, addr: Address, value: Word) -> Result<(), Chip8Error>; 37 | fn set_key(&mut self, key: Key, kind: InputKind); 38 | 39 | fn clear_framebuffer(&mut self) -> Result<(), Chip8Error>; 40 | fn push_stack(&mut self, addr: Address); 41 | fn pop_stack(&mut self); 42 | fn increment_program_counter(&mut self); 43 | fn increment_clk(&mut self) -> Result<(), Chip8Error>; 44 | fn decrement_delay_timer(&mut self); 45 | fn decrement_sound_timer(&mut self) -> Result<(), Chip8Error>; 46 | 47 | fn clk_ptr(&self) -> Arc>; 48 | fn sound_timer_ptr(&self) -> Arc>; 49 | fn frame_buffer_ptr(&self) -> Arc>; 50 | } 51 | -------------------------------------------------------------------------------- /chip8-core/src/keypad.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::error::Chip8Error; 4 | 5 | // ╔═══╦═══╦═══╦═══╗ 6 | // ║ 1 ║ 2 ║ 3 ║ C ║ 7 | // ╠═══╬═══╬═══╬═══╣ 8 | // ║ 4 ║ 5 ║ 6 ║ D ║ 9 | // ╠═══╬═══╬═══╬═══╣ 10 | // ║ 7 ║ 8 ║ 9 ║ E ║ 11 | // ╠═══╬═══╬═══╬═══╣ 12 | // ║ A ║ 0 ║ B ║ F ║ 13 | // ╚═══╩═══╩═══╩═══╝ 14 | #[derive(Debug, Clone, Copy)] 15 | pub enum Key { 16 | Key0, 17 | Key1, 18 | Key2, 19 | Key3, 20 | Key4, 21 | Key5, 22 | Key6, 23 | Key7, 24 | Key8, 25 | Key9, 26 | KeyA, 27 | KeyB, 28 | KeyC, 29 | KeyD, 30 | KeyE, 31 | KeyF, 32 | } 33 | 34 | impl From for char { 35 | fn from(key: Key) -> Self { 36 | match key { 37 | Key::Key0 => '0', 38 | Key::Key1 => '1', 39 | Key::Key2 => '2', 40 | Key::Key3 => '3', 41 | Key::Key4 => '4', 42 | Key::Key5 => '5', 43 | Key::Key6 => '6', 44 | Key::Key7 => '7', 45 | Key::Key8 => '8', 46 | Key::Key9 => '9', 47 | Key::KeyA => 'A', 48 | Key::KeyB => 'B', 49 | Key::KeyC => 'C', 50 | Key::KeyD => 'D', 51 | Key::KeyE => 'E', 52 | Key::KeyF => 'F', 53 | } 54 | } 55 | } 56 | 57 | impl TryFrom for Key { 58 | type Error = Chip8Error; 59 | 60 | fn try_from(value: char) -> Result { 61 | match value { 62 | '0' => Ok(Self::Key0), 63 | '1' => Ok(Self::Key1), 64 | '2' => Ok(Self::Key2), 65 | '3' => Ok(Self::Key3), 66 | '4' => Ok(Self::Key4), 67 | '5' => Ok(Self::Key5), 68 | '6' => Ok(Self::Key6), 69 | '7' => Ok(Self::Key7), 70 | '8' => Ok(Self::Key8), 71 | '9' => Ok(Self::Key9), 72 | 'A' => Ok(Self::KeyA), 73 | 'B' => Ok(Self::KeyB), 74 | 'C' => Ok(Self::KeyC), 75 | 'D' => Ok(Self::KeyD), 76 | 'E' => Ok(Self::KeyE), 77 | 'F' => Ok(Self::KeyF), 78 | _ => Err(Chip8Error::InputError("Unrecognized key".to_string())), 79 | } 80 | } 81 | } 82 | 83 | impl Display for Key { 84 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 85 | let key = char::from(*self); 86 | write!(f, "{key}") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory_start/air.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::constants::{FONTSET, FONTSET_START_ADDRESS, MEMORY_SIZE, PROGRAM_START_ADDRESS}; 2 | use core::borrow::Borrow; 3 | use p3_air::{Air, AirBuilder, BaseAir}; 4 | use p3_field::Field; 5 | use p3_matrix::dense::RowMajorMatrix; 6 | use p3_matrix::Matrix; 7 | 8 | use super::columns::{MemoryStartCols, MemoryStartPreprocessedCols}; 9 | use super::MemoryStartChip; 10 | 11 | impl BaseAir for MemoryStartChip { 12 | fn width(&self) -> usize { 13 | MemoryStartCols::::num_cols() 14 | } 15 | 16 | fn preprocessed_trace(&self) -> Option> { 17 | let num_preprocessed_cols = MemoryStartPreprocessedCols::::num_cols(); 18 | 19 | let num_real_rows = MEMORY_SIZE; 20 | let num_rows = num_real_rows.next_power_of_two(); 21 | let mut trace = RowMajorMatrix::new( 22 | vec![F::zero(); num_rows * num_preprocessed_cols], 23 | num_preprocessed_cols, 24 | ); 25 | let (prefix, rows, suffix) = unsafe { 26 | trace 27 | .values 28 | .align_to_mut::>() 29 | }; 30 | assert!(prefix.is_empty(), "Alignment should match"); 31 | assert!(suffix.is_empty(), "Alignment should match"); 32 | assert_eq!(rows.len(), num_rows); 33 | 34 | for i in 0..MEMORY_SIZE { 35 | rows[i].addr = F::from_canonical_usize(i); 36 | } 37 | 38 | let start = FONTSET_START_ADDRESS as usize; 39 | let end = FONTSET_START_ADDRESS as usize + FONTSET.len(); 40 | for i in start..end { 41 | rows[i].value = F::from_canonical_u8(FONTSET[i - start]); 42 | } 43 | 44 | let start = PROGRAM_START_ADDRESS as usize; 45 | let end = PROGRAM_START_ADDRESS as usize + self.rom.len(); 46 | for i in start..end { 47 | rows[i].value = F::from_canonical_u8(self.rom[i - start]); 48 | } 49 | 50 | Some(trace) 51 | } 52 | } 53 | 54 | impl Air for MemoryStartChip { 55 | fn eval(&self, builder: &mut AB) { 56 | let main = builder.main(); 57 | let local = main.row_slice(0); 58 | let next = main.row_slice(1); 59 | let local: &MemoryStartCols = (*local).borrow(); 60 | let next: &MemoryStartCols = (*next).borrow(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /chip0-tui/src/drivers/display.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::{ 2 | constants::{DISPLAY_HEIGHT, DISPLAY_WIDTH}, 3 | drivers::DisplayDriver, 4 | error::Chip8Error, 5 | }; 6 | use ratatui::{ 7 | backend::Backend, 8 | layout::Rect, 9 | style::{Color, Stylize}, 10 | widgets::{Block, Paragraph}, 11 | Terminal, 12 | }; 13 | 14 | // TODO: Builder pattern 15 | pub struct TerminalDisplay { 16 | terminal: Terminal, 17 | refresh_rate: u64, 18 | bg_color: Color, 19 | fg_color: Color, 20 | border_color: Color, 21 | } 22 | 23 | impl TerminalDisplay { 24 | pub fn new( 25 | terminal: Terminal, 26 | refresh_rate: u64, 27 | bg_color: Color, 28 | fg_color: Color, 29 | border_color: Color, 30 | ) -> Self { 31 | Self { 32 | terminal, 33 | refresh_rate, 34 | bg_color, 35 | fg_color, 36 | border_color, 37 | } 38 | } 39 | } 40 | 41 | impl DisplayDriver for TerminalDisplay { 42 | fn frequency(&self) -> u64 { 43 | self.refresh_rate 44 | } 45 | 46 | fn draw( 47 | &mut self, 48 | frame_buffer: [[bool; DISPLAY_WIDTH]; DISPLAY_HEIGHT], 49 | cpu_freq: Option, 50 | ) -> Result<(), Chip8Error> { 51 | let frame_str = frame_buffer 52 | .iter() 53 | .map(|row| { 54 | row.iter() 55 | .map(|&pixel| if pixel { "██" } else { " " }) 56 | .collect::() 57 | }) 58 | .collect::>() 59 | .join("\n"); 60 | 61 | let block = Block::bordered() 62 | .title(format!( 63 | "CHIP-8 {}", 64 | cpu_freq.map_or("".to_string(), |f| format!("{f}Hz")), 65 | )) 66 | .fg(self.border_color); 67 | let area = Rect::new( 68 | 0, 69 | 0, 70 | 2 * DISPLAY_WIDTH as u16 + 2, 71 | DISPLAY_HEIGHT as u16 + 2, 72 | ); 73 | 74 | self.terminal 75 | .draw(|frame| { 76 | frame.render_widget( 77 | Paragraph::new(frame_str) 78 | .bg(self.bg_color) 79 | .fg(self.fg_color) 80 | .block(block), 81 | area, 82 | ); 83 | }) 84 | .map_err(|e| Chip8Error::DisplayError(e.to_string()))?; 85 | 86 | Ok(()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /chip0-core/src/chips/draw/air.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use itertools::Itertools; 3 | use p3_air::{Air, AirBuilder, BaseAir}; 4 | use p3_field::AbstractField; 5 | use p3_matrix::Matrix; 6 | 7 | use super::columns::DrawCols; 8 | use super::DrawChip; 9 | 10 | impl BaseAir for DrawChip { 11 | fn width(&self) -> usize { 12 | DrawCols::::num_cols() 13 | } 14 | } 15 | 16 | impl Air for DrawChip { 17 | fn eval(&self, builder: &mut AB) { 18 | let main = builder.main(); 19 | let local = main.row_slice(0); 20 | let next = main.row_slice(1); 21 | let local: &DrawCols = (*local).borrow(); 22 | let next: &DrawCols = (*next).borrow(); 23 | 24 | builder.assert_bool(local.is_real); 25 | // TODO: Fill diagonally for proper constraints 26 | builder.assert_bool(local.is_first); 27 | builder.assert_bool(local.is_last); 28 | 29 | builder 30 | .when(local.is_first) 31 | .assert_eq(local.xs, AB::Expr::zero()); 32 | builder 33 | .when(local.is_first) 34 | .assert_eq(local.ys, AB::Expr::zero()); 35 | builder 36 | .when(local.is_first) 37 | .assert_eq(local.register_flag, local.pixel * local.frame_buffer_y_x); 38 | 39 | // Constraint clk 40 | builder 41 | .when_transition() 42 | .when_ne(local.is_last, AB::Expr::one()) 43 | .assert_eq(next.clk, local.clk); 44 | 45 | // TODO: More constraints 46 | builder 47 | .when(local.is_real) 48 | .when_ne(local.is_last, AB::Expr::one()) 49 | .assert_eq( 50 | next.ys * AB::Expr::from_canonical_u8(8) + next.xs, 51 | local.ys * AB::Expr::from_canonical_u8(8) + local.xs + AB::Expr::one(), 52 | ); 53 | builder 54 | .when(local.is_real) 55 | .assert_eq(local.flipped, local.pixel * local.frame_buffer_y_x); 56 | builder 57 | .when_transition() 58 | .when_ne(local.is_last, AB::Expr::one()) 59 | .assert_eq( 60 | next.register_flag, 61 | local.register_flag + next.pixel * next.frame_buffer_y_x, 62 | ); 63 | 64 | builder.when(local.is_real).assert_eq( 65 | local.pixel, 66 | local 67 | .pixels_bits 68 | .into_iter() 69 | .zip_eq(local.sel_7_minus_xs) 70 | .map(|(b, sel)| b * sel) 71 | .sum::(), 72 | ); 73 | // TODO: Constrain x, y, is_first_inner 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /chip0-core/src/chips/memory/interaction.rs: -------------------------------------------------------------------------------- 1 | use p3_air::VirtualPairCol; 2 | use p3_field::Field; 3 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 4 | 5 | use super::{columns::MemoryCols, MemoryChip}; 6 | 7 | impl BaseInteractionAir for MemoryChip { 8 | fn receives_from_indices( 9 | &self, 10 | _preprocessed_indices: &[usize], 11 | main_indices: &[usize], 12 | ) -> Vec> { 13 | let col_map = MemoryCols::from_slice(main_indices); 14 | vec![Interaction { 15 | fields: vec![ 16 | VirtualPairCol::single_main(col_map.addr), 17 | VirtualPairCol::single_main(col_map.value), 18 | ], 19 | count: VirtualPairCol::single_main(col_map.is_first_read), 20 | argument_index: self.bus_memory_start, 21 | }] 22 | } 23 | 24 | fn sends_from_indices( 25 | &self, 26 | _preprocessed_indices: &[usize], 27 | main_indices: &[usize], 28 | ) -> Vec> { 29 | let col_map = MemoryCols::from_slice(main_indices); 30 | vec![ 31 | Interaction { 32 | fields: vec![ 33 | VirtualPairCol::single_main(col_map.addr), 34 | VirtualPairCol::single_main(col_map.clk), 35 | VirtualPairCol::single_main(col_map.value), 36 | ], 37 | count: VirtualPairCol::sum_main(vec![col_map.is_read, col_map.is_write]), 38 | argument_index: self.bus_memory, 39 | }, 40 | Interaction { 41 | fields: vec![VirtualPairCol::single_main(col_map.diff_limb_lo)], 42 | count: VirtualPairCol::sum_main(vec![col_map.is_read, col_map.is_write]), 43 | argument_index: self.bus_range, 44 | }, 45 | Interaction { 46 | fields: vec![VirtualPairCol::single_main(col_map.diff_limb_hi)], 47 | count: VirtualPairCol::sum_main(vec![col_map.is_read, col_map.is_write]), 48 | argument_index: self.bus_range, 49 | }, 50 | ] 51 | } 52 | } 53 | 54 | impl InteractionAir for MemoryChip { 55 | fn receives(&self) -> Vec> { 56 | let col_map = MemoryCols::::col_map(); 57 | self.receives_from_main_indices(col_map.as_slice()) 58 | } 59 | 60 | fn sends(&self) -> Vec> { 61 | let col_map = MemoryCols::::col_map(); 62 | self.sends_from_main_indices(col_map.as_slice()) 63 | } 64 | } 65 | 66 | impl Rap for MemoryChip {} 67 | -------------------------------------------------------------------------------- /chip0-core/src/machine.rs: -------------------------------------------------------------------------------- 1 | use p3_field::PrimeField32; 2 | use p3_machine::machine::Machine; 3 | use p3_uni_stark::{StarkGenericConfig, Val}; 4 | 5 | use crate::{ 6 | bus::Chip0MachineBus, 7 | chips::{ 8 | clear::ClearChip, cpu::CpuChip, draw::DrawChip, frame_buffer::FrameBufferChip, 9 | keypad::KeypadChip, memory::MemoryChip, memory_start::MemoryStartChip, range::RangeChip, 10 | Chip0MachineChip, 11 | }, 12 | }; 13 | 14 | #[derive(Default, Clone)] 15 | pub struct Chip0Machine { 16 | pub rom: Vec, 17 | } 18 | 19 | impl Chip0Machine { 20 | pub fn new(rom: Vec) -> Self { 21 | Self { rom } 22 | } 23 | } 24 | 25 | impl<'a, SC> Machine<'a, SC> for Chip0Machine 26 | where 27 | SC: StarkGenericConfig, 28 | Val: PrimeField32, 29 | { 30 | type Chip = Chip0MachineChip; 31 | type Bus = Chip0MachineBus; 32 | 33 | fn chips(&self) -> Vec { 34 | let cpu_chip = CpuChip::new( 35 | Chip0MachineBus::ClearBus as usize, 36 | Chip0MachineBus::DrawBus as usize, 37 | Chip0MachineBus::MemoryBus as usize, 38 | Chip0MachineBus::KeypadBus as usize, 39 | ); 40 | let clear_chip = ClearChip::new( 41 | Chip0MachineBus::ClearBus as usize, 42 | Chip0MachineBus::FrameBufferBus as usize, 43 | ); 44 | let draw_chip = DrawChip::new( 45 | Chip0MachineBus::DrawBus as usize, 46 | Chip0MachineBus::FrameBufferBus as usize, 47 | Chip0MachineBus::MemoryBus as usize, 48 | ); 49 | let keypad_chip = KeypadChip::new(Chip0MachineBus::KeypadBus as usize); 50 | let memory_chip = MemoryChip::new( 51 | Chip0MachineBus::MemoryStartBus as usize, 52 | Chip0MachineBus::MemoryBus as usize, 53 | Chip0MachineBus::RangeBus as usize, 54 | ); 55 | let frame_buffer_chip = FrameBufferChip::new( 56 | Chip0MachineBus::FrameBufferBus as usize, 57 | Chip0MachineBus::RangeBus as usize, 58 | ); 59 | let range_chip = RangeChip::new(Chip0MachineBus::RangeBus as usize); 60 | let memory_start_chip = 61 | MemoryStartChip::new(self.rom.clone(), Chip0MachineBus::MemoryStartBus as usize); 62 | 63 | vec![ 64 | Chip0MachineChip::Cpu(cpu_chip), 65 | Chip0MachineChip::Clear(clear_chip), 66 | Chip0MachineChip::Draw(draw_chip), 67 | Chip0MachineChip::Keypad(keypad_chip), 68 | Chip0MachineChip::Memory(memory_chip), 69 | Chip0MachineChip::FrameBuffer(frame_buffer_chip), 70 | Chip0MachineChip::Range(range_chip), 71 | Chip0MachineChip::MemoryStart(memory_start_chip), 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /chip0-core/src/chips/cpu/columns.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::constants::{NUM_KEYS, NUM_REGISTERS, STACK_DEPTH}; 2 | use p3_derive::Columnar; 3 | 4 | #[repr(C)] 5 | #[derive(Columnar, Clone, Default)] 6 | pub struct CpuCols { 7 | pub is_real: T, 8 | 9 | pub clk: T, 10 | pub opcode_hi: T, 11 | pub opcode_lo: T, 12 | 13 | pub opcode: T, 14 | 15 | pub is_clear_display: T, 16 | pub is_return: T, 17 | pub is_jump: T, 18 | pub is_call: T, 19 | pub is_skip_equal: T, 20 | pub is_skip_not_equal: T, 21 | pub is_skip_equal_xy: T, 22 | pub is_load: T, 23 | pub is_add: T, 24 | pub is_move: T, 25 | pub is_or: T, 26 | pub is_and: T, 27 | pub is_xor: T, 28 | pub is_add_xy: T, 29 | pub is_sub_xy: T, 30 | pub is_shift_right: T, 31 | pub is_sub_yx: T, 32 | pub is_shift_left: T, 33 | pub is_skip_not_equal_xy: T, 34 | pub is_load_i: T, 35 | pub is_jump_v0: T, 36 | pub is_random: T, 37 | pub is_draw: T, 38 | pub is_skip_key_pressed: T, 39 | pub is_skip_key_not_pressed: T, 40 | pub is_load_delay: T, 41 | pub is_wait_key_press: T, 42 | pub is_set_delay: T, 43 | pub is_set_sound: T, 44 | pub is_add_i: T, 45 | pub is_load_font: T, 46 | pub is_store_bcd: T, 47 | pub is_store_registers: T, 48 | pub is_load_memory: T, 49 | 50 | pub program_counter: T, 51 | pub registers: [T; NUM_REGISTERS], 52 | pub index_register: T, 53 | pub stack: [T; STACK_DEPTH], 54 | pub stack_pointer: T, 55 | pub delay_timer: T, 56 | pub sound_timer: T, 57 | pub keypad: [T; NUM_KEYS], 58 | 59 | pub stack_pointer_sel: [T; STACK_DEPTH], 60 | 61 | pub x: T, 62 | pub y: T, 63 | pub n: T, 64 | pub nn: T, 65 | pub nnn: T, 66 | 67 | pub x_sel: [T; NUM_REGISTERS], 68 | pub y_sel: [T; NUM_REGISTERS], 69 | 70 | pub vx: T, 71 | pub vy: T, 72 | 73 | pub vx_bcd0: T, 74 | pub vx_bcd1: T, 75 | pub vx_bcd2: T, 76 | 77 | pub vx_sel: [T; NUM_KEYS], 78 | 79 | // x <= i 80 | pub lte_x_sel: [T; NUM_REGISTERS], 81 | 82 | pub diff_vx_nn_inv: T, 83 | pub is_equal_vx_nn: T, 84 | 85 | pub diff_vx_vy_inv: T, 86 | pub is_equal_vx_vy: T, 87 | 88 | // pub or_vx_vy: T, 89 | // pub and_vx_vy: T, 90 | // pub xor_vx_vy: T, 91 | // pub add_vx_vy: T, 92 | // pub add_vx_vy_carry: T, 93 | // pub sub_vx_vy: T, 94 | // pub sub_vx_vy_borrow: T, 95 | // pub shr_vx: T, 96 | // pub shr_vx_flag: T, 97 | // pub sub_vy_vx: T, 98 | // pub sub_vy_vx_borrow: T, 99 | // pub shl_vx: T, 100 | // pub shl_vx_flag: T, 101 | // pub add_vi_vx: T, 102 | // TODO: start and end 103 | pub is_first: T, 104 | pub is_final: T, 105 | } 106 | -------------------------------------------------------------------------------- /chip8-core/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const NUM_REGISTERS: usize = 16; 2 | pub const MEMORY_SIZE: usize = 4096; 3 | pub const STACK_DEPTH: usize = 16; 4 | pub const OPCODE_SIZE: u16 = 2; 5 | 6 | pub const FLAG_REGISTER: usize = 0xF; 7 | 8 | pub const FONTSET_START_ADDRESS: u16 = 0x0; 9 | pub const PROGRAM_START_ADDRESS: u16 = 0x200; 10 | 11 | pub const NUM_KEYS: usize = 16; 12 | 13 | pub const DISPLAY_WIDTH: usize = 64; 14 | pub const DISPLAY_HEIGHT: usize = 32; 15 | 16 | pub const FONT_SIZE: usize = 5; 17 | const NUM_FONTS: usize = 16; 18 | pub const FONTSET: [u8; NUM_FONTS * FONT_SIZE] = [ 19 | 0b11110000, // ████ 20 | 0b10010000, // █ █ 21 | 0b10010000, // █ █ 22 | 0b10010000, // █ █ 23 | 0b11110000, // ████ 24 | // 25 | 0b00100000, // █ 26 | 0b01100000, // ██ 27 | 0b00100000, // █ 28 | 0b00100000, // █ 29 | 0b01110000, // ███ 30 | // 31 | 0b11110000, // ████ 32 | 0b00010000, // █ 33 | 0b11110000, // ████ 34 | 0b10000000, // █ 35 | 0b11110000, // ████ 36 | // 37 | 0b11110000, // ████ 38 | 0b00010000, // █ 39 | 0b11110000, // ████ 40 | 0b00010000, // █ 41 | 0b11110000, // ████ 42 | // 43 | 0b10010000, // █ █ 44 | 0b10010000, // █ █ 45 | 0b11110000, // ████ 46 | 0b00010000, // █ 47 | 0b00010000, // █ 48 | // 49 | 0b11110000, // ████ 50 | 0b10000000, // █ 51 | 0b11110000, // ████ 52 | 0b00010000, // █ 53 | 0b11110000, // ████ 54 | // 55 | 0b11110000, // ████ 56 | 0b10000000, // █ 57 | 0b11110000, // ████ 58 | 0b10010000, // █ █ 59 | 0b11110000, // ████ 60 | // 61 | 0b11110000, // ████ 62 | 0b00010000, // █ 63 | 0b00100000, // █ 64 | 0b01000000, // █ 65 | 0b01000000, // █ 66 | // 67 | 0b11110000, // ████ 68 | 0b10010000, // █ █ 69 | 0b11110000, // ████ 70 | 0b10010000, // █ █ 71 | 0b11110000, // ████ 72 | // 73 | 0b11110000, // ████ 74 | 0b10010000, // █ █ 75 | 0b11110000, // ████ 76 | 0b00010000, // █ 77 | 0b11110000, // ████ 78 | // 79 | 0b11110000, // ████ 80 | 0b10010000, // █ █ 81 | 0b11110000, // ████ 82 | 0b10010000, // █ █ 83 | 0b10010000, // █ █ 84 | // 85 | 0b11100000, // ███ 86 | 0b10010000, // █ █ 87 | 0b11100000, // ███ 88 | 0b10010000, // █ █ 89 | 0b11100000, // ███ 90 | // 91 | 0b11110000, // ████ 92 | 0b10000000, // █ 93 | 0b10000000, // █ 94 | 0b10000000, // █ 95 | 0b11110000, // ████ 96 | // 97 | 0b11100000, // ███ 98 | 0b10010000, // █ █ 99 | 0b10010000, // █ █ 100 | 0b10010000, // █ █ 101 | 0b11100000, // ███ 102 | // 103 | 0b11110000, // ████ 104 | 0b10000000, // █ 105 | 0b11110000, // ████ 106 | 0b10000000, // █ 107 | 0b11110000, // ████ 108 | // 109 | 0b11110000, // ████ 110 | 0b10000000, // █ 111 | 0b11110000, // ████ 112 | 0b10000000, // █ 113 | 0b10000000, // █ 114 | ]; 115 | 116 | pub const NUM_OPCODES: usize = 34; 117 | 118 | pub const TICKS_PER_TIMER: u64 = 8; 119 | -------------------------------------------------------------------------------- /chip8-core/src/chip8.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | sync::{Arc, RwLock}, 4 | }; 5 | 6 | use super::{ 7 | cpu::Cpu, 8 | drivers::{AudioDriver, DisplayDriver, InputDriver}, 9 | error::Chip8Error, 10 | input::InputEvent, 11 | rwlock::CheckedRead, 12 | state::State, 13 | }; 14 | 15 | pub struct Chip8 16 | where 17 | C: Cpu, 18 | { 19 | cpu: C, 20 | input_queue: Arc>>, 21 | } 22 | 23 | impl Chip8 24 | where 25 | C: Cpu, 26 | { 27 | pub fn new(cpu: C, inputs: Vec<(u64, InputEvent)>) -> Self { 28 | Self { 29 | cpu, 30 | input_queue: Arc::new(RwLock::new(VecDeque::from(inputs))), 31 | } 32 | } 33 | 34 | pub fn load(&mut self, bytes: &[u8]) -> Result<(), Chip8Error> { 35 | self.cpu.state().load_rom(bytes) 36 | } 37 | 38 | pub async fn run( 39 | &mut self, 40 | num_cycles: Option, 41 | mut input: impl InputDriver + 'static, 42 | display: Option, 43 | audio: Option, 44 | ) -> Result<(), Chip8Error> { 45 | // Status flag to check if machine is still running 46 | let status = Arc::new(RwLock::new(Ok(()))); 47 | 48 | // Input loop 49 | let input_handle = { 50 | let status = status.clone(); 51 | let queue = self.input_queue.clone(); 52 | let clk = self.cpu.state().clk_ptr(); 53 | 54 | tokio::spawn(async move { input.run(status, queue, clk) }) 55 | }; 56 | // Render loop 57 | let display_handle = { 58 | display.map(|mut display| { 59 | let status = status.clone(); 60 | let frame_buffer = self.cpu.state().frame_buffer_ptr(); 61 | let clk = self.cpu.state().clk_ptr(); 62 | 63 | tokio::spawn(async move { display.run(status, frame_buffer, clk) }) 64 | }) 65 | }; 66 | // Audio loop 67 | let audio_handle = { 68 | audio.map(|mut audio| { 69 | let status = status.clone(); 70 | let sound_timer = self.cpu.state().sound_timer_ptr(); 71 | 72 | tokio::spawn(async move { audio.run(status, sound_timer) }) 73 | }) 74 | }; 75 | 76 | // CPU loop 77 | self.cpu 78 | .run(num_cycles, status.clone(), self.input_queue.clone()) 79 | .await; 80 | 81 | // Wait for all threads 82 | input_handle 83 | .await 84 | .map_err(|e| Chip8Error::AsyncAwaitError(e.to_string()))?; 85 | if let Some(display_handle) = display_handle { 86 | display_handle 87 | .await 88 | .map_err(|e| Chip8Error::AsyncAwaitError(e.to_string()))?; 89 | } 90 | if let Some(audio_handle) = audio_handle { 91 | audio_handle 92 | .await 93 | .map_err(|e| Chip8Error::AsyncAwaitError(e.to_string()))?; 94 | } 95 | 96 | let res = status.checked_read()?; 97 | res.clone() 98 | } 99 | 100 | pub async fn load_and_run( 101 | &mut self, 102 | rom: &[u8], 103 | num_cycles: Option, 104 | input: impl InputDriver + 'static, 105 | display: Option, 106 | audio: Option, 107 | ) -> Result<(), Chip8Error> { 108 | self.load(rom)?; 109 | self.run(num_cycles, input, display, audio).await 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /chip0-tui/src/drivers/input.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::{ 2 | drivers::InputDriver, 3 | error::Chip8Error, 4 | input::{InputEvent, InputKind}, 5 | keypad::Key, 6 | }; 7 | use crossterm::event::{poll, read, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; 8 | use csv::Writer; 9 | use serde::{Deserialize, Serialize}; 10 | use std::{io::Write, time::Duration}; 11 | 12 | const FREQUENCY: u64 = 120; 13 | 14 | fn keymap(c: char) -> Option { 15 | match c { 16 | '1' => Some(Key::Key1), 17 | '2' => Some(Key::Key2), 18 | '3' => Some(Key::Key3), 19 | '4' => Some(Key::KeyC), 20 | 'Q' => Some(Key::Key4), 21 | 'W' => Some(Key::Key5), 22 | 'E' => Some(Key::Key6), 23 | 'R' => Some(Key::KeyD), 24 | 'A' => Some(Key::Key7), 25 | 'S' => Some(Key::Key8), 26 | 'D' => Some(Key::Key9), 27 | 'F' => Some(Key::KeyA), 28 | 'Z' => Some(Key::KeyA), 29 | 'X' => Some(Key::Key0), 30 | 'C' => Some(Key::KeyB), 31 | 'V' => Some(Key::KeyF), 32 | _ => None, 33 | } 34 | } 35 | 36 | #[derive(Serialize, Deserialize)] 37 | pub struct CsvRecord { 38 | pub clk: u64, 39 | pub key: char, 40 | pub kind: u8, 41 | } 42 | 43 | #[derive(Default)] 44 | pub struct TerminalKeyboardInput { 45 | writer: Option>, 46 | } 47 | 48 | impl TerminalKeyboardInput { 49 | pub fn new(writer: Option>) -> Self { 50 | Self { writer } 51 | } 52 | } 53 | 54 | impl InputDriver for TerminalKeyboardInput { 55 | fn frequency(&self) -> u64 { 56 | FREQUENCY 57 | } 58 | 59 | fn log_input(&mut self, clk: u64, input: InputEvent) -> Result<(), Chip8Error> { 60 | if let Some(writer) = &mut self.writer { 61 | let record = CsvRecord { 62 | clk, 63 | key: char::from(input.key), 64 | kind: input.kind as u8, 65 | }; 66 | writer 67 | .serialize(record) 68 | .map_err(|e| Chip8Error::InputError(e.to_string())) 69 | } else { 70 | Ok(()) 71 | } 72 | } 73 | 74 | fn poll(&mut self) -> Result, Chip8Error> { 75 | let mut event = None; 76 | if poll(Duration::from_secs_f64(1.0 / self.frequency() as f64)) 77 | .map_err(|e| Chip8Error::InputError(e.to_string()))? 78 | { 79 | event = Some(read().map_err(|e| Chip8Error::InputError(e.to_string()))?); 80 | } 81 | 82 | if let Some(Event::Key(KeyEvent { 83 | code, 84 | kind, 85 | modifiers, 86 | .. 87 | })) = event 88 | { 89 | match (modifiers, code) { 90 | (KeyModifiers::CONTROL, KeyCode::Char('c')) => return Err(Chip8Error::Interrupt), 91 | (_, KeyCode::Esc) => return Err(Chip8Error::Interrupt), 92 | (_, KeyCode::Char(c)) => { 93 | let kind = match kind { 94 | KeyEventKind::Press => Some(InputKind::Press), 95 | KeyEventKind::Release => Some(InputKind::Release), 96 | _ => None, 97 | }; 98 | 99 | if let Some(kind) = kind { 100 | if let Some(key) = keymap(c.to_ascii_uppercase()) { 101 | let event = InputEvent { key, kind }; 102 | return Ok(Some(event)); 103 | } 104 | } 105 | } 106 | _ => return Ok(None), 107 | } 108 | } 109 | 110 | Ok(None) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /chip0-tui/src/main.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod drivers; 3 | mod terminal; 4 | 5 | use args::CmdArgs; 6 | use chip0_core::{config::MyConfig, cpu::StarkCpu, prover::DefaultProver}; 7 | use chip8_core::{ 8 | input::{InputEvent, InputKind}, 9 | keypad::Key, 10 | Chip8, 11 | }; 12 | use clap::Parser; 13 | use csv::{Reader, Writer, WriterBuilder}; 14 | use drivers::input::CsvRecord; 15 | use eyre::Result; 16 | use rand::{random, rngs::StdRng, SeedableRng}; 17 | use std::fs::{self, OpenOptions}; 18 | use terminal::{restore_terminal, setup_terminal}; 19 | use tracing_forest::{util::LevelFilter, ForestLayer}; 20 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry}; 21 | 22 | use crate::drivers::{ 23 | audio::TerminalAudio, display::TerminalDisplay, input::TerminalKeyboardInput, 24 | }; 25 | 26 | #[tokio::main] 27 | async fn main() -> Result<()> { 28 | let args = CmdArgs::parse(); 29 | 30 | let rom = fs::read(args.rom)?; 31 | 32 | let env_filter = EnvFilter::builder() 33 | .with_default_directive(LevelFilter::INFO.into()) 34 | .from_env_lossy(); 35 | 36 | Registry::default() 37 | .with(env_filter) 38 | .with(ForestLayer::default()) 39 | .init(); 40 | 41 | let terminal = setup_terminal(args.headless)?; 42 | 43 | let (inputs, input_writer) = if let Some(input_file) = &args.input_file { 44 | if !input_file.exists() || args.overwrite { 45 | let writer = Writer::from_path(input_file)?; 46 | (vec![], Some(writer)) 47 | } else { 48 | let mut reader = Reader::from_path(input_file)?; 49 | let parsed: Vec<(u64, InputEvent)> = reader 50 | .deserialize() 51 | .map(|result| { 52 | let record: CsvRecord = result?; 53 | let key = Key::try_from(record.key)?; 54 | let kind = InputKind::try_from(record.kind)?; 55 | Ok((record.clk, InputEvent { key, kind })) 56 | }) 57 | .collect::>()?; 58 | let f = OpenOptions::new() 59 | .create(true) 60 | .append(true) 61 | .open(input_file)?; 62 | let writer = WriterBuilder::new() 63 | .has_headers(parsed.is_empty()) 64 | .from_writer(f); 65 | (parsed, Some(writer)) 66 | } 67 | } else { 68 | (vec![], None) 69 | }; 70 | 71 | let input_driver = TerminalKeyboardInput::new(input_writer); 72 | let display_driver = { 73 | if !args.headless { 74 | Some(TerminalDisplay::new( 75 | terminal, 76 | args.refresh_rate, 77 | args.bg_color, 78 | args.fg_color, 79 | args.border_color, 80 | )) 81 | } else { 82 | None 83 | } 84 | }; 85 | let audio_driver = { 86 | if !args.headless { 87 | Some(TerminalAudio::default()) 88 | } else { 89 | None 90 | } 91 | }; 92 | 93 | let seeded_rng = StdRng::seed_from_u64(args.random_seed.unwrap_or(random())); 94 | let prover = DefaultProver::new(rom.clone()); 95 | let cpu: StarkCpu<_, MyConfig, _> = StarkCpu::new(args.clk_freq, seeded_rng, prover); 96 | let mut chip8 = Chip8::new(cpu, inputs); 97 | let res = chip8 98 | .load_and_run( 99 | rom.as_slice(), 100 | args.num_cycles, 101 | input_driver, 102 | display_driver, 103 | audio_driver, 104 | ) 105 | .await; 106 | 107 | restore_terminal(args.headless)?; 108 | res?; 109 | 110 | Ok(()) 111 | } 112 | -------------------------------------------------------------------------------- /chip0-core/src/chips/draw/interaction.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::constants::DISPLAY_WIDTH; 2 | use p3_air::VirtualPairCol; 3 | use p3_field::Field; 4 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 5 | 6 | use super::{columns::DrawCols, DrawChip}; 7 | 8 | impl BaseInteractionAir for DrawChip { 9 | fn receives_from_indices( 10 | &self, 11 | _preprocessed_indices: &[usize], 12 | main_indices: &[usize], 13 | ) -> Vec> { 14 | let col_map = DrawCols::from_slice(main_indices); 15 | vec![ 16 | Interaction { 17 | fields: vec![ 18 | VirtualPairCol::single_main(col_map.clk), 19 | VirtualPairCol::single_main(col_map.index_register), 20 | VirtualPairCol::single_main(col_map.register_x), 21 | VirtualPairCol::single_main(col_map.register_y), 22 | ], 23 | count: VirtualPairCol::single_main(col_map.is_first), 24 | argument_index: self.bus_draw, 25 | }, 26 | // Read frame_buffer[y][x] to register 27 | Interaction { 28 | fields: vec![ 29 | VirtualPairCol::new_main( 30 | vec![ 31 | (col_map.y, F::from_canonical_usize(DISPLAY_WIDTH)), 32 | (col_map.x, F::one()), 33 | ], 34 | F::zero(), 35 | ), 36 | VirtualPairCol::single_main(col_map.clk), 37 | VirtualPairCol::single_main(col_map.frame_buffer_y_x), 38 | ], 39 | count: VirtualPairCol::single_main(col_map.is_real), 40 | argument_index: self.bus_frame_buffer, 41 | }, 42 | // Flip frame_buffer[y][x] if pixel is set 43 | Interaction { 44 | fields: vec![ 45 | VirtualPairCol::new_main( 46 | vec![ 47 | (col_map.y, F::from_canonical_usize(DISPLAY_WIDTH)), 48 | (col_map.x, F::one()), 49 | ], 50 | F::zero(), 51 | ), 52 | VirtualPairCol::single_main(col_map.clk), 53 | VirtualPairCol::new_main(vec![(col_map.frame_buffer_y_x, -F::one())], F::one()), 54 | ], 55 | count: VirtualPairCol::single_main(col_map.pixel), 56 | argument_index: self.bus_frame_buffer, 57 | }, 58 | Interaction { 59 | fields: vec![ 60 | VirtualPairCol::sum_main(vec![col_map.index_register, col_map.ys]), 61 | VirtualPairCol::single_main(col_map.clk), 62 | VirtualPairCol::single_main(col_map.pixels), 63 | ], 64 | count: VirtualPairCol::single_main(col_map.is_first_inner), 65 | argument_index: self.bus_memory, 66 | }, 67 | ] 68 | } 69 | 70 | fn sends_from_indices( 71 | &self, 72 | _preprocessed_indices: &[usize], 73 | main_indices: &[usize], 74 | ) -> Vec> { 75 | let col_map = DrawCols::from_slice(main_indices); 76 | vec![Interaction { 77 | fields: vec![ 78 | VirtualPairCol::single_main(col_map.clk), 79 | VirtualPairCol::single_main(col_map.register_flag), 80 | ], 81 | count: VirtualPairCol::single_main(col_map.is_last), 82 | argument_index: self.bus_draw, 83 | }] 84 | } 85 | } 86 | 87 | impl InteractionAir for DrawChip { 88 | fn receives(&self) -> Vec> { 89 | let col_map = DrawCols::::col_map(); 90 | self.receives_from_main_indices(col_map.as_slice()) 91 | } 92 | 93 | fn sends(&self) -> Vec> { 94 | let col_map = DrawCols::::col_map(); 95 | self.sends_from_main_indices(col_map.as_slice()) 96 | } 97 | } 98 | 99 | impl Rap for DrawChip {} 100 | -------------------------------------------------------------------------------- /chip0-core/src/chips/cpu/interaction.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::constants::{FLAG_REGISTER, NUM_KEYS, NUM_REGISTERS}; 2 | use p3_air::VirtualPairCol; 3 | use p3_field::Field; 4 | use p3_interaction::{BaseInteractionAir, Interaction, InteractionAir, InteractionAirBuilder, Rap}; 5 | 6 | use crate::chips::cpu::columns::CpuCols; 7 | 8 | use super::CpuChip; 9 | 10 | impl BaseInteractionAir for CpuChip { 11 | fn receives_from_indices( 12 | &self, 13 | _preprocessed_indices: &[usize], 14 | main_indices: &[usize], 15 | ) -> Vec> { 16 | let col_map = CpuCols::from_slice(main_indices); 17 | let mut interactions = vec![ 18 | Interaction { 19 | fields: vec![ 20 | VirtualPairCol::single_main(col_map.clk), 21 | VirtualPairCol::single_main(col_map.registers[FLAG_REGISTER]), 22 | ], 23 | count: VirtualPairCol::single_main(col_map.is_draw), 24 | argument_index: self.bus_draw, 25 | }, 26 | Interaction { 27 | fields: vec![ 28 | VirtualPairCol::single_main(col_map.program_counter), 29 | VirtualPairCol::single_main(col_map.clk), 30 | VirtualPairCol::single_main(col_map.opcode_hi), 31 | ], 32 | count: VirtualPairCol::single_main(col_map.is_real), 33 | argument_index: self.bus_memory, 34 | }, 35 | Interaction { 36 | fields: vec![ 37 | VirtualPairCol::new_main(vec![(col_map.program_counter, F::one())], F::one()), 38 | VirtualPairCol::single_main(col_map.clk), 39 | VirtualPairCol::single_main(col_map.opcode_lo), 40 | ], 41 | count: VirtualPairCol::single_main(col_map.is_real), 42 | argument_index: self.bus_memory, 43 | }, 44 | Interaction { 45 | fields: vec![ 46 | VirtualPairCol::single_main(col_map.index_register), 47 | VirtualPairCol::single_main(col_map.clk), 48 | VirtualPairCol::single_main(col_map.vx_bcd0), 49 | ], 50 | count: VirtualPairCol::single_main(col_map.is_store_bcd), 51 | argument_index: self.bus_memory, 52 | }, 53 | Interaction { 54 | fields: vec![ 55 | VirtualPairCol::new_main(vec![(col_map.index_register, F::one())], F::one()), 56 | VirtualPairCol::single_main(col_map.clk), 57 | VirtualPairCol::single_main(col_map.vx_bcd1), 58 | ], 59 | count: VirtualPairCol::single_main(col_map.is_store_bcd), 60 | argument_index: self.bus_memory, 61 | }, 62 | Interaction { 63 | fields: vec![ 64 | VirtualPairCol::new_main(vec![(col_map.index_register, F::one())], F::two()), 65 | VirtualPairCol::single_main(col_map.clk), 66 | VirtualPairCol::single_main(col_map.vx_bcd2), 67 | ], 68 | count: VirtualPairCol::single_main(col_map.is_store_bcd), 69 | argument_index: self.bus_memory, 70 | }, 71 | ]; 72 | 73 | interactions.extend((0..NUM_REGISTERS).map(|i| Interaction { 74 | fields: vec![ 75 | VirtualPairCol::new_main( 76 | vec![(col_map.index_register, F::one())], 77 | F::from_canonical_usize(i), 78 | ), 79 | VirtualPairCol::single_main(col_map.clk), 80 | VirtualPairCol::single_main(col_map.registers[i]), 81 | ], 82 | // TODO: load/store registers 83 | count: VirtualPairCol::single_main(col_map.lte_x_sel[i]), 84 | argument_index: self.bus_memory, 85 | })); 86 | 87 | // TODO: keypad interactions 88 | // interactions.extend((0..NUM_KEYS).map(|i| Interaction { 89 | // fields: vec![ 90 | // VirtualPairCol::single_main(col_map.clk), 91 | // VirtualPairCol::constant(F::from_canonical_usize(i)), 92 | // VirtualPairCol::single_main(col_map.keypad[i]), 93 | // ], 94 | // count: VirtualPairCol::single_main(col_map.keypad[i]), 95 | // argument_index: self.bus_memory, 96 | // })); 97 | 98 | interactions 99 | } 100 | 101 | fn sends_from_indices( 102 | &self, 103 | _preprocessed_indices: &[usize], 104 | main_indices: &[usize], 105 | ) -> Vec> { 106 | let col_map = CpuCols::from_slice(main_indices); 107 | vec![ 108 | Interaction { 109 | fields: vec![ 110 | VirtualPairCol::single_main(col_map.clk), 111 | VirtualPairCol::single_main(col_map.index_register), 112 | VirtualPairCol::single_main(col_map.vx), 113 | VirtualPairCol::single_main(col_map.vy), 114 | ], 115 | count: VirtualPairCol::single_main(col_map.is_draw), 116 | argument_index: self.bus_draw, 117 | }, 118 | Interaction { 119 | fields: vec![VirtualPairCol::single_main(col_map.clk)], 120 | count: VirtualPairCol::single_main(col_map.is_clear_display), 121 | argument_index: self.bus_clear, 122 | }, 123 | ] 124 | } 125 | } 126 | 127 | impl InteractionAir for CpuChip { 128 | fn receives(&self) -> Vec> { 129 | let col_map = CpuCols::::col_map(); 130 | self.receives_from_main_indices(col_map.as_slice()) 131 | } 132 | 133 | fn sends(&self) -> Vec> { 134 | let col_map = CpuCols::::col_map(); 135 | self.sends_from_main_indices(col_map.as_slice()) 136 | } 137 | } 138 | 139 | impl Rap for CpuChip {} 140 | -------------------------------------------------------------------------------- /chip8-core/src/state/simple.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use super::{Address, State, Word}; 4 | use crate::{ 5 | constants::{ 6 | DISPLAY_HEIGHT, DISPLAY_WIDTH, FLAG_REGISTER, FONTSET, FONTSET_START_ADDRESS, MEMORY_SIZE, 7 | NUM_KEYS, NUM_REGISTERS, OPCODE_SIZE, PROGRAM_START_ADDRESS, STACK_DEPTH, 8 | }, 9 | error::Chip8Error, 10 | input::InputKind, 11 | keypad::Key, 12 | rwlock::{CheckedRead, CheckedWrite}, 13 | }; 14 | 15 | // TODO: Compare performance with atomics, channels instead of locks 16 | pub struct SimpleState { 17 | /// Cycle counter to keep track of the number of CPU cycles executed. 18 | // TODO: Make private 19 | pub clk: Arc>, 20 | pub registers: [Word; NUM_REGISTERS], 21 | pub memory: [Word; MEMORY_SIZE], 22 | pub index_register: Address, 23 | pub program_counter: Address, 24 | pub stack: [Address; STACK_DEPTH], 25 | pub stack_pointer: Word, 26 | pub delay_timer: Word, 27 | pub sound_timer: Arc>, 28 | pub keypad: [bool; NUM_KEYS], 29 | pub frame_buffer: Arc>, 30 | } 31 | 32 | impl Default for SimpleState { 33 | fn default() -> Self { 34 | let mut memory = [0; MEMORY_SIZE]; 35 | let start = FONTSET_START_ADDRESS as usize; 36 | let end = FONTSET_START_ADDRESS as usize + FONTSET.len(); 37 | memory[start..end].copy_from_slice(FONTSET.as_slice()); 38 | 39 | Self { 40 | clk: Arc::new(RwLock::new(0)), 41 | registers: [0; NUM_REGISTERS], 42 | memory, 43 | index_register: 0, 44 | program_counter: PROGRAM_START_ADDRESS, 45 | stack: [0; STACK_DEPTH], 46 | stack_pointer: 0, 47 | delay_timer: 0, 48 | sound_timer: Arc::new(RwLock::new(0)), 49 | keypad: [false; NUM_KEYS], 50 | frame_buffer: Arc::new(RwLock::new([[false; DISPLAY_WIDTH]; DISPLAY_HEIGHT])), 51 | } 52 | } 53 | } 54 | 55 | impl State for SimpleState { 56 | fn load_rom(&mut self, bytes: &[u8]) -> Result<(), Chip8Error> { 57 | let start = PROGRAM_START_ADDRESS as usize; 58 | let end = PROGRAM_START_ADDRESS as usize + bytes.len(); 59 | 60 | if end > MEMORY_SIZE { 61 | Err(Chip8Error::RomTooBig(bytes.len())) 62 | } else { 63 | self.memory[start..end].copy_from_slice(bytes); 64 | Ok(()) 65 | } 66 | } 67 | 68 | fn clk(&self) -> Result { 69 | let clk = *self.clk.checked_read()?; 70 | Ok(clk) 71 | } 72 | 73 | fn clk_ptr(&self) -> Arc> { 74 | self.clk.clone() 75 | } 76 | 77 | fn sound_timer_ptr(&self) -> Arc> { 78 | self.sound_timer.clone() 79 | } 80 | 81 | fn frame_buffer_ptr(&self) -> Arc> { 82 | self.frame_buffer.clone() 83 | } 84 | 85 | fn program_counter(&self) -> Address { 86 | self.program_counter 87 | } 88 | 89 | fn delay_timer(&self) -> Word { 90 | self.delay_timer 91 | } 92 | 93 | fn sound_timer(&self) -> Result { 94 | let st = *self.sound_timer.checked_read()?; 95 | Ok(st) 96 | } 97 | 98 | fn memory(&mut self, addr: Address) -> Result { 99 | if (addr as usize) < MEMORY_SIZE { 100 | Ok(self.memory[addr as usize]) 101 | } else { 102 | Err(Chip8Error::MemoryAccessOutOfBounds(addr)) 103 | } 104 | } 105 | 106 | fn register(&self, index: Word) -> Word { 107 | self.registers[index as usize] 108 | } 109 | 110 | fn index_register(&self) -> Address { 111 | self.index_register 112 | } 113 | 114 | fn key(&self, index: Word) -> bool { 115 | self.keypad[index as usize] 116 | } 117 | 118 | fn frame_buffer(&mut self, y: usize, x: usize) -> Result { 119 | let fb = ((*self.frame_buffer).checked_read()?)[y][x]; 120 | Ok(fb) 121 | } 122 | 123 | fn set_frame_buffer(&mut self, y: usize, x: usize, bit: bool) -> Result<(), Chip8Error> { 124 | ((*self.frame_buffer).checked_write()?)[y][x] = bit; 125 | Ok(()) 126 | } 127 | 128 | fn set_program_counter(&mut self, pc: Address) { 129 | self.program_counter = pc; 130 | } 131 | 132 | fn set_delay_timer(&mut self, value: Word) { 133 | self.delay_timer = value; 134 | } 135 | 136 | fn set_sound_timer(&mut self, value: Word) -> Result<(), Chip8Error> { 137 | *self.sound_timer.checked_write()? = value; 138 | Ok(()) 139 | } 140 | 141 | fn set_index_register(&mut self, addr: Address) { 142 | self.index_register = addr; 143 | } 144 | 145 | fn set_register(&mut self, index: Word, value: Word) { 146 | self.registers[index as usize] = value; 147 | } 148 | 149 | fn set_flag_register(&mut self, flag: bool) { 150 | self.registers[FLAG_REGISTER] = flag as Word; 151 | } 152 | 153 | fn set_memory(&mut self, addr: Address, value: Word) -> Result<(), Chip8Error> { 154 | if (addr as usize) < MEMORY_SIZE { 155 | self.memory[addr as usize] = value; 156 | Ok(()) 157 | } else { 158 | Err(Chip8Error::MemoryAccessOutOfBounds(addr)) 159 | } 160 | } 161 | 162 | fn set_key(&mut self, key: Key, kind: InputKind) { 163 | self.keypad[key as usize] = kind == InputKind::Press; 164 | } 165 | 166 | fn clear_framebuffer(&mut self) -> Result<(), Chip8Error> { 167 | *self.frame_buffer.checked_write()? = [[false; DISPLAY_WIDTH]; DISPLAY_HEIGHT]; 168 | Ok(()) 169 | } 170 | 171 | fn push_stack(&mut self, addr: Address) { 172 | self.stack[self.stack_pointer as usize] = self.program_counter; 173 | self.stack_pointer += 1; 174 | self.program_counter = addr; 175 | } 176 | 177 | fn pop_stack(&mut self) { 178 | self.stack_pointer -= 1; 179 | self.program_counter = self.stack[self.stack_pointer as usize]; 180 | } 181 | 182 | fn increment_program_counter(&mut self) { 183 | self.program_counter += OPCODE_SIZE; 184 | } 185 | 186 | fn increment_clk(&mut self) -> Result<(), Chip8Error> { 187 | *self.clk.checked_write()? += 1; 188 | Ok(()) 189 | } 190 | 191 | fn decrement_delay_timer(&mut self) { 192 | self.delay_timer -= 1; 193 | } 194 | 195 | fn decrement_sound_timer(&mut self) -> Result<(), Chip8Error> { 196 | *self.sound_timer.checked_write()? -= 1; 197 | Ok(()) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /chip0-core/src/chips/cpu/air.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::constants::{NUM_KEYS, NUM_OPCODES, NUM_REGISTERS, OPCODE_SIZE}; 2 | use core::borrow::Borrow; 3 | use itertools::Itertools; 4 | use p3_air::{Air, AirBuilder, BaseAir}; 5 | use p3_air_util::builders::SubAirBuilder; 6 | use p3_field::AbstractField; 7 | use p3_matrix::Matrix; 8 | 9 | use crate::airs::counter::CounterAir; 10 | use crate::airs::is_equal::IsEqualAir; 11 | use crate::airs::selector::SelectorAir; 12 | 13 | use super::columns::CpuCols; 14 | use super::CpuChip; 15 | 16 | impl BaseAir for CpuChip { 17 | fn width(&self) -> usize { 18 | CpuCols::::num_cols() 19 | } 20 | } 21 | 22 | impl Air for CpuChip { 23 | fn eval(&self, builder: &mut AB) { 24 | let main = builder.main(); 25 | let local = main.row_slice(0); 26 | let next = main.row_slice(1); 27 | let local: &CpuCols = (*local).borrow(); 28 | let next: &CpuCols = (*next).borrow(); 29 | 30 | let col_map = CpuCols::::col_map(); 31 | 32 | // is_real is boolean 33 | builder.assert_bool(local.is_real); 34 | 35 | // clk 36 | // TODO: See if can avoid is_real 37 | let counter = CounterAir; 38 | let mut builder_when_next_is_real = builder.when(next.is_real); 39 | let mut clk_builder = 40 | SubAirBuilder::new_main(&mut builder_when_next_is_real, vec![col_map.clk]); 41 | counter.eval(&mut clk_builder); 42 | 43 | // Opcode selectors 44 | let selector = SelectorAir::; 45 | let mut builder_when_local_is_real = builder.when(local.is_real); 46 | let mut selector_builder = SubAirBuilder::new_main( 47 | &mut builder_when_local_is_real, 48 | vec![ 49 | col_map.is_clear_display, 50 | col_map.is_return, 51 | col_map.is_jump, 52 | col_map.is_call, 53 | col_map.is_skip_equal, 54 | col_map.is_skip_not_equal, 55 | col_map.is_skip_equal_xy, 56 | col_map.is_load, 57 | col_map.is_add, 58 | col_map.is_move, 59 | col_map.is_or, 60 | col_map.is_and, 61 | col_map.is_xor, 62 | col_map.is_add_xy, 63 | col_map.is_sub_xy, 64 | col_map.is_shift_right, 65 | col_map.is_sub_yx, 66 | col_map.is_shift_left, 67 | col_map.is_skip_not_equal_xy, 68 | col_map.is_load_i, 69 | col_map.is_jump_v0, 70 | col_map.is_random, 71 | col_map.is_draw, 72 | col_map.is_skip_key_pressed, 73 | col_map.is_skip_key_not_pressed, 74 | col_map.is_load_delay, 75 | col_map.is_wait_key_press, 76 | col_map.is_set_delay, 77 | col_map.is_set_sound, 78 | col_map.is_add_i, 79 | col_map.is_load_font, 80 | col_map.is_store_bcd, 81 | col_map.is_store_registers, 82 | col_map.is_load_memory, 83 | ], 84 | ); 85 | selector.eval(&mut selector_builder); 86 | 87 | // register selectors 88 | for i in 0..NUM_REGISTERS { 89 | builder.assert_bool(local.x_sel[i]); 90 | builder.assert_bool(local.y_sel[i]); 91 | // TODO: Add more constraints 92 | builder.assert_bool(local.lte_x_sel[i]); 93 | } 94 | builder 95 | .when(local.is_real) 96 | .assert_one(local.x_sel.into_iter().map(|x| x.into()).sum::()); 97 | builder 98 | .when(local.is_real) 99 | .assert_one(local.y_sel.into_iter().map(|x| x.into()).sum::()); 100 | builder.when(local.is_real).assert_eq( 101 | local.x, 102 | local 103 | .x_sel 104 | .into_iter() 105 | .enumerate() 106 | .map(|(i, x)| AB::Expr::from_canonical_usize(i) * x) 107 | .sum::(), 108 | ); 109 | builder.when(local.is_real).assert_eq( 110 | local.y, 111 | local 112 | .y_sel 113 | .into_iter() 114 | .enumerate() 115 | .map(|(i, y)| AB::Expr::from_canonical_usize(i) * y) 116 | .sum::(), 117 | ); 118 | 119 | builder.assert_eq( 120 | local.vx, 121 | local 122 | .x_sel 123 | .iter() 124 | .zip_eq(local.registers.iter()) 125 | .map(|(&sel, ®ister)| sel * register) 126 | .sum::(), 127 | ); 128 | builder.assert_eq( 129 | local.vy, 130 | local 131 | .y_sel 132 | .iter() 133 | .zip_eq(local.registers.iter()) 134 | .map(|(&sel, ®ister)| sel * register) 135 | .sum::(), 136 | ); 137 | 138 | // keypad selector 139 | for i in 0..NUM_KEYS { 140 | builder.assert_bool(local.vx_sel[i]); 141 | } 142 | builder 143 | .when(local.is_skip_key_pressed + local.is_skip_key_not_pressed) 144 | .assert_one(local.x_sel.into_iter().map(|x| x.into()).sum::()); 145 | builder 146 | .when(local.is_skip_key_pressed + local.is_skip_key_not_pressed) 147 | .assert_eq( 148 | local.vx, 149 | local 150 | .vx_sel 151 | .into_iter() 152 | .enumerate() 153 | .map(|(i, x)| AB::Expr::from_canonical_usize(i) * x) 154 | .sum::(), 155 | ); 156 | let key_vx = local 157 | .vx_sel 158 | .iter() 159 | .zip_eq(local.keypad.iter()) 160 | .map(|(&sel, &key)| sel * key) 161 | .sum::(); 162 | 163 | // is_equal_vx_nn 164 | let is_equal_vx_nn_air = IsEqualAir; 165 | let mut builder_when_local_is_real = builder.when(local.is_real); 166 | let mut sub_builder = SubAirBuilder::new_main( 167 | &mut builder_when_local_is_real, 168 | vec![ 169 | col_map.vx, 170 | col_map.nn, 171 | col_map.diff_vx_nn_inv, 172 | col_map.is_equal_vx_nn, 173 | ], 174 | ); 175 | is_equal_vx_nn_air.eval(&mut sub_builder); 176 | 177 | // is_equal_vx_vy 178 | let is_equal_vx_vy_air = IsEqualAir; 179 | let mut builder_when_local_is_real = builder.when(local.is_real); 180 | let mut sub_builder = SubAirBuilder::new_main( 181 | &mut builder_when_local_is_real, 182 | vec![ 183 | col_map.vx, 184 | col_map.vy, 185 | col_map.diff_vx_vy_inv, 186 | col_map.is_equal_vx_vy, 187 | ], 188 | ); 189 | is_equal_vx_vy_air.eval(&mut sub_builder); 190 | 191 | // program counter 192 | builder 193 | .when_transition() 194 | .when(next.is_real) 195 | .when( 196 | local.is_clear_display 197 | + local.is_load 198 | + local.is_move 199 | + local.is_or 200 | + local.is_and 201 | + local.is_xor 202 | + local.is_add_xy 203 | + local.is_sub_xy 204 | + local.is_shift_right 205 | + local.is_sub_yx 206 | + local.is_shift_left 207 | + local.is_load_i 208 | + local.is_random 209 | + local.is_draw 210 | + local.is_load_delay 211 | + local.is_wait_key_press 212 | + local.is_set_delay 213 | + local.is_set_sound 214 | + local.is_add_i 215 | + local.is_load_font 216 | + local.is_store_bcd 217 | + local.is_store_registers 218 | + local.is_load_memory, 219 | ) 220 | .assert_eq( 221 | next.program_counter, 222 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE), 223 | ); 224 | 225 | let stack_top = local 226 | .stack_pointer_sel 227 | .iter() 228 | .zip_eq(local.stack.iter()) 229 | .map(|(&sel, &val)| sel * val) 230 | .sum::(); 231 | builder 232 | .when_transition() 233 | .when(next.is_real) // TODO: Check if necessary 234 | .when(local.is_return) 235 | .assert_eq(next.program_counter, stack_top); 236 | 237 | builder 238 | .when_transition() 239 | .when(next.is_real) 240 | .when(local.is_jump + local.is_call) 241 | .assert_eq(next.program_counter, local.nnn); 242 | 243 | builder 244 | .when_transition() 245 | .when(next.is_real) 246 | .when(local.is_jump_v0) 247 | .assert_eq(next.program_counter, local.registers[0] + local.nnn); 248 | 249 | builder 250 | .when_transition() 251 | .when(next.is_real) 252 | .when(local.is_skip_equal) 253 | .when(local.is_equal_vx_nn) 254 | .assert_eq( 255 | next.program_counter, 256 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE).double(), 257 | ); 258 | builder 259 | .when_transition() 260 | .when(next.is_real) 261 | .when(local.is_skip_not_equal) 262 | .when_ne(local.is_equal_vx_nn, AB::Expr::one()) 263 | .assert_eq( 264 | next.program_counter, 265 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE).double(), 266 | ); 267 | 268 | builder 269 | .when_transition() 270 | .when(next.is_real) 271 | .when(local.is_skip_equal_xy) 272 | .when(local.is_equal_vx_vy) 273 | .assert_eq( 274 | next.program_counter, 275 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE).double(), 276 | ); 277 | builder 278 | .when_transition() 279 | .when(next.is_real) 280 | .when(local.is_skip_not_equal_xy) 281 | .when_ne(local.is_equal_vx_vy, AB::Expr::one()) 282 | .assert_eq( 283 | next.program_counter, 284 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE).double(), 285 | ); 286 | builder 287 | .when_transition() 288 | .when(next.is_real) 289 | .when(local.is_skip_key_pressed) 290 | .when(key_vx.clone()) 291 | .assert_eq( 292 | next.program_counter, 293 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE).double(), 294 | ); 295 | builder 296 | .when_transition() 297 | .when(next.is_real) 298 | .when(local.is_skip_key_not_pressed) 299 | .when_ne(key_vx, AB::Expr::one()) 300 | .assert_eq( 301 | next.program_counter, 302 | local.program_counter + AB::Expr::from_canonical_u16(OPCODE_SIZE).double(), 303 | ); 304 | 305 | // lte_x_sel only on 2 opcodes 306 | // builder.when(local.is_store_registers + local.is_load_memory).assert 307 | 308 | // TODO: Constrain bcd_i 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /chip0-core/src/cpu.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::{ 2 | constants::{DISPLAY_HEIGHT, DISPLAY_WIDTH, NUM_REGISTERS, TICKS_PER_TIMER}, 3 | cpu::Cpu, 4 | error::Chip8Error, 5 | input::{InputEvent, InputQueue}, 6 | instruction::Instruction, 7 | rwlock::CheckedWrite, 8 | state::{State, Word}, 9 | util::run_loop, 10 | }; 11 | use p3_field::{AbstractField, PrimeField32}; 12 | use p3_uni_stark::{StarkGenericConfig, Val}; 13 | use rand::Rng; 14 | use std::{ 15 | collections::VecDeque, 16 | sync::{Arc, RwLock}, 17 | }; 18 | 19 | use crate::{chips::draw::columns::WORD_BITS, prover::Prover, trace::StarkState}; 20 | 21 | pub const TICKS_PER_PROOF: u64 = 10000; 22 | 23 | pub struct StarkCpu 24 | where 25 | R: Rng, 26 | SC: StarkGenericConfig, 27 | Val: PrimeField32, 28 | P: Prover + Send + Sync + 'static, 29 | { 30 | state: StarkState>, 31 | clk_freq: u64, 32 | rng: R, 33 | 34 | prover: Arc

, 35 | } 36 | 37 | impl StarkCpu 38 | where 39 | R: Rng, 40 | SC: StarkGenericConfig, 41 | Val: PrimeField32, 42 | P: Prover + Send + Sync + 'static, 43 | { 44 | pub fn new(clk_freq: u64, rng: R, prover: P) -> Self { 45 | Self { 46 | state: StarkState::default(), 47 | clk_freq, 48 | rng, 49 | prover: Arc::new(prover), 50 | } 51 | } 52 | } 53 | 54 | impl Cpu for StarkCpu 55 | where 56 | R: Rng, 57 | SC: StarkGenericConfig, 58 | Val: PrimeField32, 59 | P: Prover + Send + Sync + 'static, 60 | { 61 | type State = StarkState>; 62 | 63 | fn state(&mut self) -> &mut Self::State { 64 | &mut self.state 65 | } 66 | 67 | fn random(&mut self) -> Word { 68 | self.rng.gen() 69 | } 70 | 71 | fn frequency(&self) -> u64 { 72 | self.clk_freq 73 | } 74 | 75 | fn op_draw(&mut self, x: Word, y: Word, n: Word) -> Result<(), Chip8Error> { 76 | let clk = self.state().clk()?; 77 | 78 | let vx = self.state().register(x); 79 | let vy = self.state().register(y); 80 | let vi = self.state().index_register(); 81 | 82 | let x0 = vx as usize % DISPLAY_WIDTH; 83 | let y0 = vy as usize % DISPLAY_HEIGHT; 84 | let mut flipped = false; 85 | 86 | // Each row in loop 87 | let curr_row = &mut self.state().trace.draw.curr_row; 88 | curr_row.is_real = Val::::one(); 89 | curr_row.is_first = Val::::one(); 90 | curr_row.clk = Val::::from_canonical_u64(clk); 91 | curr_row.register_x = Val::::from_canonical_u8(vx); 92 | curr_row.register_y = Val::::from_canonical_u8(vy); 93 | curr_row.index_register = Val::::from_canonical_u16(vi); 94 | 95 | for ys in 0..n { 96 | self.state().trace.draw.curr_row.is_first_inner = Val::::one(); 97 | 98 | let y = (y0 + ys as usize) % DISPLAY_HEIGHT; 99 | let pixels = self.state().memory(vi + ys as u16)?; 100 | for xs in 0..8u8 { 101 | let x = (x0 + xs as usize) % DISPLAY_WIDTH; 102 | let pixel = (pixels >> (7 - xs)) & 1 == 1; 103 | let fb = self.state().frame_buffer(y, x)?; 104 | let curr_flipped = pixel & fb; 105 | flipped |= curr_flipped; 106 | if pixel { 107 | self.state().set_frame_buffer(y, x, !fb)?; 108 | } 109 | 110 | let curr_row = &mut self.state().trace.draw.curr_row; 111 | curr_row.ys = Val::::from_canonical_u8(ys); 112 | curr_row.y = Val::::from_canonical_usize(y); 113 | curr_row.pixels = Val::::from_canonical_u8(pixels); 114 | curr_row.xs = Val::::from_canonical_u8(xs); 115 | curr_row.x = Val::::from_canonical_usize(x); 116 | curr_row.pixel = Val::::from_bool(pixel); 117 | curr_row.frame_buffer_y_x = Val::::from_bool(fb); 118 | curr_row.flipped = Val::::from_bool(curr_flipped); 119 | curr_row.register_flag = Val::::from_bool(flipped); 120 | 121 | for i in 0..WORD_BITS { 122 | curr_row.pixels_bits[i] = Val::::from_canonical_u8((pixels >> i) & 1); 123 | curr_row.sel_7_minus_xs[i] = Val::::from_bool(i == 7 - xs as usize); 124 | } 125 | if ys == n - 1 && xs == 7 { 126 | curr_row.is_last = Val::::one(); 127 | } 128 | 129 | self.state().trace.draw.add_curr_row_to_trace(); 130 | } 131 | } 132 | self.state().set_flag_register(flipped); 133 | Ok(()) 134 | } 135 | 136 | fn fetch(&mut self) -> Result { 137 | let pc = self.state().program_counter(); 138 | self.state().trace.cpu.next_row.program_counter = Val::::from_canonical_u16(pc); 139 | 140 | let hi = self.state().memory(pc)?; 141 | let lo = self.state().memory(pc + 1)?; 142 | 143 | let opcode = u16::from_be_bytes([hi, lo]); 144 | 145 | self.state().increment_program_counter(); 146 | let curr_row = &mut self.state().trace.cpu.curr_row; 147 | curr_row.opcode_hi = Val::::from_canonical_u8(hi); 148 | curr_row.opcode_lo = Val::::from_canonical_u8(lo); 149 | curr_row.opcode = Val::::from_canonical_u16(opcode); 150 | 151 | Ok(opcode) 152 | } 153 | 154 | fn decode(&mut self, opcode: u16) -> Result { 155 | let curr_row = &mut self.state().trace.cpu.curr_row; 156 | 157 | let x = ((opcode >> 8) & 0x000F) as u8; 158 | let y = ((opcode >> 4) & 0x000F) as u8; 159 | 160 | let n = (opcode & 0x000F) as u8; 161 | let nn = (opcode & 0x00FF) as u8; 162 | let nnn = opcode & 0x0FFF; 163 | 164 | curr_row.x = Val::::from_canonical_u8(x); 165 | curr_row.y = Val::::from_canonical_u8(y); 166 | 167 | for i in 0..NUM_REGISTERS { 168 | curr_row.x_sel[i] = Val::::from_bool(x == i as u8); 169 | curr_row.y_sel[i] = Val::::from_bool(y == i as u8); 170 | } 171 | 172 | curr_row.n = Val::::from_canonical_u8(n); 173 | curr_row.nn = Val::::from_canonical_u8(nn); 174 | curr_row.nnn = Val::::from_canonical_u16(nnn); 175 | 176 | // TODO: Constraints for this 177 | match opcode & 0xF000 { 178 | 0x0000 => match opcode & 0xF0FF { 179 | // 0x00E0 180 | 0x00E0 => { 181 | curr_row.is_clear_display = Val::::one(); 182 | Ok(Instruction::ClearDisplay) 183 | } 184 | // 0x00EE 185 | 0x00EE => { 186 | curr_row.is_return = Val::::one(); 187 | Ok(Instruction::Return) 188 | } 189 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 190 | }, 191 | // 0x1NNN 192 | 0x1000 => { 193 | curr_row.is_jump = Val::::one(); 194 | Ok(Instruction::Jump(nnn)) 195 | } 196 | // 0x2NNN 197 | 0x2000 => { 198 | curr_row.is_call = Val::::one(); 199 | Ok(Instruction::Call(nnn)) 200 | } 201 | // 0x3XNN 202 | 0x3000 => { 203 | curr_row.is_skip_equal = Val::::one(); 204 | Ok(Instruction::SkipEqual(x, nn)) 205 | } 206 | // 0x4XNN 207 | 0x4000 => { 208 | curr_row.is_skip_not_equal = Val::::one(); 209 | Ok(Instruction::SkipNotEqual(x, nn)) 210 | } 211 | // 0x5XY0 212 | 0x5000 => match opcode & 0xF00F { 213 | 0x5000 => { 214 | curr_row.is_skip_equal_xy = Val::::one(); 215 | Ok(Instruction::SkipEqualXY(x, y)) 216 | } 217 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 218 | }, 219 | // 0x6XNN 220 | 0x6000 => { 221 | curr_row.is_load = Val::::one(); 222 | Ok(Instruction::Load(x, nn)) 223 | } 224 | // 0x7XNN 225 | 0x7000 => { 226 | curr_row.is_add = Val::::one(); 227 | Ok(Instruction::Add(x, nn)) 228 | } 229 | 0x8000 => match opcode & 0xF00F { 230 | // 0x8XY0 231 | 0x8000 => { 232 | curr_row.is_move = Val::::one(); 233 | Ok(Instruction::Move(x, y)) 234 | } 235 | // 0x8XY1 236 | 0x8001 => { 237 | curr_row.is_or = Val::::one(); 238 | Ok(Instruction::Or(x, y)) 239 | } 240 | // 0x8XY2 241 | 0x8002 => { 242 | curr_row.is_and = Val::::one(); 243 | Ok(Instruction::And(x, y)) 244 | } 245 | // 0x8XY3 246 | 0x8003 => { 247 | curr_row.is_xor = Val::::one(); 248 | Ok(Instruction::Xor(x, y)) 249 | } 250 | // 0x8XY4 251 | 0x8004 => { 252 | curr_row.is_add_xy = Val::::one(); 253 | Ok(Instruction::AddXY(x, y)) 254 | } 255 | // 0x8XY5 256 | 0x8005 => { 257 | curr_row.is_sub_xy = Val::::one(); 258 | Ok(Instruction::SubXY(x, y)) 259 | } 260 | // 0x8XY6 261 | 0x8006 => { 262 | curr_row.is_shift_right = Val::::one(); 263 | Ok(Instruction::ShiftRight(x)) 264 | } 265 | // 0x8XY7 266 | 0x8007 => { 267 | curr_row.is_sub_yx = Val::::one(); 268 | Ok(Instruction::SubYX(x, y)) 269 | } 270 | // 0x8XYE 271 | 0x800E => { 272 | curr_row.is_shift_left = Val::::one(); 273 | Ok(Instruction::ShiftLeft(x)) 274 | } 275 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 276 | }, 277 | 0x9000 => match opcode & 0xF00F { 278 | // 0x9XY0 279 | 0x9000 => { 280 | curr_row.is_skip_not_equal_xy = Val::::one(); 281 | Ok(Instruction::SkipNotEqualXY(x, y)) 282 | } 283 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 284 | }, 285 | // 0xANNN 286 | 0xA000 => { 287 | curr_row.is_load_i = Val::::one(); 288 | Ok(Instruction::LoadI(nnn)) 289 | } 290 | // 0xBNNN 291 | 0xB000 => { 292 | curr_row.is_jump_v0 = Val::::one(); 293 | Ok(Instruction::JumpV0(nnn)) 294 | } 295 | // 0xCXNN 296 | 0xC000 => { 297 | curr_row.is_random = Val::::one(); 298 | Ok(Instruction::Random(x, nn)) 299 | } 300 | // 0xDXYN 301 | 0xD000 => { 302 | curr_row.is_draw = Val::::one(); 303 | Ok(Instruction::Draw(x, y, n)) 304 | } 305 | 0xE000 => match opcode & 0xF0FF { 306 | // 0xEX9E 307 | 0xE09E => { 308 | curr_row.is_skip_key_pressed = Val::::one(); 309 | Ok(Instruction::SkipKeyPressed(x)) 310 | } 311 | // 0xEXA1 312 | 0xE0A1 => { 313 | curr_row.is_skip_key_not_pressed = Val::::one(); 314 | Ok(Instruction::SkipKeyNotPressed(x)) 315 | } 316 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 317 | }, 318 | 0xF000 => match opcode & 0xF0FF { 319 | // 0xFX07 320 | 0xF007 => { 321 | curr_row.is_load_delay = Val::::one(); 322 | Ok(Instruction::LoadDelay(x)) 323 | } 324 | // 0xFX0A 325 | 0xF00A => { 326 | curr_row.is_wait_key_press = Val::::one(); 327 | Ok(Instruction::WaitKeyPress(x)) 328 | } 329 | // 0xFX15 330 | 0xF015 => { 331 | curr_row.is_set_delay = Val::::one(); 332 | Ok(Instruction::SetDelay(x)) 333 | } 334 | // 0xFX18 335 | 0xF018 => { 336 | curr_row.is_set_sound = Val::::one(); 337 | Ok(Instruction::SetSound(x)) 338 | } 339 | // 0xFX1E 340 | 0xF01E => { 341 | curr_row.is_add_i = Val::::one(); 342 | Ok(Instruction::AddI(x)) 343 | } 344 | // 0xFX29 345 | 0xF029 => { 346 | curr_row.is_load_font = Val::::one(); 347 | Ok(Instruction::LoadFont(x)) 348 | } 349 | // 0xFX33 350 | 0xF033 => { 351 | curr_row.is_store_bcd = Val::::one(); 352 | Ok(Instruction::StoreBCD(x)) 353 | } 354 | // 0xFX55 355 | 0xF055 => { 356 | for i in 0..NUM_REGISTERS { 357 | curr_row.lte_x_sel[i] = Val::::from_bool((i as u8) <= x); 358 | } 359 | curr_row.is_store_registers = Val::::one(); 360 | Ok(Instruction::StoreRegisters(x)) 361 | } 362 | // 0xFX65 363 | 0xF065 => { 364 | for i in 0..NUM_REGISTERS { 365 | curr_row.lte_x_sel[i] = Val::::from_bool((i as u8) <= x); 366 | } 367 | curr_row.is_load_memory = Val::::one(); 368 | Ok(Instruction::LoadMemory(x)) 369 | } 370 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 371 | }, 372 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 373 | } 374 | } 375 | 376 | async fn run( 377 | &mut self, 378 | num_cycles: Option, 379 | status: Arc>>, 380 | input_queue: Arc>>, 381 | ) { 382 | // let mut prover_handle = None; 383 | run_loop(status.clone(), self.frequency(), |_| { 384 | let clk = self.state().clk()?; 385 | if let Some(num_cycles) = num_cycles { 386 | if clk >= num_cycles { 387 | return Err(Chip8Error::Terminated); 388 | } 389 | } 390 | 391 | let curr_row = &mut self.state().trace.cpu.curr_row; 392 | if clk == 0 { 393 | curr_row.is_first = Val::::one(); 394 | } 395 | curr_row.is_real = Val::::one(); 396 | 397 | while let Some(event) = (*input_queue.checked_write()?).dequeue(clk) { 398 | self.state().set_key(event.key, event.kind); 399 | } 400 | // TODO: How do I remove this clone? 401 | self.tick(status.clone(), input_queue.clone())?; 402 | if clk % TICKS_PER_TIMER == 0 { 403 | self.tick_timers()?; 404 | } 405 | 406 | self.state().increment_clk()?; 407 | 408 | Ok(()) 409 | }); 410 | 411 | let trace = self.state.finalize_trace(); 412 | let prover = self.prover.clone(); 413 | tokio::spawn(async move { prover.prove(trace) }); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /chip8-core/src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | sync::{Arc, RwLock}, 4 | }; 5 | 6 | mod simple; 7 | pub use simple::SimpleCpu; 8 | 9 | use crate::{ 10 | constants::{ 11 | DISPLAY_HEIGHT, DISPLAY_WIDTH, FONTSET_START_ADDRESS, FONT_SIZE, NUM_KEYS, TICKS_PER_TIMER, 12 | }, 13 | error::Chip8Error, 14 | input::{InputEvent, InputQueue}, 15 | instruction::Instruction, 16 | rwlock::{CheckedRead, CheckedWrite}, 17 | state::{Address, State, Word}, 18 | util::run_loop, 19 | }; 20 | 21 | pub trait Cpu { 22 | type State: State; 23 | 24 | fn state(&mut self) -> &mut Self::State; 25 | 26 | fn frequency(&self) -> u64; 27 | 28 | fn random(&mut self) -> Word; 29 | 30 | // Instructions 31 | fn op_clear_display(&mut self) -> Result<(), Chip8Error> { 32 | self.state().clear_framebuffer() 33 | } 34 | 35 | fn op_return(&mut self) { 36 | self.state().pop_stack() 37 | } 38 | 39 | fn op_jump(&mut self, nnn: Address) { 40 | self.state().set_program_counter(nnn); 41 | } 42 | 43 | fn op_call(&mut self, nnn: Address) { 44 | self.state().push_stack(nnn); 45 | } 46 | 47 | fn op_skip_equal(&mut self, x: Word, nn: Word) { 48 | let vx = self.state().register(x); 49 | if vx == nn { 50 | self.state().increment_program_counter(); 51 | } 52 | } 53 | 54 | fn op_skip_not_equal(&mut self, x: Word, nn: Word) { 55 | let vx = self.state().register(x); 56 | if vx != nn { 57 | self.state().increment_program_counter(); 58 | } 59 | } 60 | 61 | fn op_skip_equal_xy(&mut self, x: Word, y: Word) { 62 | let vx = self.state().register(x); 63 | let vy = self.state().register(y); 64 | if vx == vy { 65 | self.state().increment_program_counter(); 66 | } 67 | } 68 | 69 | fn op_load(&mut self, x: Word, nn: Word) { 70 | self.state().set_register(x, nn); 71 | } 72 | 73 | fn op_add(&mut self, x: Word, nn: Word) { 74 | let vx = self.state().register(x); 75 | let val = vx.wrapping_add(nn); 76 | self.state().set_register(x, val); 77 | } 78 | 79 | fn op_move(&mut self, x: Word, y: Word) { 80 | let vy = self.state().register(y); 81 | self.state().set_register(x, vy); 82 | } 83 | 84 | fn op_or(&mut self, x: Word, y: Word) { 85 | let vx = self.state().register(x); 86 | let vy = self.state().register(y); 87 | let val = vx | vy; 88 | self.state().set_register(x, val); 89 | } 90 | 91 | fn op_and(&mut self, x: Word, y: Word) { 92 | let vx = self.state().register(x); 93 | let vy = self.state().register(y); 94 | let val = vx & vy; 95 | self.state().set_register(x, val); 96 | } 97 | 98 | fn op_xor(&mut self, x: Word, y: Word) { 99 | let vx = self.state().register(x); 100 | let vy = self.state().register(y); 101 | let val = vx ^ vy; 102 | self.state().set_register(x, val); 103 | } 104 | 105 | fn op_add_xy(&mut self, x: Word, y: Word) { 106 | let vx = self.state().register(x); 107 | let vy = self.state().register(y); 108 | let (sum, carry) = vx.overflowing_add(vy); 109 | 110 | self.state().set_register(x, sum); 111 | self.state().set_flag_register(carry); 112 | } 113 | 114 | fn op_sub_xy(&mut self, x: Word, y: Word) { 115 | let vx = self.state().register(x); 116 | let vy = self.state().register(y); 117 | let (diff, borrow) = vx.overflowing_sub(vy); 118 | 119 | self.state().set_register(x, diff); 120 | self.state().set_flag_register(!borrow); 121 | } 122 | 123 | fn op_shift_right(&mut self, x: Word) { 124 | let vx = self.state().register(x); 125 | let flag = (vx & 1) != 0; 126 | let val = vx >> 1; 127 | 128 | self.state().set_flag_register(flag); 129 | self.state().set_register(x, val); 130 | } 131 | 132 | fn op_sub_yx(&mut self, x: Word, y: Word) { 133 | let vx = self.state().register(x); 134 | let vy = self.state().register(y); 135 | let (diff, borrow) = vy.overflowing_sub(vx); 136 | 137 | self.state().set_register(x, diff); 138 | self.state().set_flag_register(!borrow); 139 | } 140 | 141 | fn op_shift_left(&mut self, x: Word) { 142 | let vx = self.state().register(x); 143 | let flag = ((vx >> 7) & 1) != 0; 144 | let val = vx << 1; 145 | 146 | self.state().set_register(x, val); 147 | self.state().set_flag_register(flag); 148 | } 149 | 150 | fn op_skip_not_equal_xy(&mut self, x: Word, y: Word) { 151 | let vx = self.state().register(x); 152 | let vy = self.state().register(y); 153 | if vx != vy { 154 | self.state().increment_program_counter(); 155 | } 156 | } 157 | 158 | fn op_load_i(&mut self, nnn: Address) { 159 | self.state().set_index_register(nnn); 160 | } 161 | 162 | fn op_jump_v0(&mut self, nnn: Address) { 163 | let v0 = self.state().register(0); 164 | let offset = (v0 as u16) + nnn; 165 | self.state().set_program_counter(offset); 166 | } 167 | 168 | fn op_random(&mut self, x: Word, nn: Word) { 169 | let r = self.random(); 170 | let val = r & nn; 171 | self.state().set_register(x, val); 172 | } 173 | 174 | fn op_draw(&mut self, x: Word, y: Word, n: Word) -> Result<(), Chip8Error> { 175 | let vx = self.state().register(x); 176 | let vy = self.state().register(y); 177 | let vi = self.state().index_register(); 178 | 179 | let x0 = vx as usize % DISPLAY_WIDTH; 180 | let y0 = vy as usize % DISPLAY_HEIGHT; 181 | let mut flipped = false; 182 | for ys in 0..n { 183 | let y = (y0 + ys as usize) % DISPLAY_HEIGHT; 184 | let pixels = self.state().memory(vi + ys as u16)?; 185 | for xs in 0..8 { 186 | let x = (x0 + xs) % DISPLAY_WIDTH; 187 | let pixel = (pixels >> (7 - xs)) & 1 == 1; 188 | let fb = self.state().frame_buffer(y, x)?; 189 | flipped |= pixel & fb; 190 | if pixel { 191 | self.state().set_frame_buffer(y, x, !fb)?; 192 | } 193 | } 194 | } 195 | self.state().set_flag_register(flipped); 196 | Ok(()) 197 | } 198 | 199 | fn op_skip_key_pressed(&mut self, x: Word) { 200 | let vx = self.state().register(x); 201 | if self.state().key(vx) { 202 | self.state().increment_program_counter(); 203 | } 204 | } 205 | 206 | fn op_skip_key_not_pressed(&mut self, x: Word) { 207 | let vx = self.state().register(x); 208 | if !self.state().key(vx) { 209 | self.state().increment_program_counter(); 210 | } 211 | } 212 | 213 | fn op_load_delay(&mut self, x: Word) { 214 | let val = self.state().delay_timer(); 215 | self.state().set_register(x, val); 216 | } 217 | 218 | fn op_wait_key_press( 219 | &mut self, 220 | status: Arc>>, 221 | input_queue: Arc>>, 222 | x: Word, 223 | ) -> Result<(), Chip8Error> { 224 | let clk = self.state().clk()?; 225 | 226 | let mut pressed = false; 227 | for i in 0..NUM_KEYS { 228 | if self.state().key(i as u8) { 229 | pressed = true; 230 | } 231 | } 232 | while status.checked_read()?.is_ok() && !pressed { 233 | if let Some(event) = (*input_queue.checked_write()?).dequeue(clk) { 234 | self.state().set_key(event.key, event.kind); 235 | self.state().set_register(x, event.key as u8); 236 | pressed = true; 237 | } 238 | } 239 | Ok(()) 240 | } 241 | 242 | fn op_set_delay(&mut self, x: Word) { 243 | let vx = self.state().register(x); 244 | self.state().set_delay_timer(vx); 245 | } 246 | 247 | fn op_set_sound(&mut self, x: Word) -> Result<(), Chip8Error> { 248 | let vx = self.state().register(x); 249 | self.state().set_sound_timer(vx) 250 | } 251 | 252 | fn op_add_i(&mut self, x: Word) { 253 | let vx = self.state().register(x); 254 | let vi = self.state().index_register(); 255 | let addr = vi.wrapping_add(vx as u16); 256 | self.state().set_index_register(addr); 257 | } 258 | 259 | fn op_load_font(&mut self, x: Word) { 260 | let vx = self.state().register(x); 261 | let addr = FONTSET_START_ADDRESS + (FONT_SIZE as u16) * (vx as u16); 262 | self.state().set_index_register(addr); 263 | } 264 | 265 | fn op_store_bcd(&mut self, x: Word) -> Result<(), Chip8Error> { 266 | let vx = self.state().register(x); 267 | let vi = self.state().index_register(); 268 | 269 | self.state().set_memory(vi, (vx / 100) % 10)?; 270 | self.state().set_memory(vi + 1, (vx / 10) % 10)?; 271 | self.state().set_memory(vi + 2, vx % 10) 272 | } 273 | 274 | fn op_store_registers(&mut self, x: Word) -> Result<(), Chip8Error> { 275 | let vi = self.state().index_register(); 276 | for j in 0..=x { 277 | let vj = self.state().register(j); 278 | self.state().set_memory(vi + j as u16, vj)?; 279 | } 280 | Ok(()) 281 | } 282 | 283 | fn op_load_memory(&mut self, x: Word) -> Result<(), Chip8Error> { 284 | let vi = self.state().index_register(); 285 | for j in 0..=x { 286 | let val = self.state().memory(vi + j as u16)?; 287 | self.state().set_register(j, val); 288 | } 289 | Ok(()) 290 | } 291 | 292 | // Fetch - Decode - Execute 293 | fn fetch(&mut self) -> Result { 294 | let pc = self.state().program_counter(); 295 | let hi = self.state().memory(pc)?; 296 | let lo = self.state().memory(pc + 1)?; 297 | 298 | self.state().increment_program_counter(); 299 | Ok(u16::from_be_bytes([hi, lo])) 300 | } 301 | 302 | fn decode(&mut self, opcode: u16) -> Result { 303 | let x = ((opcode >> 8) & 0x000F) as u8; 304 | let y = ((opcode >> 4) & 0x000F) as u8; 305 | 306 | let n = (opcode & 0x000F) as u8; 307 | let nn = (opcode & 0x00FF) as u8; 308 | let nnn = opcode & 0x0FFF; 309 | 310 | match opcode & 0xF000 { 311 | 0x0000 => match opcode & 0xF0FF { 312 | // 0x00E0 313 | 0x00E0 => Ok(Instruction::ClearDisplay), 314 | // 0x00EE 315 | 0x00EE => Ok(Instruction::Return), 316 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 317 | }, 318 | // 0x1NNN 319 | 0x1000 => Ok(Instruction::Jump(nnn)), 320 | // 0x2NNN 321 | 0x2000 => Ok(Instruction::Call(nnn)), 322 | // 0x3XNN 323 | 0x3000 => Ok(Instruction::SkipEqual(x, nn)), 324 | // 0x4XNN 325 | 0x4000 => Ok(Instruction::SkipNotEqual(x, nn)), 326 | // 0x5XY0 327 | 0x5000 => match opcode & 0xF00F { 328 | 0x5000 => Ok(Instruction::SkipEqualXY(x, y)), 329 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 330 | }, 331 | // 0x6XNN 332 | 0x6000 => Ok(Instruction::Load(x, nn)), 333 | // 0x7XNN 334 | 0x7000 => Ok(Instruction::Add(x, nn)), 335 | 0x8000 => match opcode & 0xF00F { 336 | // 0x8XY0 337 | 0x8000 => Ok(Instruction::Move(x, y)), 338 | // 0x8XY1 339 | 0x8001 => Ok(Instruction::Or(x, y)), 340 | // 0x8XY2 341 | 0x8002 => Ok(Instruction::And(x, y)), 342 | // 0x8XY3 343 | 0x8003 => Ok(Instruction::Xor(x, y)), 344 | // 0x8XY4 345 | 0x8004 => Ok(Instruction::AddXY(x, y)), 346 | // 0x8XY5 347 | 0x8005 => Ok(Instruction::SubXY(x, y)), 348 | // 0x8XY6 349 | 0x8006 => Ok(Instruction::ShiftRight(x)), 350 | // 0x8XY7 351 | 0x8007 => Ok(Instruction::SubYX(x, y)), 352 | // 0x8XYE 353 | 0x800E => Ok(Instruction::ShiftLeft(x)), 354 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 355 | }, 356 | 0x9000 => match opcode & 0xF00F { 357 | // 0x9XY0 358 | 0x9000 => Ok(Instruction::SkipNotEqualXY(x, y)), 359 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 360 | }, 361 | // 0xANNN 362 | 0xA000 => Ok(Instruction::LoadI(nnn)), 363 | // 0xBNNN 364 | 0xB000 => Ok(Instruction::JumpV0(nnn)), 365 | // 0xCXNN 366 | 0xC000 => Ok(Instruction::Random(x, nn)), 367 | // 0xDXYN 368 | 0xD000 => Ok(Instruction::Draw(x, y, n)), 369 | 0xE000 => match opcode & 0xF0FF { 370 | // 0xEX9E 371 | 0xE09E => Ok(Instruction::SkipKeyPressed(x)), 372 | // 0xEXA1 373 | 0xE0A1 => Ok(Instruction::SkipKeyNotPressed(x)), 374 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 375 | }, 376 | 0xF000 => match opcode & 0xF0FF { 377 | // 0xFX07 378 | 0xF007 => Ok(Instruction::LoadDelay(x)), 379 | // 0xFX0A 380 | 0xF00A => Ok(Instruction::WaitKeyPress(x)), 381 | // 0xFX15 382 | 0xF015 => Ok(Instruction::SetDelay(x)), 383 | // 0xFX18 384 | 0xF018 => Ok(Instruction::SetSound(x)), 385 | // 0xFX1E 386 | 0xF01E => Ok(Instruction::AddI(x)), 387 | // 0xFX29 388 | 0xF029 => Ok(Instruction::LoadFont(x)), 389 | // 0xFX33 390 | 0xF033 => Ok(Instruction::StoreBCD(x)), 391 | // 0xFX55 392 | 0xF055 => Ok(Instruction::StoreRegisters(x)), 393 | // 0xFX65 394 | 0xF065 => Ok(Instruction::LoadMemory(x)), 395 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 396 | }, 397 | _ => Err(Chip8Error::UnimplementedOpcode(opcode)), 398 | } 399 | } 400 | 401 | fn execute( 402 | &mut self, 403 | instruction: Instruction, 404 | status: Arc>>, 405 | input_queue: Arc>>, 406 | ) -> Result<(), Chip8Error> { 407 | match instruction { 408 | Instruction::ClearDisplay => { 409 | self.op_clear_display()?; 410 | } 411 | Instruction::Return => { 412 | self.op_return(); 413 | } 414 | Instruction::Jump(nnn) => { 415 | self.op_jump(nnn); 416 | } 417 | Instruction::Call(nnn) => { 418 | self.op_call(nnn); 419 | } 420 | Instruction::SkipEqual(x, nn) => { 421 | self.op_skip_equal(x, nn); 422 | } 423 | Instruction::SkipNotEqual(x, nn) => { 424 | self.op_skip_not_equal(x, nn); 425 | } 426 | Instruction::SkipEqualXY(x, y) => { 427 | self.op_skip_equal_xy(x, y); 428 | } 429 | Instruction::Load(x, nn) => { 430 | self.op_load(x, nn); 431 | } 432 | Instruction::Add(x, nn) => { 433 | self.op_add(x, nn); 434 | } 435 | Instruction::Move(x, y) => { 436 | self.op_move(x, y); 437 | } 438 | Instruction::Or(x, y) => { 439 | self.op_or(x, y); 440 | } 441 | Instruction::And(x, y) => { 442 | self.op_and(x, y); 443 | } 444 | Instruction::Xor(x, y) => { 445 | self.op_xor(x, y); 446 | } 447 | Instruction::AddXY(x, y) => { 448 | self.op_add_xy(x, y); 449 | } 450 | Instruction::SubXY(x, y) => { 451 | self.op_sub_xy(x, y); 452 | } 453 | Instruction::ShiftRight(x) => { 454 | self.op_shift_right(x); 455 | } 456 | Instruction::SubYX(x, y) => { 457 | self.op_sub_yx(x, y); 458 | } 459 | Instruction::ShiftLeft(x) => { 460 | self.op_shift_left(x); 461 | } 462 | Instruction::SkipNotEqualXY(x, y) => { 463 | self.op_skip_not_equal_xy(x, y); 464 | } 465 | Instruction::LoadI(nnn) => { 466 | self.op_load_i(nnn); 467 | } 468 | Instruction::JumpV0(nnn) => { 469 | self.op_jump_v0(nnn); 470 | } 471 | Instruction::Random(x, nn) => { 472 | self.op_random(x, nn); 473 | } 474 | Instruction::Draw(x, y, n) => { 475 | self.op_draw(x, y, n)?; 476 | } 477 | Instruction::SkipKeyPressed(x) => { 478 | self.op_skip_key_pressed(x); 479 | } 480 | Instruction::SkipKeyNotPressed(x) => { 481 | self.op_skip_key_not_pressed(x); 482 | } 483 | Instruction::LoadDelay(x) => { 484 | self.op_load_delay(x); 485 | } 486 | Instruction::WaitKeyPress(x) => { 487 | self.op_wait_key_press(status, input_queue, x)?; 488 | } 489 | Instruction::SetDelay(x) => { 490 | self.op_set_delay(x); 491 | } 492 | Instruction::SetSound(x) => { 493 | self.op_set_sound(x)?; 494 | } 495 | Instruction::AddI(x) => { 496 | self.op_add_i(x); 497 | } 498 | Instruction::LoadFont(x) => { 499 | self.op_load_font(x); 500 | } 501 | Instruction::StoreBCD(x) => { 502 | self.op_store_bcd(x)?; 503 | } 504 | Instruction::StoreRegisters(x) => { 505 | self.op_store_registers(x)?; 506 | } 507 | Instruction::LoadMemory(x) => { 508 | self.op_load_memory(x)?; 509 | } 510 | } 511 | 512 | Ok(()) 513 | } 514 | 515 | // Cycle 516 | fn tick( 517 | &mut self, 518 | status: Arc>>, 519 | input_queue: Arc>>, 520 | ) -> Result<(), Chip8Error> { 521 | let op = self.fetch()?; 522 | let instruction = self.decode(op)?; 523 | self.execute(instruction, status, input_queue) 524 | } 525 | 526 | fn tick_timers(&mut self) -> Result<(), Chip8Error> { 527 | if self.state().delay_timer() > 0 { 528 | self.state().decrement_delay_timer(); 529 | } 530 | if self.state().sound_timer()? > 0 { 531 | self.state().decrement_sound_timer()?; 532 | } 533 | Ok(()) 534 | } 535 | 536 | async fn run( 537 | &mut self, 538 | num_cycles: Option, 539 | status: Arc>>, 540 | input_queue: Arc>>, 541 | ) { 542 | run_loop(status.clone(), self.frequency(), move |_| { 543 | let clk = self.state().clk()?; 544 | if let Some(num_cycles) = num_cycles { 545 | if clk >= num_cycles { 546 | return Ok(()); 547 | } 548 | } 549 | 550 | while let Some(event) = (*input_queue.checked_write()?).dequeue(clk) { 551 | self.state().set_key(event.key, event.kind); 552 | } 553 | // TODO: How do I remove this clone? 554 | self.tick(status.clone(), input_queue.clone())?; 555 | if clk % TICKS_PER_TIMER == 0 { 556 | self.tick_timers()?; 557 | } 558 | 559 | self.state().increment_clk()?; 560 | Ok(()) 561 | }) 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /chip0-core/src/trace.rs: -------------------------------------------------------------------------------- 1 | use chip8_core::{ 2 | constants::{ 3 | DISPLAY_HEIGHT, DISPLAY_WIDTH, FLAG_REGISTER, MEMORY_SIZE, NUM_KEYS, OPCODE_SIZE, 4 | PROGRAM_START_ADDRESS, STACK_DEPTH, 5 | }, 6 | error::Chip8Error, 7 | input::InputKind, 8 | keypad::Key, 9 | state::{Address, SimpleState, State, Word}, 10 | }; 11 | use core::slice; 12 | use itertools::Itertools; 13 | use p3_field::PrimeField32; 14 | use p3_matrix::dense::RowMajorMatrix; 15 | use std::{ 16 | collections::{BTreeMap, BTreeSet}, 17 | sync::{Arc, RwLock}, 18 | }; 19 | 20 | use crate::chips::{ 21 | clear::columns::ClearCols, cpu::columns::CpuCols, draw::columns::DrawCols, 22 | frame_buffer::columns::FrameBufferCols, keypad::columns::KeypadCols, 23 | memory::columns::MemoryCols, memory_start::columns::MemoryStartCols, range::columns::RangeCols, 24 | }; 25 | 26 | #[derive(Default, Clone)] 27 | pub struct IncrementalTrace { 28 | pub trace: Vec, 29 | pub curr_row: Cols, 30 | pub next_row: Cols, 31 | } 32 | 33 | #[repr(C)] 34 | #[derive(Default, Debug, Copy, Clone)] 35 | pub struct MemoryEventLike { 36 | pub clk: T, 37 | pub address: T, 38 | pub value: T, 39 | pub is_read: T, 40 | } 41 | 42 | #[derive(Clone)] 43 | pub struct PartialMachineTrace { 44 | pub cpu: Vec>, 45 | pub clear: Vec>, 46 | pub draw: Vec>, 47 | pub keypad: Vec>, 48 | // range_trace: Vec::default(), 49 | pub memory: Vec>, 50 | pub frame_buffer: Vec>, 51 | // TODO: Change to running hash 52 | // pub inputs: Vec<(u64, InputKind)>, 53 | } 54 | 55 | impl PartialMachineTrace { 56 | pub fn get_trace_matrices(mut self) -> Vec>> { 57 | let mut range_counts = BTreeMap::new(); 58 | let mut first_memory_reads = BTreeSet::new(); 59 | 60 | self.memory.sort_by_key(|event| event.address); 61 | let mut memory_trace = vec![MemoryCols::default(); self.memory.len()]; 62 | for (i, event) in self.memory.iter().enumerate() { 63 | memory_trace[i].addr = event.address; 64 | memory_trace[i].clk = event.clk; 65 | memory_trace[i].value = event.value; 66 | 67 | memory_trace[i].is_read = event.is_read; 68 | memory_trace[i].is_write = F::one() - event.is_read; 69 | 70 | let diff = if i > 0 { 71 | if memory_trace[i].addr == memory_trace[i - 1].addr { 72 | memory_trace[i].addr_unchanged = F::one(); 73 | memory_trace[i].clk - memory_trace[i - 1].clk 74 | } else { 75 | memory_trace[i].addr - memory_trace[i - 1].addr - F::one() 76 | } 77 | } else { 78 | F::zero() 79 | }; 80 | 81 | if event.is_read == F::one() && (i == 0 || memory_trace[i].addr_unchanged == F::zero()) 82 | { 83 | first_memory_reads.insert(event.address); 84 | memory_trace[i].is_first_read = F::one(); 85 | } 86 | 87 | let diff_limb_lo = F::from_canonical_u32(diff.as_canonical_u32() % (1 << 8)); 88 | let diff_limb_hi = F::from_canonical_u32((diff.as_canonical_u32() >> 8) % (1 << 8)); 89 | 90 | memory_trace[i].diff_limb_lo = diff_limb_lo; 91 | memory_trace[i].diff_limb_hi = diff_limb_hi; 92 | 93 | range_counts 94 | .entry(diff_limb_lo) 95 | .and_modify(|count| *count += F::one()) 96 | .or_insert(F::one()); 97 | range_counts 98 | .entry(diff_limb_hi) 99 | .and_modify(|count| *count += F::one()) 100 | .or_insert(F::one()); 101 | } 102 | 103 | self.frame_buffer.sort_by_key(|event| event.address); 104 | let mut frame_buffer_trace = vec![FrameBufferCols::default(); self.frame_buffer.len()]; 105 | for (i, event) in self.frame_buffer.iter().enumerate() { 106 | frame_buffer_trace[i].addr = event.address; 107 | frame_buffer_trace[i].clk = event.clk; 108 | frame_buffer_trace[i].value = event.value; 109 | 110 | frame_buffer_trace[i].is_read = event.is_read; 111 | frame_buffer_trace[i].is_write = F::one() - event.is_read; 112 | 113 | let diff = if i > 0 { 114 | if frame_buffer_trace[i].addr == frame_buffer_trace[i - 1].addr { 115 | frame_buffer_trace[i].addr_unchanged = F::one(); 116 | frame_buffer_trace[i].clk - frame_buffer_trace[i - 1].clk 117 | } else { 118 | frame_buffer_trace[i].addr - frame_buffer_trace[i - 1].addr - F::one() 119 | } 120 | } else { 121 | F::zero() 122 | }; 123 | let diff_limb_lo = F::from_canonical_u32(diff.as_canonical_u32() % (1 << 8)); 124 | let diff_limb_hi = F::from_canonical_u32((diff.as_canonical_u32() >> 8) % (1 << 8)); 125 | 126 | frame_buffer_trace[i].diff_limb_lo = diff_limb_lo; 127 | frame_buffer_trace[i].diff_limb_hi = diff_limb_hi; 128 | 129 | range_counts 130 | .entry(diff_limb_lo) 131 | .and_modify(|count| *count += F::one()) 132 | .or_insert(F::one()); 133 | range_counts 134 | .entry(diff_limb_hi) 135 | .and_modify(|count| *count += F::one()) 136 | .or_insert(F::one()); 137 | } 138 | 139 | let range_trace = (0..(1 << 8)) 140 | .map(|n| { 141 | let n = F::from_canonical_u32(n); 142 | RangeCols { 143 | value: n, 144 | mult: *range_counts.get(&n).unwrap_or(&F::zero()), 145 | } 146 | }) 147 | .collect_vec(); 148 | let memory_start_trace = (0..MEMORY_SIZE) 149 | .map(|n| MemoryStartCols { 150 | mult: F::from_bool(first_memory_reads.contains(&F::from_canonical_usize(n))), 151 | }) 152 | .collect_vec(); 153 | 154 | let cpu_matrix = self.cpu.to_trace_matrix(CpuCols::::num_cols()); 155 | let clear_matrix = self.clear.to_trace_matrix(ClearCols::::num_cols()); 156 | let draw_matrix = self.draw.to_trace_matrix(DrawCols::::num_cols()); 157 | let keypad_matrix = self.keypad.to_trace_matrix(KeypadCols::::num_cols()); 158 | let memory_matrix = memory_trace.to_trace_matrix(MemoryCols::::num_cols()); 159 | let frame_buffer_matrix = 160 | frame_buffer_trace.to_trace_matrix(FrameBufferCols::::num_cols()); 161 | let range_matrix = range_trace.to_trace_matrix(RangeCols::::num_cols()); 162 | let memory_start_matrix = 163 | memory_start_trace.to_trace_matrix(MemoryStartCols::::num_cols()); 164 | 165 | vec![ 166 | cpu_matrix, 167 | clear_matrix, 168 | draw_matrix, 169 | keypad_matrix, 170 | memory_matrix, 171 | frame_buffer_matrix, 172 | range_matrix, 173 | memory_start_matrix, 174 | ] 175 | } 176 | } 177 | 178 | #[derive(Clone)] 179 | pub struct IncrementalMachineTrace { 180 | pub cpu: IncrementalTrace>, 181 | pub clear: IncrementalTrace>, 182 | pub draw: IncrementalTrace>, 183 | pub keypad: IncrementalTrace>, 184 | // range_trace: IncrementalTrace::default(), 185 | pub memory: Vec>, 186 | pub frame_buffer: Vec>, 187 | // TODO: Change to running hash 188 | // pub inputs: Vec<(u64, InputKind)>, 189 | } 190 | 191 | impl Default for IncrementalMachineTrace { 192 | fn default() -> Self { 193 | let mut cpu: IncrementalTrace> = IncrementalTrace::default(); 194 | cpu.curr_row.program_counter = F::from_canonical_u16(PROGRAM_START_ADDRESS); 195 | 196 | Self { 197 | cpu, 198 | clear: IncrementalTrace::default(), 199 | draw: IncrementalTrace::default(), 200 | keypad: IncrementalTrace::default(), 201 | // range: IncrementalTrace::default(), 202 | memory: Vec::new(), 203 | frame_buffer: Vec::new(), 204 | } 205 | } 206 | } 207 | 208 | // TODO: Derive simple state from traces 209 | pub struct StarkState { 210 | pub state: SimpleState, 211 | pub trace: IncrementalMachineTrace, 212 | } 213 | 214 | impl Default for StarkState { 215 | fn default() -> Self { 216 | Self { 217 | state: SimpleState::default(), 218 | trace: IncrementalMachineTrace::default(), 219 | } 220 | } 221 | } 222 | 223 | impl StarkState { 224 | pub fn finalize_trace(&mut self) -> PartialMachineTrace { 225 | // TODO: Remove clones 226 | let cpu = self.trace.cpu.trace.clone(); 227 | let clear = self.trace.clear.trace.clone(); 228 | let draw = self.trace.draw.trace.clone(); 229 | let keypad = self.trace.keypad.trace.clone(); 230 | 231 | PartialMachineTrace { 232 | cpu, 233 | clear, 234 | draw, 235 | keypad, 236 | memory: self.trace.memory.clone(), 237 | frame_buffer: self.trace.frame_buffer.clone(), 238 | } 239 | } 240 | } 241 | 242 | impl State for StarkState { 243 | fn load_rom(&mut self, bytes: &[u8]) -> Result<(), Chip8Error> { 244 | self.state.load_rom(bytes) 245 | } 246 | 247 | fn clk(&self) -> Result { 248 | self.state.clk() 249 | } 250 | 251 | fn clk_ptr(&self) -> Arc> { 252 | self.state.clk_ptr() 253 | } 254 | 255 | fn sound_timer_ptr(&self) -> Arc> { 256 | self.state.sound_timer_ptr() 257 | } 258 | 259 | fn frame_buffer_ptr(&self) -> Arc> { 260 | self.state.frame_buffer_ptr() 261 | } 262 | 263 | fn program_counter(&self) -> Address { 264 | self.state.program_counter() 265 | } 266 | 267 | fn delay_timer(&self) -> Word { 268 | self.state.delay_timer() 269 | } 270 | 271 | fn sound_timer(&self) -> Result { 272 | self.state.sound_timer() 273 | } 274 | 275 | fn memory(&mut self, addr: Address) -> Result { 276 | let value = self.state.memory(addr)?; 277 | 278 | let clk = self.clk()?; 279 | let event = MemoryEventLike { 280 | clk: F::from_canonical_u64(clk), 281 | address: F::from_canonical_u16(addr), 282 | value: F::from_canonical_u8(value), 283 | is_read: F::from_bool(true), 284 | }; 285 | self.trace.memory.push(event); 286 | 287 | Ok(value) 288 | } 289 | 290 | fn register(&self, index: Word) -> Word { 291 | self.state.register(index) 292 | } 293 | 294 | fn index_register(&self) -> Address { 295 | self.state.index_register() 296 | } 297 | 298 | fn key(&self, index: Word) -> bool { 299 | self.state.key(index) 300 | } 301 | 302 | fn frame_buffer(&mut self, y: usize, x: usize) -> Result { 303 | let value = self.state.frame_buffer(y, x)?; 304 | 305 | let clk = self.clk()?; 306 | let addr = y * DISPLAY_WIDTH + x; 307 | let event = MemoryEventLike { 308 | clk: F::from_canonical_u64(clk), 309 | address: F::from_canonical_usize(addr), 310 | value: F::from_bool(value), 311 | is_read: F::from_bool(true), 312 | }; 313 | self.trace.frame_buffer.push(event); 314 | 315 | Ok(value) 316 | } 317 | 318 | fn set_frame_buffer(&mut self, y: usize, x: usize, bit: bool) -> Result<(), Chip8Error> { 319 | let clk = self.clk()?; 320 | let addr = y * DISPLAY_WIDTH + x; 321 | let event = MemoryEventLike { 322 | clk: F::from_canonical_u64(clk), 323 | address: F::from_canonical_usize(addr), 324 | value: F::from_bool(bit), 325 | is_read: F::from_bool(false), 326 | }; 327 | self.trace.frame_buffer.push(event); 328 | 329 | self.state.set_frame_buffer(y, x, bit) 330 | } 331 | 332 | fn set_program_counter(&mut self, pc: Address) { 333 | let next_row = &mut self.trace.cpu.next_row; 334 | next_row.program_counter = F::from_canonical_u16(pc); 335 | 336 | self.state.set_program_counter(pc) 337 | } 338 | 339 | fn set_delay_timer(&mut self, value: Word) { 340 | let curr_row = &mut self.trace.cpu.curr_row; 341 | curr_row.delay_timer = F::from_canonical_u8(value); 342 | 343 | self.state.set_delay_timer(value) 344 | } 345 | 346 | fn set_sound_timer(&mut self, value: Word) -> Result<(), Chip8Error> { 347 | let curr_row = &mut self.trace.cpu.curr_row; 348 | curr_row.sound_timer = F::from_canonical_u8(value); 349 | 350 | self.state.set_sound_timer(value) 351 | } 352 | 353 | fn set_index_register(&mut self, addr: Address) { 354 | let curr_row = &mut self.trace.cpu.curr_row; 355 | curr_row.index_register = F::from_canonical_u16(addr); 356 | 357 | self.state.set_index_register(addr) 358 | } 359 | 360 | fn set_register(&mut self, index: Word, value: Word) { 361 | let curr_row = &mut self.trace.cpu.curr_row; 362 | curr_row.registers[index as usize] = F::from_canonical_u8(value); 363 | 364 | self.state.set_register(index, value) 365 | } 366 | 367 | fn set_flag_register(&mut self, flag: bool) { 368 | let curr_row = &mut self.trace.cpu.curr_row; 369 | curr_row.registers[FLAG_REGISTER] = F::from_bool(flag); 370 | 371 | self.state.set_flag_register(flag) 372 | } 373 | 374 | fn set_memory(&mut self, addr: Address, value: Word) -> Result<(), Chip8Error> { 375 | let clk = self.clk()?; 376 | let event = MemoryEventLike { 377 | clk: F::from_canonical_u64(clk), 378 | address: F::from_canonical_u16(addr), 379 | value: F::from_canonical_u8(value), 380 | is_read: F::from_bool(false), 381 | }; 382 | self.trace.memory.push(event); 383 | 384 | self.state.set_memory(addr, value) 385 | } 386 | 387 | fn set_key(&mut self, key: Key, kind: InputKind) { 388 | self.trace.cpu.curr_row.keypad[key as usize] = F::from_bool(kind == InputKind::Press); 389 | 390 | self.trace.keypad.curr_row.index = F::from_canonical_usize(key as usize); 391 | self.trace.keypad.curr_row.value = F::from_bool(kind == InputKind::Press); 392 | self.trace.keypad.add_curr_row_to_trace(); 393 | 394 | self.state.set_key(key, kind) 395 | } 396 | 397 | fn clear_framebuffer(&mut self) -> Result<(), Chip8Error> { 398 | let clk = self.clk()?; 399 | for y in 0..DISPLAY_HEIGHT { 400 | for x in 0..DISPLAY_WIDTH { 401 | let curr_row = &mut self.trace.clear.curr_row; 402 | curr_row.is_real = F::one(); 403 | if y * DISPLAY_WIDTH + x == 0 { 404 | curr_row.is_start = F::one(); 405 | } else { 406 | curr_row.is_start = F::zero(); 407 | } 408 | curr_row.clk = F::from_canonical_u64(clk); 409 | curr_row.addr = F::from_canonical_usize(y * DISPLAY_WIDTH + x); 410 | 411 | self.trace.clear.add_curr_row_to_trace(); 412 | 413 | let addr = y * DISPLAY_WIDTH + x; 414 | let event = MemoryEventLike { 415 | clk: F::from_canonical_u64(clk), 416 | address: F::from_canonical_usize(addr), 417 | value: F::from_bool(false), 418 | is_read: F::from_bool(false), 419 | }; 420 | self.trace.frame_buffer.push(event); 421 | } 422 | } 423 | 424 | self.state.clear_framebuffer() 425 | } 426 | 427 | fn push_stack(&mut self, addr: Address) { 428 | let curr_row = &mut self.trace.cpu.curr_row; 429 | let next_row = &mut self.trace.cpu.next_row; 430 | curr_row.stack[self.state.stack_pointer as usize] = 431 | F::from_canonical_u16(self.state.program_counter); 432 | curr_row.stack_pointer += F::one(); 433 | // TODO: Move this to a helper function 434 | for i in 0..STACK_DEPTH { 435 | if i == self.state.stack_pointer as usize + 1 { 436 | curr_row.stack_pointer_sel[i] = F::from_bool(true); 437 | } else { 438 | curr_row.stack_pointer_sel[i] = F::from_bool(false); 439 | } 440 | } 441 | next_row.program_counter = F::from_canonical_u16(addr); 442 | 443 | self.state.push_stack(addr) 444 | } 445 | 446 | fn pop_stack(&mut self) { 447 | let curr_row = &mut self.trace.cpu.curr_row; 448 | let next_row = &mut self.trace.cpu.next_row; 449 | curr_row.stack_pointer -= F::one(); 450 | for i in 0..STACK_DEPTH { 451 | if i == self.state.stack_pointer as usize - 1 { 452 | curr_row.stack_pointer_sel[i] = F::from_bool(true); 453 | } else { 454 | curr_row.stack_pointer_sel[i] = F::from_bool(false); 455 | } 456 | } 457 | next_row.program_counter = 458 | F::from_canonical_u16(self.state.stack[self.state.stack_pointer as usize - 1]); 459 | 460 | self.state.pop_stack() 461 | } 462 | 463 | fn increment_program_counter(&mut self) { 464 | let next_row = &mut self.trace.cpu.next_row; 465 | next_row.program_counter += F::from_canonical_u16(OPCODE_SIZE); 466 | 467 | self.state.increment_program_counter() 468 | } 469 | 470 | fn increment_clk(&mut self) -> Result<(), Chip8Error> { 471 | let curr_row = &self.trace.cpu.curr_row; 472 | let next_row = &mut self.trace.cpu.next_row; 473 | next_row.clk = curr_row.clk + F::one(); 474 | 475 | self.trace.cpu.add_curr_row_to_trace(); 476 | 477 | self.state.increment_clk() 478 | } 479 | 480 | fn decrement_delay_timer(&mut self) { 481 | let curr_row = &mut self.trace.cpu.curr_row; 482 | curr_row.delay_timer -= F::one(); 483 | 484 | self.state.decrement_delay_timer() 485 | } 486 | 487 | fn decrement_sound_timer(&mut self) -> Result<(), Chip8Error> { 488 | let curr_row = &mut self.trace.cpu.curr_row; 489 | curr_row.sound_timer -= F::one(); 490 | 491 | self.state.decrement_sound_timer() 492 | } 493 | } 494 | 495 | impl IncrementalTrace> { 496 | pub fn add_curr_row_to_trace(&mut self) { 497 | let vx = self 498 | .curr_row 499 | .x_sel 500 | .iter() 501 | .zip_eq(self.curr_row.registers.iter()) 502 | .map(|(&sel, ®ister)| sel * register) 503 | .sum::(); 504 | let vy = self 505 | .curr_row 506 | .y_sel 507 | .iter() 508 | .zip_eq(self.curr_row.registers.iter()) 509 | .map(|(&sel, ®ister)| sel * register) 510 | .sum::(); 511 | 512 | self.curr_row.vx = vx; 513 | self.curr_row.vy = vy; 514 | for i in 0..NUM_KEYS { 515 | self.curr_row.vx_sel[i] = F::from_bool(vx == F::from_canonical_usize(i)); 516 | } 517 | self.curr_row.diff_vx_nn_inv = (vx - self.curr_row.nn).try_inverse().unwrap_or_default(); 518 | self.curr_row.is_equal_vx_nn = F::from_bool(vx == self.curr_row.nn); 519 | self.curr_row.vx_bcd0 = F::from_canonical_u64((vx.as_canonical_u64() / 100) % 10); 520 | self.curr_row.vx_bcd1 = F::from_canonical_u64((vx.as_canonical_u64() / 10) % 10); 521 | self.curr_row.vx_bcd2 = F::from_canonical_u64(vx.as_canonical_u64() % 10); 522 | 523 | self.curr_row.diff_vx_vy_inv = (vx - vy).try_inverse().unwrap_or_default(); 524 | self.curr_row.is_equal_vx_vy = F::from_bool(vx == vy); 525 | 526 | self.trace.push(self.curr_row.clone()); 527 | // Copy state 528 | self.next_row.registers = self.curr_row.registers; 529 | self.next_row.index_register = self.curr_row.index_register; 530 | self.next_row.stack = self.curr_row.stack; 531 | self.next_row.stack_pointer = self.curr_row.stack_pointer; 532 | self.next_row.keypad = self.curr_row.keypad; 533 | self.next_row.stack_pointer_sel = self.curr_row.stack_pointer_sel; 534 | 535 | self.curr_row = self.next_row.clone(); 536 | self.next_row = CpuCols::default(); 537 | } 538 | } 539 | 540 | impl IncrementalTrace> { 541 | pub fn add_curr_row_to_trace(&mut self) { 542 | self.trace.push(self.curr_row.clone()); 543 | self.curr_row = self.next_row.clone(); 544 | 545 | self.next_row = KeypadCols::default(); 546 | } 547 | } 548 | 549 | impl IncrementalTrace> { 550 | pub fn add_curr_row_to_trace(&mut self) { 551 | self.trace.push(self.curr_row.clone()); 552 | // Copy state 553 | self.next_row.is_real = self.curr_row.is_real; 554 | self.next_row.clk = self.curr_row.clk; 555 | self.next_row.register_x = self.curr_row.register_x; 556 | self.next_row.register_y = self.curr_row.register_y; 557 | self.next_row.index_register = self.curr_row.index_register; 558 | 559 | self.curr_row = self.next_row.clone(); 560 | self.next_row = DrawCols::default(); 561 | } 562 | } 563 | 564 | impl IncrementalTrace> { 565 | pub fn add_curr_row_to_trace(&mut self) { 566 | self.trace.push(self.curr_row.clone()); 567 | 568 | // Copy state 569 | self.next_row.is_real = self.curr_row.is_real; 570 | self.next_row.is_start = self.curr_row.is_start; 571 | self.next_row.clk = self.curr_row.clk; 572 | self.next_row.addr = self.curr_row.addr; 573 | 574 | self.curr_row = self.next_row.clone(); 575 | self.next_row = ClearCols::default(); 576 | } 577 | } 578 | 579 | pub trait ToTraceMatrix { 580 | fn to_trace_matrix(&self, num_cols: usize) -> Option>; 581 | } 582 | 583 | impl ToTraceMatrix for Vec { 584 | // TODO: Calculate num_cols from struct 585 | fn to_trace_matrix(&self, num_cols: usize) -> Option> { 586 | if self.is_empty() { 587 | None 588 | } else { 589 | let mut trace = self.clone(); 590 | let next_power_of_two = trace.len().next_power_of_two(); 591 | trace.resize(next_power_of_two, Cols::default()); 592 | 593 | let ptr = trace.as_ptr() as *const F; 594 | let len = trace.len() * num_cols; 595 | let values = unsafe { slice::from_raw_parts(ptr, len) }; 596 | Some(RowMajorMatrix::new(values.to_vec(), num_cols)) 597 | } 598 | } 599 | } 600 | --------------------------------------------------------------------------------