Skip to content

Commit 9e643db

Browse files
authored
feat: Emu builder pattern (#10)
1 parent a81659c commit 9e643db

File tree

15 files changed

+122
-85
lines changed

15 files changed

+122
-85
lines changed

.github/workflows/rust_ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
cargo-lint:
7070
runs-on: ubuntu-latest
7171
timeout-minutes: 20
72-
name: Clippy
72+
name: Clippy (feature powerset)
7373
steps:
7474
- name: Checkout sources
7575
uses: actions/checkout@v4

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ unnameable-types = "warn"
2525
all = "warn"
2626

2727
[workspace.lints.clippy]
28-
needless-return = "allow" # Temporary fix since this is breaking in nightly clippy
2928
all = { level = "warn", priority = -1 }
3029
missing-const-for-fn = "warn"
3130
use-self = "warn"
@@ -42,7 +41,6 @@ brisc-emu = { path = "crates/hw", default-features = false }
4241
# External
4342
cfg-if = "1.0.0"
4443
hashbrown = "0.15"
45-
bitflags = { version = "2.6.0", default-features = false }
4644
thiserror = { version = "2.0.11", default-features = false }
4745
num-traits = { version = "0.2", default-features = false }
4846

crates/emu/src/cfg.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
//! Emulator configuration trait.
1+
//! Emulator type configuration trait
22
3-
use brisc_hw::{linux::SyscallInterface, memory::Memory};
3+
use brisc_hw::{kernel::Kernel, memory::Memory};
44

5-
#[allow(unused, missing_docs)]
5+
/// The [`EmuConfig`] trait defines the type configuration for the emulator.
66
pub trait EmuConfig {
77
/// The [Memory] type used by the emulator.
88
type Memory: Memory;
99

10-
/// The system call interface used by the emulator.
11-
type SyscallInterface: SyscallInterface;
12-
13-
/// External configuration for the emulator.
14-
type ExternalConfig;
10+
/// The kernel used by the emulator.
11+
type Kernel: Kernel;
1512
}

crates/emu/src/elf/load.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! ELF file loading utilities.
22
3-
use crate::{cfg::EmuConfig, st::StEmu};
43
use alloc::{
54
format,
65
string::{String, ToString},
@@ -11,21 +10,21 @@ use brisc_hw::{
1110
};
1211
use elf::{abi::PT_LOAD, endian::AnyEndian, ElfBytes};
1312

14-
/// Load a raw ELF file into a [StEmu] object.
13+
/// Load a raw ELF file into a fresh instance of [`Memory`].
1514
///
1615
/// ### Takes
1716
/// - `raw`: The raw contents of the ELF file to load.
1817
///
1918
/// ### Returns
20-
/// - `Ok(state)` if the ELF file was loaded successfully
19+
/// - `Ok((memory, entry_pc))` if the ELF file was loaded successfully
2120
/// - `Err(_)` if the ELF file could not be loaded
22-
pub fn load_elf<Config>(raw: &[u8]) -> Result<StEmu<Config>, String>
21+
pub fn load_elf<M>(raw: &[u8]) -> Result<(M, XWord), String>
2322
where
24-
Config: EmuConfig<Memory: Default, SyscallInterface: Default>,
23+
M: Memory + Default,
2524
{
2625
let elf = ElfBytes::<AnyEndian>::minimal_parse(raw)
2726
.map_err(|e| format!("Failed to parse ELF file: {e}"))?;
28-
let mut memory = Config::Memory::default();
27+
let mut memory = M::default();
2928

3029
let headers = elf.segments().ok_or("Failed to load section headers")?;
3130
for (i, header) in headers.iter().enumerate() {
@@ -73,7 +72,5 @@ where
7372
.map_err(|e| e.to_string())?;
7473
}
7574

76-
// TODO: Allow for passing a syscall interface, _or_ just make this function
77-
// initialize the memory.
78-
Ok(StEmu::new(elf.ehdr.e_entry as XWord, memory, Config::SyscallInterface::default()))
75+
Ok((memory, elf.ehdr.e_entry as XWord))
7976
}

crates/emu/src/st/builder.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! A builder for the [`StEmu`] emulator.
2+
3+
use super::StEmu;
4+
use crate::{cfg::EmuConfig, elf::load_elf};
5+
use alloc::string::String;
6+
use brisc_hw::{pipeline::PipelineRegister, XWord};
7+
8+
/// A builder for the [`StEmu`] emulator.
9+
#[derive(Debug)]
10+
pub struct StEmuBuilder<Config>
11+
where
12+
Config: EmuConfig,
13+
{
14+
/// The starting program counter.
15+
pub pc: XWord,
16+
/// The initial memory for the emulator.
17+
pub memory: Option<Config::Memory>,
18+
/// The system call interface for the emulator.
19+
pub kernel: Option<Config::Kernel>,
20+
}
21+
22+
impl<Config> Default for StEmuBuilder<Config>
23+
where
24+
Config: EmuConfig,
25+
{
26+
fn default() -> Self {
27+
Self { pc: 0, memory: None, kernel: None }
28+
}
29+
}
30+
31+
impl<Config> StEmuBuilder<Config>
32+
where
33+
Config: EmuConfig,
34+
Config::Memory: Default,
35+
{
36+
/// Loads an elf file into the emulator builder, initializing the program counter and memory.
37+
pub fn with_elf(mut self, elf_bytes: &[u8]) -> Result<Self, String> {
38+
let (memory, entry_pc) = load_elf::<Config::Memory>(elf_bytes)?;
39+
self.pc = entry_pc;
40+
self.memory = Some(memory);
41+
Ok(self)
42+
}
43+
}
44+
45+
impl<Config> StEmuBuilder<Config>
46+
where
47+
Config: EmuConfig,
48+
{
49+
/// Assigns the entry point of the program.
50+
pub const fn with_pc(mut self, pc: XWord) -> Self {
51+
self.pc = pc;
52+
self
53+
}
54+
55+
/// Assigns a pre-created memory instance to the emulator.
56+
pub fn with_memory(mut self, memory: Config::Memory) -> Self {
57+
self.memory = Some(memory);
58+
self
59+
}
60+
61+
/// Assigns the kernel to the emulator.
62+
pub fn with_kernel(mut self, kernel: Config::Kernel) -> Self {
63+
self.kernel = Some(kernel);
64+
self
65+
}
66+
67+
/// Builds the emulator with the current configuration.
68+
///
69+
/// ## Panics
70+
///
71+
/// Panics if the memory or kernel is not set.
72+
pub fn build(self) -> StEmu<Config> {
73+
StEmu {
74+
register: PipelineRegister::new(self.pc),
75+
memory: self.memory.expect("Memory not instantiated"),
76+
kernel: self.kernel.expect("Kernel not instantiated"),
77+
}
78+
}
79+
}

crates/emu/src/st/mod.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
use crate::cfg::EmuConfig;
44
use brisc_hw::{
55
errors::{PipelineError, PipelineResult},
6-
linux::SyscallInterface,
6+
kernel::Kernel,
77
pipeline::{
88
decode_instruction, execute, instruction_fetch, mem_access, writeback, PipelineRegister,
99
},
10-
XWord,
1110
};
1211

12+
mod builder;
13+
pub use builder::StEmuBuilder;
14+
1315
/// Single-cycle RISC-V processor emulator.
1416
#[derive(Debug, Default)]
1517
pub struct StEmu<Config>
@@ -21,22 +23,16 @@ where
2123
/// The device memory.
2224
pub memory: Config::Memory,
2325
/// The system call interface.
24-
pub syscall_interface: Config::SyscallInterface,
26+
pub kernel: Config::Kernel,
2527
}
2628

2729
impl<Config> StEmu<Config>
2830
where
2931
Config: EmuConfig,
3032
{
31-
/// Create a new [StEmu] with the given [Memory] and [SyscallInterface].
32-
///
33-
/// [Memory]: brisc_hw::memory::Memory
34-
pub fn new(
35-
pc: XWord,
36-
memory: Config::Memory,
37-
syscall_interface: Config::SyscallInterface,
38-
) -> Self {
39-
Self { register: PipelineRegister::new(pc), memory, syscall_interface }
33+
/// Creates a new [`StEmuBuilder`].
34+
pub fn builder() -> StEmuBuilder<Config> {
35+
StEmuBuilder::default()
4036
}
4137

4238
/// Executes the program until it exits, returning the final [PipelineRegister].
@@ -49,6 +45,7 @@ where
4945
}
5046

5147
/// Execute a single cycle of the processor in full.
48+
#[inline(always)]
5249
pub fn cycle(&mut self) -> PipelineResult<()> {
5350
let r = &mut self.register;
5451

@@ -61,16 +58,16 @@ where
6158

6259
// Handle system calls.
6360
match cycle_res {
61+
Ok(()) => {}
6462
Err(PipelineError::SyscallException(syscall_no)) => {
65-
self.syscall_interface.syscall(syscall_no, &mut self.memory, r)?;
63+
self.kernel.syscall(syscall_no, &mut self.memory, r)?;
6664

6765
// Exit emulation if the syscall terminated the program.
6866
if r.exit {
6967
return Ok(());
7068
}
7169
}
7270
Err(e) => return Err(e),
73-
_ => {}
7471
}
7572

7673
r.advance();

crates/emu/src/test_utils.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Test utilities for the emulator crate.
22
3-
use crate::{cfg::EmuConfig, elf::load_elf};
3+
use crate::{cfg::EmuConfig, st::StEmu};
44
use brisc_hw::{
55
errors::PipelineResult,
6-
linux::SyscallInterface,
6+
kernel::Kernel,
77
memory::{Memory, SimpleMemory},
8+
pipeline::PipelineRegister,
89
XWord, REG_A0,
910
};
1011
use rstest as _;
@@ -36,7 +37,11 @@ macro_rules! test_suites {
3637
pub fn run_riscv_test(test_path: &PathBuf) -> f64 {
3738
// Load the program
3839
let elf_bytes = fs::read(test_path).unwrap();
39-
let mut hart = load_elf::<TestStEmuConfig>(&elf_bytes).unwrap();
40+
let mut hart = StEmu::<TestStEmuConfig>::builder()
41+
.with_kernel(RiscvTestKernel)
42+
.with_elf(&elf_bytes)
43+
.unwrap()
44+
.build();
4045

4146
// Run the program until it exits
4247
let mut clock = 0;
@@ -45,9 +50,8 @@ pub fn run_riscv_test(test_path: &PathBuf) -> f64 {
4550
hart.cycle().unwrap();
4651
clock += 1;
4752
}
48-
let elapsed = now.elapsed();
4953

50-
let ips = clock as f64 / elapsed.as_secs_f64();
54+
let ips = clock as f64 / now.elapsed().as_secs_f64();
5155
println!("ips: {ips}");
5256

5357
// Check the exit code
@@ -68,20 +72,18 @@ struct TestStEmuConfig;
6872
impl EmuConfig for TestStEmuConfig {
6973
type Memory = SimpleMemory;
7074

71-
type SyscallInterface = RiscvTestSyscalls;
72-
73-
type ExternalConfig = ();
75+
type Kernel = RiscvTestKernel;
7476
}
7577

7678
#[derive(Default)]
77-
struct RiscvTestSyscalls;
79+
struct RiscvTestKernel;
7880

79-
impl SyscallInterface for RiscvTestSyscalls {
81+
impl Kernel for RiscvTestKernel {
8082
fn syscall<M: Memory>(
8183
&mut self,
8284
sysno: XWord,
8385
_: &mut M,
84-
p_reg: &mut brisc_hw::pipeline::PipelineRegister,
86+
p_reg: &mut PipelineRegister,
8587
) -> PipelineResult<XWord> {
8688
match sysno {
8789
0x5D => {

crates/hw/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ workspace = true
1717
brisc-isa.workspace = true
1818

1919
# External
20-
bitflags.workspace = true
2120
thiserror.workspace = true
2221
hashbrown.workspace = true
2322
cfg-if.workspace = true
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
//! Linux system call interface.
1+
//! Linux kernel interface.
22
33
use crate::{errors::PipelineResult, memory::Memory, pipeline::PipelineRegister};
44
use brisc_isa::XWord;
55

6-
/// The [SyscallInterface] trait defines the interface for performing system calls.
7-
pub trait SyscallInterface {
6+
/// The [`Kernel`] trait defines the interface for performing system calls.
7+
pub trait Kernel {
88
/// Perform a system call with the given arguments.
99
fn syscall<M: Memory>(
1010
&mut self,
@@ -14,7 +14,7 @@ pub trait SyscallInterface {
1414
) -> PipelineResult<XWord>;
1515
}
1616

17-
impl SyscallInterface for () {
17+
impl Kernel for () {
1818
fn syscall<M: Memory>(
1919
&mut self,
2020
_: XWord,

0 commit comments

Comments
 (0)