Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,54 @@ jobs:
RUST_LOG=info FRI_QUERIES=1 cargo run --release --example test_riscv -- --field m31
RUST_LOG=info FRI_QUERIES=1 cargo run --release --example test_riscv -- --elf precompile --field m31
RUST_LOG=info FRI_QUERIES=1 cargo run --release --example test_riscv -- --elf poseidon2 --field m31 --n 2118082624

riscof:
if: true
name: RISCOF Arch Tests
runs-on: ubuntu-latest
steps:
- name: Configure git for private repo
run: |
git config --global url."https://${{ secrets.GH_TOKEN }}:@github.com/".insteadOf "https://github.com/"
- name: Checkout pico sources
uses: actions/checkout@v4
with:
path: pico
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2025-08-04
override: true
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: pico
- name: Build pico-cli
run: |
cd pico
cargo build --release -p pico-cli
- name: Checkout zkevm-test-monitor
uses: actions/checkout@v4
with:
repository: eth-act/zkevm-test-monitor
path: zkevm-test-monitor
- name: Setup pico binary for RISCOF
run: |
mkdir -p zkevm-test-monitor/binaries
cp pico/target/release/cargo-pico zkevm-test-monitor/binaries/pico-binary
chmod +x zkevm-test-monitor/binaries/pico-binary
- name: Run RISCOF arch tests
run: |
cd zkevm-test-monitor
./src/test.sh --arch pico
- name: Display test results
if: always()
run: |
cd zkevm-test-monitor
if [ -f "test-results/pico/summary-arch.json" ]; then
echo "📊 RISCOF Test Results:"
cat test-results/pico/summary-arch.json | jq '.'
else
echo "⚠️ No test results found"
fi
6 changes: 5 additions & 1 deletion sdk/cli/src/bin/cargo-pico.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::Result;
use clap::{crate_version, Parser, Subcommand};
use pico_cli::subcommand::{build::BuildCmd, new::NewCmd, prove::ProveCmd};
use pico_cli::subcommand::{
build::BuildCmd, new::NewCmd, prove::ProveCmd, test_emulator::TestEmulatorCmd,
};
use pico_sdk::init_logger;

#[derive(Parser)]
Expand All @@ -21,6 +23,7 @@ pub enum SubCommands {
Build(BuildCmd),
Prove(ProveCmd),
New(NewCmd),
TestEmulator(TestEmulatorCmd),
}

fn main() -> Result<()> {
Expand All @@ -32,5 +35,6 @@ fn main() -> Result<()> {
SubCommands::Build(cmd) => cmd.run(),
SubCommands::Prove(cmd) => cmd.run(),
SubCommands::New(cmd) => cmd.run(),
SubCommands::TestEmulator(cmd) => cmd.run(),
}
}
1 change: 1 addition & 0 deletions sdk/cli/src/subcommand/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod build;
pub mod new;
pub mod prove;
pub mod test_emulator;
57 changes: 57 additions & 0 deletions sdk/cli/src/subcommand/test_emulator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use anyhow::Result;
use clap::Parser;
use log::debug;
use std::{fs::File, io::Write, path::PathBuf};

use pico_vm::compiler::riscv::disassembler::find_signature_region;

#[derive(Parser)]
#[command(
name = "test-emulator",
about = "Execute ELF execution and collect RISCOF signatures"
)]
pub struct TestEmulatorCmd {
#[clap(long, help = "ELF file path")]
elf: String,

#[clap(long, help = "Output signature file path")]
signatures: PathBuf,
}

