diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 50d3575..e14b5c1 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -69,7 +69,7 @@ jobs: cargo-lint: runs-on: ubuntu-latest timeout-minutes: 20 - name: Clippy + name: Clippy (feature powerset) steps: - name: Checkout sources uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index aa71b2f..34c2e53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,12 +35,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "brisc-emu" version = "0.0.1" @@ -55,7 +49,6 @@ dependencies = [ name = "brisc-hw" version = "0.0.1" dependencies = [ - "bitflags", "brisc-isa", "cfg-if", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 0fa4282..3069579 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ unnameable-types = "warn" all = "warn" [workspace.lints.clippy] -needless-return = "allow" # Temporary fix since this is breaking in nightly clippy all = { level = "warn", priority = -1 } missing-const-for-fn = "warn" use-self = "warn" @@ -42,7 +41,6 @@ brisc-emu = { path = "crates/hw", default-features = false } # External cfg-if = "1.0.0" hashbrown = "0.15" -bitflags = { version = "2.6.0", default-features = false } thiserror = { version = "2.0.11", default-features = false } num-traits = { version = "0.2", default-features = false } diff --git a/crates/emu/src/cfg.rs b/crates/emu/src/cfg.rs index e347724..d3b72fe 100644 --- a/crates/emu/src/cfg.rs +++ b/crates/emu/src/cfg.rs @@ -1,15 +1,12 @@ -//! Emulator configuration trait. +//! Emulator type configuration trait -use brisc_hw::{linux::SyscallInterface, memory::Memory}; +use brisc_hw::{kernel::Kernel, memory::Memory}; -#[allow(unused, missing_docs)] +/// The [`EmuConfig`] trait defines the type configuration for the emulator. pub trait EmuConfig { /// The [Memory] type used by the emulator. type Memory: Memory; - /// The system call interface used by the emulator. - type SyscallInterface: SyscallInterface; - - /// External configuration for the emulator. - type ExternalConfig; + /// The kernel used by the emulator. + type Kernel: Kernel; } diff --git a/crates/emu/src/elf/load.rs b/crates/emu/src/elf/load.rs index 60a65f2..f0ac18c 100644 --- a/crates/emu/src/elf/load.rs +++ b/crates/emu/src/elf/load.rs @@ -1,6 +1,5 @@ //! ELF file loading utilities. -use crate::{cfg::EmuConfig, st::StEmu}; use alloc::{ format, string::{String, ToString}, @@ -11,21 +10,21 @@ use brisc_hw::{ }; use elf::{abi::PT_LOAD, endian::AnyEndian, ElfBytes}; -/// Load a raw ELF file into a [StEmu] object. +/// Load a raw ELF file into a fresh instance of [`Memory`]. /// /// ### Takes /// - `raw`: The raw contents of the ELF file to load. /// /// ### Returns -/// - `Ok(state)` if the ELF file was loaded successfully +/// - `Ok((memory, entry_pc))` if the ELF file was loaded successfully /// - `Err(_)` if the ELF file could not be loaded -pub fn load_elf(raw: &[u8]) -> Result, String> +pub fn load_elf(raw: &[u8]) -> Result<(M, XWord), String> where - Config: EmuConfig, + M: Memory + Default, { let elf = ElfBytes::::minimal_parse(raw) .map_err(|e| format!("Failed to parse ELF file: {e}"))?; - let mut memory = Config::Memory::default(); + let mut memory = M::default(); let headers = elf.segments().ok_or("Failed to load section headers")?; for (i, header) in headers.iter().enumerate() { @@ -73,7 +72,5 @@ where .map_err(|e| e.to_string())?; } - // TODO: Allow for passing a syscall interface, _or_ just make this function - // initialize the memory. - Ok(StEmu::new(elf.ehdr.e_entry as XWord, memory, Config::SyscallInterface::default())) + Ok((memory, elf.ehdr.e_entry as XWord)) } diff --git a/crates/emu/src/st/builder.rs b/crates/emu/src/st/builder.rs new file mode 100644 index 0000000..da1423f --- /dev/null +++ b/crates/emu/src/st/builder.rs @@ -0,0 +1,79 @@ +//! A builder for the [`StEmu`] emulator. + +use super::StEmu; +use crate::{cfg::EmuConfig, elf::load_elf}; +use alloc::string::String; +use brisc_hw::{pipeline::PipelineRegister, XWord}; + +/// A builder for the [`StEmu`] emulator. +#[derive(Debug)] +pub struct StEmuBuilder +where + Config: EmuConfig, +{ + /// The starting program counter. + pub pc: XWord, + /// The initial memory for the emulator. + pub memory: Option, + /// The system call interface for the emulator. + pub kernel: Option, +} + +impl Default for StEmuBuilder +where + Config: EmuConfig, +{ + fn default() -> Self { + Self { pc: 0, memory: None, kernel: None } + } +} + +impl StEmuBuilder +where + Config: EmuConfig, + Config::Memory: Default, +{ + /// Loads an elf file into the emulator builder, initializing the program counter and memory. + pub fn with_elf(mut self, elf_bytes: &[u8]) -> Result { + let (memory, entry_pc) = load_elf::(elf_bytes)?; + self.pc = entry_pc; + self.memory = Some(memory); + Ok(self) + } +} + +impl StEmuBuilder +where + Config: EmuConfig, +{ + /// Assigns the entry point of the program. + pub const fn with_pc(mut self, pc: XWord) -> Self { + self.pc = pc; + self + } + + /// Assigns a pre-created memory instance to the emulator. + pub fn with_memory(mut self, memory: Config::Memory) -> Self { + self.memory = Some(memory); + self + } + + /// Assigns the kernel to the emulator. + pub fn with_kernel(mut self, kernel: Config::Kernel) -> Self { + self.kernel = Some(kernel); + self + } + + /// Builds the emulator with the current configuration. + /// + /// ## Panics + /// + /// Panics if the memory or kernel is not set. + pub fn build(self) -> StEmu { + StEmu { + register: PipelineRegister::new(self.pc), + memory: self.memory.expect("Memory not instantiated"), + kernel: self.kernel.expect("Kernel not instantiated"), + } + } +} diff --git a/crates/emu/src/st/mod.rs b/crates/emu/src/st/mod.rs index 9f6941b..fd70096 100644 --- a/crates/emu/src/st/mod.rs +++ b/crates/emu/src/st/mod.rs @@ -3,13 +3,15 @@ use crate::cfg::EmuConfig; use brisc_hw::{ errors::{PipelineError, PipelineResult}, - linux::SyscallInterface, + kernel::Kernel, pipeline::{ decode_instruction, execute, instruction_fetch, mem_access, writeback, PipelineRegister, }, - XWord, }; +mod builder; +pub use builder::StEmuBuilder; + /// Single-cycle RISC-V processor emulator. #[derive(Debug, Default)] pub struct StEmu @@ -21,22 +23,16 @@ where /// The device memory. pub memory: Config::Memory, /// The system call interface. - pub syscall_interface: Config::SyscallInterface, + pub kernel: Config::Kernel, } impl StEmu where Config: EmuConfig, { - /// Create a new [StEmu] with the given [Memory] and [SyscallInterface]. - /// - /// [Memory]: brisc_hw::memory::Memory - pub fn new( - pc: XWord, - memory: Config::Memory, - syscall_interface: Config::SyscallInterface, - ) -> Self { - Self { register: PipelineRegister::new(pc), memory, syscall_interface } + /// Creates a new [`StEmuBuilder`]. + pub fn builder() -> StEmuBuilder { + StEmuBuilder::default() } /// Executes the program until it exits, returning the final [PipelineRegister]. @@ -49,6 +45,7 @@ where } /// Execute a single cycle of the processor in full. + #[inline(always)] pub fn cycle(&mut self) -> PipelineResult<()> { let r = &mut self.register; @@ -61,8 +58,9 @@ where // Handle system calls. match cycle_res { + Ok(()) => {} Err(PipelineError::SyscallException(syscall_no)) => { - self.syscall_interface.syscall(syscall_no, &mut self.memory, r)?; + self.kernel.syscall(syscall_no, &mut self.memory, r)?; // Exit emulation if the syscall terminated the program. if r.exit { @@ -70,7 +68,6 @@ where } } Err(e) => return Err(e), - _ => {} } r.advance(); diff --git a/crates/emu/src/test_utils.rs b/crates/emu/src/test_utils.rs index e243334..09e99f0 100644 --- a/crates/emu/src/test_utils.rs +++ b/crates/emu/src/test_utils.rs @@ -1,10 +1,11 @@ //! Test utilities for the emulator crate. -use crate::{cfg::EmuConfig, elf::load_elf}; +use crate::{cfg::EmuConfig, st::StEmu}; use brisc_hw::{ errors::PipelineResult, - linux::SyscallInterface, + kernel::Kernel, memory::{Memory, SimpleMemory}, + pipeline::PipelineRegister, XWord, REG_A0, }; use rstest as _; @@ -36,7 +37,11 @@ macro_rules! test_suites { pub fn run_riscv_test(test_path: &PathBuf) -> f64 { // Load the program let elf_bytes = fs::read(test_path).unwrap(); - let mut hart = load_elf::(&elf_bytes).unwrap(); + let mut hart = StEmu::::builder() + .with_kernel(RiscvTestKernel) + .with_elf(&elf_bytes) + .unwrap() + .build(); // Run the program until it exits let mut clock = 0; @@ -45,9 +50,8 @@ pub fn run_riscv_test(test_path: &PathBuf) -> f64 { hart.cycle().unwrap(); clock += 1; } - let elapsed = now.elapsed(); - let ips = clock as f64 / elapsed.as_secs_f64(); + let ips = clock as f64 / now.elapsed().as_secs_f64(); println!("ips: {ips}"); // Check the exit code @@ -68,20 +72,18 @@ struct TestStEmuConfig; impl EmuConfig for TestStEmuConfig { type Memory = SimpleMemory; - type SyscallInterface = RiscvTestSyscalls; - - type ExternalConfig = (); + type Kernel = RiscvTestKernel; } #[derive(Default)] -struct RiscvTestSyscalls; +struct RiscvTestKernel; -impl SyscallInterface for RiscvTestSyscalls { +impl Kernel for RiscvTestKernel { fn syscall( &mut self, sysno: XWord, _: &mut M, - p_reg: &mut brisc_hw::pipeline::PipelineRegister, + p_reg: &mut PipelineRegister, ) -> PipelineResult { match sysno { 0x5D => { diff --git a/crates/hw/Cargo.toml b/crates/hw/Cargo.toml index d0651d2..2583fa8 100644 --- a/crates/hw/Cargo.toml +++ b/crates/hw/Cargo.toml @@ -17,7 +17,6 @@ workspace = true brisc-isa.workspace = true # External -bitflags.workspace = true thiserror.workspace = true hashbrown.workspace = true cfg-if.workspace = true diff --git a/crates/hw/src/linux.rs b/crates/hw/src/kernel.rs similarity index 74% rename from crates/hw/src/linux.rs rename to crates/hw/src/kernel.rs index 86b1d22..e74620b 100644 --- a/crates/hw/src/linux.rs +++ b/crates/hw/src/kernel.rs @@ -1,10 +1,10 @@ -//! Linux system call interface. +//! Linux kernel interface. use crate::{errors::PipelineResult, memory::Memory, pipeline::PipelineRegister}; use brisc_isa::XWord; -/// The [SyscallInterface] trait defines the interface for performing system calls. -pub trait SyscallInterface { +/// The [`Kernel`] trait defines the interface for performing system calls. +pub trait Kernel { /// Perform a system call with the given arguments. fn syscall( &mut self, @@ -14,7 +14,7 @@ pub trait SyscallInterface { ) -> PipelineResult; } -impl SyscallInterface for () { +impl Kernel for () { fn syscall( &mut self, _: XWord, diff --git a/crates/hw/src/lib.rs b/crates/hw/src/lib.rs index f599301..745423b 100644 --- a/crates/hw/src/lib.rs +++ b/crates/hw/src/lib.rs @@ -8,7 +8,7 @@ extern crate alloc; pub mod errors; -pub mod linux; +pub mod kernel; pub mod memory; pub mod pipeline; diff --git a/crates/hw/src/pipeline/decode.rs b/crates/hw/src/pipeline/decode.rs index ea29eb9..00d78a0 100644 --- a/crates/hw/src/pipeline/decode.rs +++ b/crates/hw/src/pipeline/decode.rs @@ -23,8 +23,6 @@ pub fn decode_instruction(register: &mut PipelineRegister) -> PipelineResult<()> // Set the decoded instruction in the pipeline register. register.instruction = Some(instruction); - // TODO: Assign control signals. - // Throw an interrupt if the instruction is a system call. if instruction.is_system_call() { return Err(PipelineError::SyscallException(register.registers[REG_A7 as usize])); diff --git a/crates/hw/src/pipeline/execute.rs b/crates/hw/src/pipeline/execute.rs index df94ea6..46d3765 100644 --- a/crates/hw/src/pipeline/execute.rs +++ b/crates/hw/src/pipeline/execute.rs @@ -49,8 +49,7 @@ pub fn execute(p_reg: &mut PipelineRegister) -> PipelineResult<()> { } Instruction::Environment(_i_type, funct) => { if matches!(funct, EnvironmentFunction::Ecall) { - // TODO: Fix; Needs to be a0 - return Err(PipelineError::SyscallException(p_reg.registers[0])); + unreachable!("Ecall should be handled in the decode stage"); } else { // no-op EBREAK operations. 0 diff --git a/crates/hw/src/pipeline/register.rs b/crates/hw/src/pipeline/register.rs index e8b99a3..39c6852 100644 --- a/crates/hw/src/pipeline/register.rs +++ b/crates/hw/src/pipeline/register.rs @@ -1,28 +1,8 @@ //! Pipeline registers and control signals. use crate::memory::Address; -use bitflags::bitflags; use brisc_isa::{Instruction, Word, XWord}; -bitflags! { - /// Represents control signals given to an instruction during its traversal through the pipeline. - #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct ControlSignals: u8 { - /// Memory was read. - const MEM_READ = 0b0000_0001; - /// Memory was written to. - const MEM_WRITE = 0b0000_0010; - /// A register was written to. - const REG_WRITE = 0b0000_0100; - /// Memory was read into a register. - const MEM_TO_REG = Self::MEM_READ.bits() | Self::REG_WRITE.bits(); - /// A branch was taken. - const BRANCH = 0b0000_1000; - /// A jump was taken. - const JUMP = 0b0001_0000; - } -} - /// The [PipelineRegister] represents an intermediate state of an instruction's execution within /// the CPU pipeline. As the [PipelineRegister] passes through each stage, the type is saturated. /// Ultimately, it is discarded after it has made its way through the register write-back stage @@ -51,8 +31,6 @@ pub struct PipelineRegister { pub immediate: Option, /// The cached `rd` register index. pub rd: Option, - /// The control signals given to the instruction during its execution. - pub control_signals: ControlSignals, /// The result of the ALU computation, if one occurred. pub alu_result: Option, /// The data read from memory, if any. diff --git a/crates/isa/src/instructions/rvc/mod.rs b/crates/isa/src/instructions/rvc/mod.rs index a6ba692..a6299a7 100644 --- a/crates/isa/src/instructions/rvc/mod.rs +++ b/crates/isa/src/instructions/rvc/mod.rs @@ -201,7 +201,7 @@ impl C1 { // In 64-bit mode, C.ADDIW is used instead of C.JAL. if #[cfg(feature = "64-bit")] { if rs1_rd == 0 { - return Err(InstructionDecodeError::InvalidFunction { q_a: funct3, q_b: 0 }); + Err(InstructionDecodeError::InvalidFunction { q_a: funct3, q_b: 0 }) } else { Ok(Self::CAddiw(CIType::decode(instruction))) }