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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/emu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ workspace = true
brisc-hw.workspace = true

# External
thiserror.workspace = true
elf = { version = "0.8.0", default-features = false }

# `test-utils` feature
Expand Down
9 changes: 7 additions & 2 deletions crates/emu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,15 @@ const HELLO_WORLD_ELF: &str = "7f454c460201010000000000000000000200f30001000000e
struct ExampleKernel;

impl Kernel for ExampleKernel {
type Error = ();

fn syscall<M: Memory>(
&mut self,
sysno: XWord,
mem: &mut M,
p_reg: &mut PipelineRegister,
) -> PipelineResult<XWord> {
_context: &mut ()
) -> Result<XWord, Self::Error> {
match sysno {
0x5D => {
let exit_code = p_reg.registers[REG_A0 as usize];
Expand Down Expand Up @@ -95,14 +98,16 @@ impl Kernel for ExampleKernel {
#[derive(Default)]
struct ExampleEmuConfig;

impl EmuConfig for ExampleEmuConfig {
impl EmuConfig<'_> for ExampleEmuConfig {
type Memory = SimpleMemory;
type Kernel = ExampleKernel;
type Context = ();
}

let elf = const_hex::decode(HELLO_WORLD_ELF).unwrap();
let mut emu = StEmu::<ExampleEmuConfig>::builder()
.with_kernel(ExampleKernel)
.with_ctx(())
.with_elf(&elf)
.unwrap()
.build();
Expand Down
9 changes: 6 additions & 3 deletions crates/emu/src/cfg.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//! Emulator type configuration trait

use brisc_hw::{kernel::Kernel, memory::Memory};
use brisc_hw::memory::Memory;

/// The [`EmuConfig`] trait defines the type configuration for the emulator.
pub trait EmuConfig {
pub trait EmuConfig<'ctx> {
/// The [Memory] type used by the emulator.
type Memory: Memory;

/// The kernel used by the emulator.
type Kernel: Kernel;
type Kernel;

/// The external state passed to the kernel.
type Context: 'ctx;
}
29 changes: 19 additions & 10 deletions crates/emu/src/st/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,32 @@ use brisc_hw::{pipeline::PipelineRegister, XWord};

/// A builder for the [`StEmu`] emulator.
#[derive(Debug)]
pub struct StEmuBuilder<Config>
pub struct StEmuBuilder<'ctx, Config>
where
Config: EmuConfig,
Config: EmuConfig<'ctx>,
{
/// The starting program counter.
pub pc: XWord,
/// The initial memory for the emulator.
pub memory: Option<Config::Memory>,
/// The system call interface for the emulator.
pub kernel: Option<Config::Kernel>,
/// The emulator's external context.
pub ctx: Option<Config::Context>,
}

impl<Config> Default for StEmuBuilder<Config>
impl<'ctx, Config> Default for StEmuBuilder<'ctx, Config>
where
Config: EmuConfig,
Config: EmuConfig<'ctx>,
{
fn default() -> Self {
Self { pc: 0, memory: None, kernel: None }
Self { pc: 0, memory: None, kernel: None, ctx: None }
}
}

impl<Config> StEmuBuilder<Config>
impl<'ctx, Config> StEmuBuilder<'ctx, Config>
where
Config: EmuConfig,
Config: EmuConfig<'ctx>,
Config::Memory: Default,
{
/// Loads an elf file into the emulator builder, initializing the program counter and memory.
Expand All @@ -42,9 +44,9 @@ where
}
}

impl<Config> StEmuBuilder<Config>
impl<'ctx, Config> StEmuBuilder<'ctx, Config>
where
Config: EmuConfig,
Config: EmuConfig<'ctx>,
{
/// Assigns the entry point of the program.
pub const fn with_pc(mut self, pc: XWord) -> Self {
Expand All @@ -64,16 +66,23 @@ where
self
}

/// Assigns the context to the emulator.
pub fn with_ctx(mut self, ctx: Config::Context) -> Self {
self.ctx = Some(ctx);
self
}

/// Builds the emulator with the current configuration.
///
/// ## Panics
///
/// Panics if the memory or kernel is not set.
pub fn build(self) -> StEmu<Config> {
pub fn build(self) -> StEmu<'ctx, Config> {
StEmu {
register: PipelineRegister::new(self.pc),
memory: self.memory.expect("Memory not instantiated"),
kernel: self.kernel.expect("Kernel not instantiated"),
ctx: self.ctx.expect("Context not instantiated"),
}
}
}
110 changes: 99 additions & 11 deletions crates/emu/src/st/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,72 @@

use crate::cfg::EmuConfig;
use brisc_hw::{
errors::{PipelineError, PipelineResult},
kernel::Kernel,
errors::PipelineError,
kernel::{AsyncKernel, Kernel},
pipeline::{
decode_instruction, execute, instruction_fetch, mem_access, writeback, PipelineRegister,
},
};
use thiserror::Error;

mod builder;
pub use builder::StEmuBuilder;

/// An error that can occur during emulation.
#[derive(Error, Debug)]
pub enum EmulationError<E> {
/// An error that occurred in the pipeline.
#[error(transparent)]
Pipeline(#[from] PipelineError),

/// An error that occurred in the kernel.
#[error(transparent)]
Kernel(E),
}

/// A [`Result`] type aslias for emulation results.
pub type EmulationResult<T, KernelError> = Result<T, EmulationError<KernelError>>;

/// Single-cycle RISC-V processor emulator.
#[derive(Debug, Default)]
pub struct StEmu<Config>
pub struct StEmu<'ctx, Config>
where
Config: EmuConfig,
Config: EmuConfig<'ctx>,
{
/// The pipeline register.
pub register: PipelineRegister,
/// The device memory.
pub memory: Config::Memory,
/// The system call interface.
pub kernel: Config::Kernel,
/// The emulator's context.
pub ctx: Config::Context,
}

impl<Config> StEmu<Config>
impl<'ctx, Config> StEmu<'ctx, Config>
where
Config: EmuConfig,
Config: EmuConfig<'ctx>,
{
/// Creates a new [`StEmuBuilder`].
pub fn builder() -> StEmuBuilder<Config> {
pub fn builder() -> StEmuBuilder<'ctx, Config> {
StEmuBuilder::default()
}

/// Destroys the emulator and returns the context.
pub fn take_ctx(self) -> Config::Context {
self.ctx
}
}

impl<'ctx, Config> StEmu<'ctx, Config>
where
Config: EmuConfig<'ctx>,
Config::Kernel: Kernel<Config::Context> + 'ctx,
{
/// Executes the program until it exits, returning the final [PipelineRegister].
pub fn run(&mut self) -> PipelineResult<PipelineRegister> {
pub fn run(
&mut self,
) -> EmulationResult<PipelineRegister, <Config::Kernel as Kernel<Config::Context>>::Error> {
while !self.register.exit {
self.cycle()?;
}
Expand All @@ -46,7 +77,61 @@ where

/// Execute a single cycle of the processor in full.
#[inline(always)]
pub fn cycle(&mut self) -> PipelineResult<()> {
pub fn cycle(
&mut self,
) -> EmulationResult<(), <Config::Kernel as Kernel<Config::Context>>::Error> {
let r = &mut self.register;

// Execute all pipeline stages sequentially.
let cycle_res = instruction_fetch(r, &self.memory)
.and_then(|_| decode_instruction(r))
.and_then(|_| execute(r))
.and_then(|_| mem_access(r, &mut self.memory))
.and_then(|_| writeback(r));

// Handle system calls.
match cycle_res {
Ok(()) => {}
Err(PipelineError::SyscallException(syscall_no)) => {
self.kernel
.syscall(syscall_no, &mut self.memory, r, &mut self.ctx)
.map_err(EmulationError::Kernel)?;

// Exit emulation if the syscall terminated the program.
if r.exit {
return Ok(());
}
}
Err(e) => return Err(e.into()),
}

r.advance();
Ok(())
}
}

impl<'ctx, Config> StEmu<'ctx, Config>
where
Config: EmuConfig<'ctx>,
Config::Kernel: AsyncKernel<Config::Context> + 'ctx,
{
/// Executes the program until it exits, returning the final [PipelineRegister].
pub async fn run_async(
&mut self,
) -> EmulationResult<PipelineRegister, <Config::Kernel as AsyncKernel<Config::Context>>::Error>
{
while !self.register.exit {
self.cycle_async().await?;
}

Ok(self.register)
}

/// Execute a single cycle of the processor in full.
#[inline(always)]
pub async fn cycle_async(
&mut self,
) -> EmulationResult<(), <Config::Kernel as AsyncKernel<Config::Context>>::Error> {
let r = &mut self.register;

// Execute all pipeline stages sequentially.
Expand All @@ -60,14 +145,17 @@ where
match cycle_res {
Ok(()) => {}
Err(PipelineError::SyscallException(syscall_no)) => {
self.kernel.syscall(syscall_no, &mut self.memory, r)?;
self.kernel
.syscall(syscall_no, &mut self.memory, r, &mut self.ctx)
.await
.map_err(EmulationError::Kernel)?;

// Exit emulation if the syscall terminated the program.
if r.exit {
return Ok(());
}
}
Err(e) => return Err(e),
Err(e) => return Err(e.into()),
}

r.advance();
Expand Down
23 changes: 14 additions & 9 deletions crates/emu/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use crate::{cfg::EmuConfig, st::StEmu};
use brisc_hw::{
errors::PipelineResult,
kernel::Kernel,
memory::{Memory, SimpleMemory},
pipeline::PipelineRegister,
Expand Down Expand Up @@ -48,6 +47,7 @@ pub fn run_riscv_test(test_path: &PathBuf) -> f64 {
let elf_bytes = fs::read(test_path).unwrap();
let mut hart = StEmu::<TestStEmuConfig>::builder()
.with_kernel(RiscvTestKernel)
.with_ctx(())
.with_elf(&elf_bytes)
.unwrap()
.build();
Expand Down Expand Up @@ -78,32 +78,37 @@ pub fn run_riscv_test(test_path: &PathBuf) -> f64 {
#[derive(Default)]
struct TestStEmuConfig;

impl EmuConfig for TestStEmuConfig {
impl EmuConfig<'_> for TestStEmuConfig {
type Memory = SimpleMemory;

type Kernel = RiscvTestKernel;

type Context = ();
}

#[derive(Default)]
#[derive(Default, Debug)]
struct RiscvTestKernel;

impl Kernel for RiscvTestKernel {
impl Kernel<()> for RiscvTestKernel {
type Error = ();

fn syscall<M: Memory>(
&mut self,
sysno: XWord,
mem: &mut M,
p_reg: &mut PipelineRegister,
) -> PipelineResult<XWord> {
_: &mut (),
) -> Result<XWord, Self::Error> {
match sysno {
0x5D => {
let exit_code = p_reg.registers[REG_A0 as usize];
let exit_code = p_reg.registers[REG_A0];
p_reg.exit_code = exit_code;
p_reg.exit = true;
}
0x40 => {
let fd = p_reg.registers[REG_A0 as usize];
let ptr = p_reg.registers[REG_A1 as usize];
let len = p_reg.registers[REG_A2 as usize];
let fd = p_reg.registers[REG_A0];
let ptr = p_reg.registers[REG_A1];
let len = p_reg.registers[REG_A2];

let raw_msg = mem.read_memory_range(ptr, len).unwrap();
let msg = String::from_utf8_lossy(&raw_msg);
Expand Down
Loading