impl TestEmulatorCmd {
pub fn run(&self) -> Result<()> {
// Read ELF bytes
let elf_bytes = std::fs::read(&self.elf)?;

// Parse symbols to find begin_signature and end_signature
let (begin, end) = find_signature_region(&elf_bytes)
.map_err(|e| anyhow::anyhow!("Failed to parse ELF symbols: {}", e))?;

debug!(
"Found signature region: begin=0x{:08x}, end=0x{:08x}",
begin, end
);

// Create RiscvProver directly (hard-coded to KoalaBear for RISCOF)
use pico_vm::{
configs::{config::StarkGenericConfig, stark_config::KoalaBearPoseidon2},
proverchain::{InitialProverSetup, RiscvProver},
};
let prover = RiscvProver::new_initial_prover(
(KoalaBearPoseidon2::new(), &elf_bytes),
Default::default(),
None,
);

// Collect signatures
let signatures = prover.test_emulator(begin, end);

// Write signatures to file
let mut file = File::create(&self.signatures)?;
for sig in signatures {
writeln!(file, "{:08x}", sig)?;
}

Ok(())
}
}
26 changes: 26 additions & 0 deletions vm/src/compiler/riscv/disassembler/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ pub struct Elf {
pub(crate) memory_image: Arc<BTreeMap<u32, u32>>,
}

/// Parse symbol table from ELF to extract signature region boundary for RISCOF testing.
pub fn find_signature_region(elf_bytes: &[u8]) -> eyre::Result<(u32, u32)> {
let mut begin_signature = None;
let mut end_signature = None;

let file = ElfBytes::<LittleEndian>::minimal_parse(elf_bytes)?;

if let Some((symbol_table, string_table)) = file.symbol_table()? {
for sym in symbol_table.iter() {
if let Ok(name) = string_table.get(sym.st_name as usize) {
match name {
"begin_signature" => begin_signature = Some(sym.st_value as u32),
"end_signature" => end_signature = Some(sym.st_value as u32),
_ => {}
}
}
}
}

let begin =
begin_signature.ok_or_else(|| eyre::eyre!("Missing begin_signature symbol in ELF"))?;
let end = end_signature.ok_or_else(|| eyre::eyre!("Missing end_signature symbol in ELF"))?;

Ok((begin, end))
}

impl Elf {
/// Create a new [Elf].
///
Expand Down
3 changes: 3 additions & 0 deletions vm/src/compiler/riscv/disassembler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ mod rrs;

pub(crate) use elf::*;
pub(crate) use rrs::*;

// Public re-export for RISCOF testing
pub use elf::find_signature_region;
18 changes: 18 additions & 0 deletions vm/src/emulator/riscv/emulator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,24 @@ impl RiscvEmulator {
));
}
}

/// Collect signatures from memory between the given begin and end addresses
pub fn collect_signatures(&mut self, begin: u32, end: u32) -> Vec<u32> {
if begin >= end {
return Vec::new();
}

let size = (end - begin) as usize;
let mut signatures = Vec::with_capacity(size / 4);

for offset in (0..size).step_by(4) {
let addr = begin + offset as u32;
let word = self.word(addr);
signatures.push(word);
}

signatures
}
}

#[cfg(test)]
Expand Down
34 changes: 34 additions & 0 deletions vm/src/proverchain/riscv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,40 @@ where
(reports, pv_stream)
}

/// Emulate and collect RISCOF signatures from memory between the given addresses
pub fn test_emulator(&self, begin: u32, end: u32) -> Vec<u32> {
use crate::emulator::riscv::riscv_emulator::RiscvEmulator;

let mut emulator =
RiscvEmulator::new_single::<Val<SC>>(self.program.clone(), self.opts, None);

loop {
let mut batch_records = vec![];
let result = emulator.emulate_batch(&mut |record| batch_records.push(record));

match result {
Ok(report) => {
if report.done {
// Normal completion
break;
} else {
// Continue execution
continue;
}
}
Err(err) => {
tracing::error!(
?err,
"test_emulator: emulate_batch failed, will still try to collect signatures"
);
break;
}
}
}

emulator.collect_signatures(begin, end)
}

pub fn get_program(&self) -> Arc<Program> {
self.program.clone()
}
Expand Down
Loading