From ea4d64aa019b4cc1fdc342580a87a546f47b610c Mon Sep 17 00:00:00 2001 From: Jim Keener Date: Fri, 30 May 2025 23:24:19 -0400 Subject: [PATCH 1/5] Modularize the bus. Add example of usage. --- examples/jumplinkhandler/main.rs | 70 +++++++ src/bus.rs | 129 ++++++------ src/cpu.rs | 166 +++++++++++---- src/csr.rs | 9 +- src/devices/clint.rs | 28 ++- src/{ => devices}/dram.rs | 64 +++--- src/devices/mod.rs | 2 + src/devices/plic.rs | 39 +++- src/{ => devices}/rom.rs | 58 ++++-- src/devices/uart_cli.rs | 62 ++++-- src/devices/uart_wasm.rs | 51 +++-- src/devices/virtio_blk.rs | 348 ++++++++++++++++--------------- src/emulator.rs | 55 ++++- src/lib.rs | 2 - 14 files changed, 701 insertions(+), 382 deletions(-) create mode 100644 examples/jumplinkhandler/main.rs rename src/{ => devices}/dram.rs (78%) rename src/{ => devices}/rom.rs (92%) diff --git a/examples/jumplinkhandler/main.rs b/examples/jumplinkhandler/main.rs new file mode 100644 index 0000000..be5a12f --- /dev/null +++ b/examples/jumplinkhandler/main.rs @@ -0,0 +1,70 @@ +extern crate rvemu; + +use rvemu::cpu; +use rvemu::cpu::{Cpu, FRegisters, JumpLinkHandler, XRegisters}; +use rvemu::devices::dram::Dram; +use rvemu::exception::Exception; + +pub const SPF_MIN_ADDR: u64 = 0x800_0000; +pub const SPF_FABS: u64 = 0x800_bc40; +pub const SPF_FCN: u64 = 0x800_0400; + +pub const DRAM_BASE: u64 = 0x800_0000; +pub const DRAM_SIZE: u64 = 0xff; + +struct SysProvided {} + +const SPF_FCN_TARGET_VALUE: f64 = 654.321; + +impl JumpLinkHandler for SysProvided { + fn should_handle(&self, new_pc: u64) -> bool { + new_pc >= SPF_MIN_ADDR + } + fn handle(&self, new_pc: u64, cpu: &Cpu) -> (XRegisters, FRegisters) { + let xregs = cpu.xregs.clone(); + let mut fregs = cpu.fregs.clone(); + match new_pc { + SPF_FABS => { + let f0 = fregs.read(cpu::REG_FA0); + fregs.write(cpu::REG_FA0, f0.abs()); + } + SPF_FCN => { + fregs.write(cpu::REG_FA1, SPF_FCN_TARGET_VALUE); + } + _ => {} + } + + (xregs, fregs) + } +} + +fn main() -> Result<(), Exception> { + #[rustfmt::skip] + let prog: Vec = vec![ + // 80000000: f2000553 fmv.d.x fa0,zero + 0x53, 0x05, 0x00, 0xf2, + // 80000004: 3fc000ef jal 80000400 + 0xef, 0x00, 0xc0, 0x3f, + ]; + + let jh = SysProvided {}; + let mut cpu = Cpu::new(); + cpu.with_jump_link_handler(Box::new(jh)); + let mut dram = Box::new(Dram::new(DRAM_SIZE)); + dram.initialize(prog); + cpu.bus.mount(DRAM_BASE, dram); + + cpu.reset(); + cpu.pc = DRAM_BASE; + cpu.xregs.write(cpu::REG_SP, DRAM_BASE + DRAM_SIZE); + + println!("instr: {:x?}", cpu.cycle()?); + println!("instr: {:x?}", cpu.cycle()?); + cpu.print_registers(); + + println!( + "Was the handler successful? {:?}", + cpu.fregs.read(cpu::REG_FA1) == SPF_FCN_TARGET_VALUE + ); + Ok(()) +} diff --git a/src/bus.rs b/src/bus.rs index 33ce2bf..32240f3 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,104 +1,109 @@ //! The bus module contains the system bus which can access the memroy or memory-mapped peripheral //! devices. -use crate::devices::{clint::Clint, plic::Plic, uart::Uart, virtio_blk::Virtio}; -use crate::dram::{Dram, DRAM_SIZE}; +use crate::devices::clint::Clint; +use crate::devices::plic::Plic; use crate::exception::Exception; -use crate::rom::Rom; -// QEMU virt machine: -// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c#L46-L63 - -/// The address which the mask ROM starts. -pub const MROM_BASE: u64 = 0x1000; -/// The address which the mask ROM ends. -const MROM_END: u64 = MROM_BASE + 0xf000; +use std::ops::RangeInclusive; /// The address which the core-local interruptor (CLINT) starts. It contains the timer and generates /// per-hart software interrupts and timer interrupts. pub const CLINT_BASE: u64 = 0x200_0000; -/// The address which the core-local interruptor (CLINT) ends. -const CLINT_END: u64 = CLINT_BASE + 0x10000; /// The address which the platform-level interrupt controller (PLIC) starts. The PLIC connects all /// external interrupts in the system to all hart contexts in the system, via the external interrupt /// source in each hart. pub const PLIC_BASE: u64 = 0xc00_0000; -/// The address which the platform-level interrupt controller (PLIC) ends. -const PLIC_END: u64 = PLIC_BASE + 0x208000; - -/// The address which UART starts. QEMU puts UART registers here in physical memory. -pub const UART_BASE: u64 = 0x1000_0000; -/// The size of UART. -pub const UART_SIZE: u64 = 0x100; -/// The address which UART ends. -const UART_END: u64 = UART_BASE + 0x100; - -/// The address which virtio starts. -pub const VIRTIO_BASE: u64 = 0x1000_1000; -/// The address which virtio ends. -const VIRTIO_END: u64 = VIRTIO_BASE + 0x1000; - -/// The address which DRAM starts. -pub const DRAM_BASE: u64 = 0x8000_0000; -/// The address which DRAM ends. -const DRAM_END: u64 = DRAM_BASE + DRAM_SIZE; + +pub trait Device { + /// Load `size`-bit data from the memory. + fn read(&mut self, addr: u64, size: u8) -> Result; + /// Store `size`-bit data to the memory. + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception>; + + fn size(&self) -> u64; + + fn is_interrupting(&mut self) -> bool; + + fn irq(&self) -> Option; + + fn reset(&mut self); +} /// The system bus. pub struct Bus { + devices: Vec<(RangeInclusive, Box)>, + pub clint: Clint, pub plic: Plic, - pub uart: Uart, - pub virtio: Virtio, - dram: Dram, - pub rom: Rom, + clint_addr_range: RangeInclusive, + plic_addr_range: RangeInclusive, } impl Bus { /// Create a new bus object. pub fn new() -> Bus { + let clint = Clint::new(); + let plic = Plic::new(); Self { - clint: Clint::new(), - plic: Plic::new(), - uart: Uart::new(), - virtio: Virtio::new(), - dram: Dram::new(), - rom: Rom::new(), + devices: vec![], + clint_addr_range: (CLINT_BASE..=(CLINT_BASE + clint.size())), + plic_addr_range: (PLIC_BASE..=(PLIC_BASE + plic.size())), + clint: clint, + plic: plic, } } - /// Set the binary data to the memory. - pub fn initialize_dram(&mut self, data: Vec) { - self.dram.initialize(data); + pub fn is_interrupting(&mut self) -> bool { + let mut saw_interrupt = false; + for (_, device) in self.devices.iter_mut() { + if device.is_interrupting() { + if let Some(irq) = device.irq() { + self.plic.update_pending(irq); + saw_interrupt = true; + } + } + } + saw_interrupt } - /// Set the binary data to the virtIO disk. - pub fn initialize_disk(&mut self, data: Vec) { - self.virtio.initialize(data); + pub fn mount(&mut self, memory_start: u64, device: Box) { + let addr_range = memory_start..=(memory_start + device.size()); + self.devices.push((addr_range, device)); } /// Load a `size`-bit data from the device that connects to the system bus. pub fn read(&mut self, addr: u64, size: u8) -> Result { - match addr { - MROM_BASE..=MROM_END => self.rom.read(addr, size), - CLINT_BASE..=CLINT_END => self.clint.read(addr, size), - PLIC_BASE..=PLIC_END => self.plic.read(addr, size), - UART_BASE..=UART_END => self.uart.read(addr, size), - VIRTIO_BASE..=VIRTIO_END => self.virtio.read(addr, size), - DRAM_BASE..=DRAM_END => self.dram.read(addr, size), - _ => Err(Exception::LoadAccessFault), + if self.clint_addr_range.contains(&addr) { + self.clint.read(addr - self.clint_addr_range.start(), size) + } else if self.plic_addr_range.contains(&addr) { + self.plic.read(addr - self.plic_addr_range.start(), size) + } else { + for (addr_range, device) in self.devices.iter_mut() { + if addr_range.contains(&addr) { + return device.read(addr - addr_range.start(), size); + } + } + Err(Exception::LoadAccessFault) } } /// Store a `size`-bit data to the device that connects to the system bus. pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { - match addr { - CLINT_BASE..=CLINT_END => self.clint.write(addr, value, size), - PLIC_BASE..=PLIC_END => self.plic.write(addr, value, size), - UART_BASE..=UART_END => self.uart.write(addr, value as u8, size), - VIRTIO_BASE..=VIRTIO_END => self.virtio.write(addr, value as u32, size), - DRAM_BASE..=DRAM_END => self.dram.write(addr, value, size), - _ => Err(Exception::StoreAMOAccessFault), + if self.clint_addr_range.contains(&addr) { + self.clint + .write(addr - self.clint_addr_range.start(), value, size) + } else if self.plic_addr_range.contains(&addr) { + self.plic + .write(addr - self.plic_addr_range.start(), value, size) + } else { + for (addr_range, device) in self.devices.iter_mut() { + if addr_range.contains(&addr) { + return device.write(addr - addr_range.start(), value, size); + } + } + Err(Exception::StoreAMOAccessFault) } } } diff --git a/src/cpu.rs b/src/cpu.rs index 759dc40..a5e3040 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -6,17 +6,73 @@ use std::collections::BTreeMap; use std::fmt; use std::num::FpCategory; -use crate::{ - bus::{Bus, DRAM_BASE}, - csr::*, - devices::{ - uart::UART_IRQ, - virtio_blk::{Virtio, VIRTIO_IRQ}, - }, - dram::DRAM_SIZE, - exception::Exception, - interrupt::Interrupt, -}; +use crate::{bus::Bus, csr::*, exception::Exception, interrupt::Interrupt}; + +pub const REG_FT0: u64 = 0; +pub const REG_FT1: u64 = 1; +pub const REG_FT2: u64 = 2; +pub const REG_FT3: u64 = 3; +pub const REG_FT4: u64 = 4; +pub const REG_FT5: u64 = 5; +pub const REG_FT6: u64 = 6; +pub const REG_FT7: u64 = 7; +pub const REG_FS0: u64 = 8; +pub const REG_FS1: u64 = 9; +pub const REG_FA0: u64 = 10; +pub const REG_FA1: u64 = 11; +pub const REG_FA2: u64 = 12; +pub const REG_FA3: u64 = 13; +pub const REG_FA4: u64 = 14; +pub const REG_FA5: u64 = 15; +pub const REG_FA6: u64 = 16; +pub const REG_FA7: u64 = 17; +pub const REG_FS2: u64 = 18; +pub const REG_FS3: u64 = 19; +pub const REG_FS4: u64 = 20; +pub const REG_FS5: u64 = 21; +pub const REG_FS6: u64 = 22; +pub const REG_FS7: u64 = 23; +pub const REG_FS8: u64 = 24; +pub const REG_FS9: u64 = 25; +pub const REG_FS10: u64 = 26; +pub const REG_FS11: u64 = 27; +pub const REG_FT8: u64 = 28; +pub const REG_FT9: u64 = 29; +pub const REG_FT10: u64 = 30; +pub const REG_FT11: u64 = 31; + +pub const REG_ZERO: u64 = 0; +pub const REG_RA: u64 = 1; +pub const REG_SP: u64 = 2; +pub const REG_GP: u64 = 3; +pub const REG_TP: u64 = 4; +pub const REG_T0: u64 = 5; +pub const REG_T1: u64 = 6; +pub const REG_T2: u64 = 7; +pub const REG_S0: u64 = 8; +pub const REG_S1: u64 = 9; +pub const REG_A0: u64 = 10; +pub const REG_A1: u64 = 11; +pub const REG_A2: u64 = 12; +pub const REG_A3: u64 = 13; +pub const REG_A4: u64 = 14; +pub const REG_A5: u64 = 15; +pub const REG_A6: u64 = 16; +pub const REG_A7: u64 = 17; +pub const REG_S2: u64 = 18; +pub const REG_S3: u64 = 19; +pub const REG_S4: u64 = 20; +pub const REG_S5: u64 = 21; +pub const REG_S6: u64 = 22; +pub const REG_S7: u64 = 23; +pub const REG_S8: u64 = 24; +pub const REG_S9: u64 = 25; +pub const REG_S10: u64 = 26; +pub const REG_S11: u64 = 27; +pub const REG_T3: u64 = 28; +pub const REG_T4: u64 = 29; +pub const REG_T5: u64 = 30; +pub const REG_T6: u64 = 31; /// The number of registers. pub const REGISTERS_COUNT: usize = 32; @@ -66,7 +122,7 @@ pub enum Mode { } /// The integer registers. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct XRegisters { xregs: [u64; REGISTERS_COUNT], } @@ -75,8 +131,6 @@ impl XRegisters { /// Create a new `XRegisters` object. pub fn new() -> Self { let mut xregs = [0; REGISTERS_COUNT]; - // The stack pointer is set in the default maximum memory size + the start address of dram. - xregs[2] = DRAM_BASE + DRAM_SIZE; // From riscv-pk: // https://github.com/riscv/riscv-pk/blob/master/machine/mentry.S#L233-L235 // save a0 and a1; arguments from previous boot loader stage: @@ -141,7 +195,7 @@ impl fmt::Display for XRegisters { } /// The floating-point registers. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FRegisters { fregs: [f64; REGISTERS_COUNT], } @@ -209,6 +263,11 @@ impl fmt::Display for FRegisters { } } +pub trait JumpLinkHandler { + fn should_handle(&self, new_pc: u64) -> bool; + fn handle(&self, new_pc: u64, cpu: &Cpu) -> (XRegisters, FRegisters); +} + /// The CPU to contain registers, a program counter, status, and a privileged mode. pub struct Cpu { /// 64-bit integer registers. @@ -223,6 +282,9 @@ pub struct Cpu { pub mode: Mode, /// System bus. pub bus: Bus, + + pub jump_handler: Option>, + /// SV39 paging flag. enable_paging: bool, /// Physical page number (PPN) × PAGE_SIZE (4096). @@ -240,6 +302,10 @@ pub struct Cpu { pub pre_inst: u64, } +// impl fmt::Debug for Cpu { +// +// } + impl Cpu { /// Create a new `Cpu` object. pub fn new() -> Cpu { @@ -250,6 +316,7 @@ impl Cpu { state: State::new(), mode: Mode::Machine, bus: Bus::new(), + jump_handler: None, enable_paging: false, page_table: 0, reservation_set: Vec::new(), @@ -260,6 +327,10 @@ impl Cpu { } } + pub fn with_jump_link_handler(&mut self, jh: Box) { + self.jump_handler = Some(jh); + } + fn debug(&self, _inst: u64, _name: &str) { /* if (((0x20_0000_0000 & self.pc) >> 37) == 1) && (self.pc & 0xf0000000_00000000) == 0 { @@ -314,6 +385,19 @@ impl Cpu { } } + pub fn print_registers(&self) { + println!("{}", self.xregs); + println!("{}", self.fregs); + println!("{}", self.state); + println!("pc: {:#x} prev_instr: {:#x}", self.pc, self.pre_inst); + } + + pub fn cycle(&mut self) -> Result<(Option, u64), Exception> { + let interrupt = self.check_pending_interrupt(); + self.devices_increment(); + Ok((interrupt, self.execute()?)) + } + /// Check interrupt flags for all devices that can interrupt. pub fn check_pending_interrupt(&mut self) -> Option { // global interrupt: PLIC (Platform Local Interrupt Controller) dispatches global @@ -343,21 +427,9 @@ impl Cpu { // TODO: Take interrupts based on priorities. // Check external interrupt for uart and virtio. - let irq; - if self.bus.uart.is_interrupting() { - irq = UART_IRQ; - } else if self.bus.virtio.is_interrupting() { - // An interrupt is raised after a disk access is done. - Virtio::disk_access(self).expect("failed to access the disk"); - irq = VIRTIO_IRQ; - } else { - irq = 0; - } - - if irq != 0 { + if self.bus.is_interrupting() { // TODO: assume that hart is 0 // TODO: write a value to MCLAIM if the mode is machine - self.bus.plic.update_pending(irq); self.state.write(MIP, self.state.read(MIP) | SEIP_BIT); } @@ -1237,9 +1309,15 @@ impl Cpu { self.debug(inst, "c.jalr"); let rs1 = (inst >> 7) & 0x1f; - let t = self.pc.wrapping_add(2); - self.pc = self.xregs.read(rs1).wrapping_sub(2); - self.xregs.write(1, t); + let new_pc = self.xregs.read(rs1).wrapping_sub(2); + if let Some(jh) = &self.jump_handler { + if jh.should_handle(new_pc) { + (self.xregs, self.fregs) = jh.handle(new_pc, self); + } + } else { + self.pc = new_pc; + self.xregs.write(1, self.pc.wrapping_add(2)); + } } } (1, _) => { @@ -3287,29 +3365,39 @@ impl Cpu { inst_count!(self, "jalr"); self.debug(inst, "jalr"); - let t = self.pc.wrapping_add(4); - let offset = (inst as i32 as i64) >> 20; let target = ((self.xregs.read(rs1) as i64).wrapping_add(offset)) & !1; - self.pc = (target as u64).wrapping_sub(4); - - self.xregs.write(rd, t); + let new_pc = target as u64; + if let Some(jh) = &self.jump_handler { + if jh.should_handle(new_pc) { + (self.xregs, self.fregs) = jh.handle(new_pc, self); + } + } else { + self.pc = new_pc; + self.xregs.write(rd, self.pc.wrapping_add(4)); + } } 0x6F => { // jal inst_count!(self, "jal"); self.debug(inst, "jal"); - self.xregs.write(rd, self.pc.wrapping_add(4)); - // imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12] let offset = (((inst & 0x80000000) as i32 as i64 >> 11) as u64) // imm[20] | (inst & 0xff000) // imm[19:12] | ((inst >> 9) & 0x800) // imm[11] | ((inst >> 20) & 0x7fe); // imm[10:1] - self.pc = self.pc.wrapping_add(offset).wrapping_sub(4); + let new_pc = self.pc.wrapping_add(offset); + if let Some(jh) = &self.jump_handler { + if jh.should_handle(new_pc) { + (self.xregs, self.fregs) = jh.handle(new_pc, self); + } + } else { + self.pc = new_pc; + self.xregs.write(rd, self.pc.wrapping_add(4)); + } } 0x73 => { // RV32I, RVZicsr, and supervisor ISA diff --git a/src/csr.rs b/src/csr.rs index a2634ef..8e6e804 100644 --- a/src/csr.rs +++ b/src/csr.rs @@ -180,9 +180,9 @@ impl fmt::Display for State { f, "{}", format!( - "{}\n{}\n{}", + "{}{}{}", format!( - "mstatus={:>#18x} mtvec={:>#18x} mepc={:>#18x}\n mcause={:>#18x} medeleg={:>#18x} mideleg={:>#18x}", + "mstatus={:>#18x} mtvec={:>#18x} mepc={:>#18x} mcause={:>#18x}\nmedeleg={:>#18x} mideleg={:>#18x}", self.read(MSTATUS), self.read(MTVEC), self.read(MEPC), @@ -191,7 +191,7 @@ impl fmt::Display for State { self.read(MIDELEG), ), format!( - "sstatus={:>#18x} stvec={:>#18x} sepc={:>#18x}\n scause={:>#18x} sedeleg={:>#18x} sideleg={:>#18x}", + "sstatus={:>#18x} stvec={:>#18x}\n sepc={:>#18x} scause={:>#18x} sedeleg={:>#18x} sideleg={:>#18x}\n", self.read(SSTATUS), self.read(STVEC), self.read(SEPC), @@ -200,11 +200,12 @@ impl fmt::Display for State { self.read(SIDELEG), ), format!( - "ustatus={:>#18x} utvec={:>#18x} uepc={:>#18x}\n ucause={:>#18x}", + "ustatus={:>#18x} utvec={:>#18x} uepc={:>#18x} ucause={:>#18x}\n time={:>#18x}", self.read(USTATUS), self.read(UTVEC), self.read(UEPC), self.read(UCAUSE), + self.read(TIME), ), ) ) diff --git a/src/devices/clint.rs b/src/devices/clint.rs index 14ee554..f5dd38c 100644 --- a/src/devices/clint.rs +++ b/src/devices/clint.rs @@ -12,26 +12,27 @@ // - https://github.com/qemu/qemu/blob/master/hw/intc/sifive_clint.c // - https://github.com/qemu/qemu/blob/master/include/hw/intc/sifive_clint.h -use crate::bus::CLINT_BASE; use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::csr::{State, MIP, MSIP_BIT, MTIP_BIT}; use crate::exception::Exception; +use crate::bus::Device; + /// The address that a msip register starts. A msip is a machine mode software interrupt pending /// register, used to assert a software interrupt for a CPU. -const MSIP: u64 = CLINT_BASE; +const MSIP: u64 = 0; /// The address that a msip register ends. `msip` is a 4-byte register. const MSIP_END: u64 = MSIP + 0x4; /// The address that a mtimecmp register starts. A mtimecmp is a memory mapped machine mode timer /// compare register, used to trigger an interrupt when mtimecmp is greater than or equal to mtime. -const MTIMECMP: u64 = CLINT_BASE + 0x4000; +const MTIMECMP: u64 = 0x4000; /// The address that a mtimecmp register ends. `mtimecmp` is a 8-byte register. const MTIMECMP_END: u64 = MTIMECMP + 0x8; /// The address that a timer register starts. A mtime is a machine mode timer register which runs /// at a constant frequency. -const MTIME: u64 = CLINT_BASE + 0xbff8; +const MTIME: u64 = 0xbff8; /// The address that a timer register ends. `mtime` is a 8-byte register. const MTIME_END: u64 = MTIME + 0x8; @@ -88,9 +89,24 @@ impl Clint { state.write(MIP, state.read(MIP) | MTIP_BIT); } } +} +impl Device for Clint { + fn size(&self) -> u64 { + 0x10000 + } + + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None + } + + fn reset(&mut self) {} /// Load `size`-bit data from a register located at `addr` in CLINT. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { // `reg` is the value of a target register in CLINT and `offset` is the byte of the start // position in the register. let (reg, offset) = match addr { @@ -110,7 +126,7 @@ impl Clint { } /// Store `size`-bit data to a register located at `addr` in CLINT. - pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { // `reg` is the value of a target register in CLINT and `offset` is the byte of the start // position in the register. let (mut reg, offset) = match addr { diff --git a/src/dram.rs b/src/devices/dram.rs similarity index 78% rename from src/dram.rs rename to src/devices/dram.rs index f5673a2..9554db6 100644 --- a/src/dram.rs +++ b/src/devices/dram.rs @@ -1,47 +1,43 @@ //! The memory module contains the memory structure and implementation to read/write the memory. -use crate::bus::DRAM_BASE; +use crate::bus::Device; use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::exception::Exception; -/// Default memory size (1GiB). -pub const DRAM_SIZE: u64 = 1024 * 1024 * 1024; - /// The memory used by the emulator. #[derive(Debug)] pub struct Dram { pub dram: Vec, - code_size: u64, } -impl Dram { - /// Create a new memory object with default memory size. - pub fn new() -> Self { - Self { - dram: vec![0; DRAM_SIZE as usize], - code_size: 0, - } +impl Device for Dram { + fn size(&self) -> u64 { + self.dram.len() as u64 } - /// Set the binary in the memory. - pub fn initialize(&mut self, binary: Vec) { - self.code_size = binary.len() as u64; - self.dram.splice(..binary.len(), binary.iter().cloned()); + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None } + fn reset(&mut self) {} + /// Load `size`-bit data from the memory. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { match size { BYTE => Ok(self.read8(addr)), HALFWORD => Ok(self.read16(addr)), WORD => Ok(self.read32(addr)), DOUBLEWORD => Ok(self.read64(addr)), - _ => return Err(Exception::LoadAccessFault), + _ => Err(Exception::LoadAccessFault), } } /// Store `size`-bit data to the memory. - pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { match size { BYTE => self.write8(addr, value), HALFWORD => self.write16(addr, value), @@ -51,23 +47,37 @@ impl Dram { } Ok(()) } +} + +impl Dram { + /// Create a new memory object + pub fn new(size: u64) -> Self { + Self { + dram: vec![0; size as usize], + } + } + + /// Set the binary in the memory. + pub fn initialize(&mut self, binary: Vec) { + self.dram.splice(..binary.len(), binary.iter().cloned()); + } /// Write a byte to the memory. fn write8(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = val as u8 } /// Write 2 bytes to the memory with little endian. fn write16(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = (val & 0xff) as u8; self.dram[index + 1] = ((val >> 8) & 0xff) as u8; } /// Write 4 bytes to the memory with little endian. fn write32(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = (val & 0xff) as u8; self.dram[index + 1] = ((val >> 8) & 0xff) as u8; self.dram[index + 2] = ((val >> 16) & 0xff) as u8; @@ -76,7 +86,7 @@ impl Dram { /// Write 8 bytes to the memory with little endian. fn write64(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = (val & 0xff) as u8; self.dram[index + 1] = ((val >> 8) & 0xff) as u8; self.dram[index + 2] = ((val >> 16) & 0xff) as u8; @@ -89,19 +99,19 @@ impl Dram { /// Read a byte from the memory. fn read8(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] as u64 } /// Read 2 bytes from the memory with little endian. fn read16(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; return (self.dram[index] as u64) | ((self.dram[index + 1] as u64) << 8); } /// Read 4 bytes from the memory with little endian. fn read32(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; return (self.dram[index] as u64) | ((self.dram[index + 1] as u64) << 8) | ((self.dram[index + 2] as u64) << 16) @@ -110,7 +120,7 @@ impl Dram { /// Read 8 bytes from the memory with little endian. fn read64(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; return (self.dram[index] as u64) | ((self.dram[index + 1] as u64) << 8) | ((self.dram[index + 2] as u64) << 16) diff --git a/src/devices/mod.rs b/src/devices/mod.rs index b5f391a..42239bf 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,7 +1,9 @@ //! The devices module contains peripheral devices. pub mod clint; +pub mod dram; pub mod plic; +pub mod rom; pub mod virtio_blk; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/devices/plic.rs b/src/devices/plic.rs index 2d0ee44..f107637 100644 --- a/src/devices/plic.rs +++ b/src/devices/plic.rs @@ -12,23 +12,24 @@ // - https://github.com/qemu/qemu/blob/master/hw/intc/sifive_plic.c // - https://github.com/qemu/qemu/blob/master/include/hw/intc/sifive_plic.h -use crate::bus::PLIC_BASE; use crate::cpu::WORD; use crate::exception::Exception; +use crate::bus::Device; + /// The address for interrupt source priority. 1024 4-byte registers exist. Each interrupt into the /// PLIC has a configurable priority, from 1-7, with 7 being the highest priority. A value of 0 /// means do not interrupt, effectively disabling that interrupt. -const SOURCE_PRIORITY: u64 = PLIC_BASE; -const SOURCE_PRIORITY_END: u64 = PLIC_BASE + 0xfff; +const SOURCE_PRIORITY: u64 = 0; +const SOURCE_PRIORITY_END: u64 = 0 + 0xfff; /// The address range for interrupt pending bits. 32 4-byte (1024 bits) registers exist. /// /// https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc#memory-map /// base + 0x001000: Interrupt Pending bit 0-31 /// base + 0x00107C: Interrupt Pending bit 992-1023 -const PENDING: u64 = PLIC_BASE + 0x1000; -const PENDING_END: u64 = PLIC_BASE + 0x107f; +const PENDING: u64 = 0 + 0x1000; +const PENDING_END: u64 = 0 + 0x107f; /// The address range for enable registers. The maximum number of contexts is 15871 but this PLIC /// supports only 2 contexts. @@ -42,8 +43,8 @@ const PENDING_END: u64 = PLIC_BASE + 0x107f; /// base + 0x002084: Enable bits for sources 32-63 on context 1 /// ... /// base + 0x0020FF: Enable bits for sources 992-1023 on context 1 -const ENABLE: u64 = PLIC_BASE + 0x2000; -const ENABLE_END: u64 = PLIC_BASE + 0x20ff; +const ENABLE: u64 = 0 + 0x2000; +const ENABLE_END: u64 = 0 + 0x20ff; /// The address range for priority thresholds and claim/complete registers. The maximum number of /// contexts is 15871 but this PLIC supports only 2 contexts. @@ -56,8 +57,8 @@ const ENABLE_END: u64 = PLIC_BASE + 0x20ff; /// base + 0x200FFC: Reserved /// base + 0x201000: Priority threshold for context 1 /// base + 0x201004: Claim/complete for context 1 -const THRESHOLD_AND_CLAIM: u64 = PLIC_BASE + 0x200000; -const THRESHOLD_AND_CLAIM_END: u64 = PLIC_BASE + 0x201007; +const THRESHOLD_AND_CLAIM: u64 = 0 + 0x200000; +const THRESHOLD_AND_CLAIM_END: u64 = 0 + 0x201007; const WORD_SIZE: u64 = 0x4; const CONTEXT_OFFSET: u64 = 0x1000; @@ -125,9 +126,25 @@ impl Plic { let offset = (irq.wrapping_rem(SOURCE_NUM)).wrapping_rem(WORD_SIZE * 8); return ((self.enable[(context * 32 + index) as usize] >> offset) & 1) == 1; } +} + +impl Device for Plic { + fn size(&self) -> u64 { + 0x208000 + } + + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None + } + + fn reset(&mut self) {} /// Load `size`-bit data from a register located at `addr` in PLIC. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { // TODO: should support byte-base access. if size != WORD { return Err(Exception::LoadAccessFault); @@ -171,7 +188,7 @@ impl Plic { } /// Store `size`-bit data to a register located at `addr` in PLIC. - pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { // TODO: should support byte-base access. if size != WORD { return Err(Exception::StoreAMOAccessFault); diff --git a/src/rom.rs b/src/devices/rom.rs similarity index 92% rename from src/rom.rs rename to src/devices/rom.rs index 3542f53..48c331b 100644 --- a/src/rom.rs +++ b/src/devices/rom.rs @@ -1,6 +1,6 @@ //! The rom module contains the read-only memory structure and implementation to read the memory. ROM includes a device tree blob (DTB) compiled from a device tree source (DTS). -use crate::bus::MROM_BASE; +use crate::bus::Device; use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::exception::Exception; @@ -134,6 +134,38 @@ pub struct Rom { data: Vec, } +impl Device for Rom { + fn size(&self) -> u64 { + 0xf000 + } + + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None + } + + fn reset(&mut self) {} + + /// Load `size`-bit data from the memory. + fn read(&mut self, addr: u64, size: u8) -> Result { + match size { + BYTE => Ok(self.read8(addr)), + HALFWORD => Ok(self.read16(addr)), + WORD => Ok(self.read32(addr)), + DOUBLEWORD => Ok(self.read64(addr)), + _ => return Err(Exception::LoadAccessFault), + } + } + + /// Store `size`-bit data to the memory. Returns the exception because the ROM is read-only. + fn write(&mut self, _addr: u64, _value: u64, _size: u8) -> Result<(), Exception> { + Err(Exception::StoreAMOAccessFault) + } +} + impl Rom { /// Create a new `rom` object. pub fn new() -> Self { @@ -163,37 +195,21 @@ impl Rom { Rom { data } } - /// Load `size`-bit data from the memory. - pub fn read(&self, addr: u64, size: u8) -> Result { - match size { - BYTE => Ok(self.read8(addr)), - HALFWORD => Ok(self.read16(addr)), - WORD => Ok(self.read32(addr)), - DOUBLEWORD => Ok(self.read64(addr)), - _ => return Err(Exception::LoadAccessFault), - } - } - - /// Store `size`-bit data to the memory. Returns the exception because the ROM is read-only. - pub fn write(&self, _addr: u64, _value: u64, _size: u8) -> Result<(), Exception> { - Err(Exception::StoreAMOAccessFault) - } - /// Read a byte from the rom. fn read8(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; self.data[index] as u64 } /// Read 2 bytes from the rom. fn read16(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; return (self.data[index] as u64) | ((self.data[index + 1] as u64) << 8); } /// Read 4 bytes from the rom. fn read32(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; return (self.data[index] as u64) | ((self.data[index + 1] as u64) << 8) | ((self.data[index + 2] as u64) << 16) @@ -202,7 +218,7 @@ impl Rom { /// Read 8 bytes from the rom. fn read64(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; return (self.data[index] as u64) | ((self.data[index + 1] as u64) << 8) | ((self.data[index + 2] as u64) << 16) diff --git a/src/devices/uart_cli.rs b/src/devices/uart_cli.rs index 97dee47..2cb6a33 100644 --- a/src/devices/uart_cli.rs +++ b/src/devices/uart_cli.rs @@ -10,29 +10,33 @@ use std::sync::{ }; use std::thread; -use crate::bus::{UART_BASE, UART_SIZE}; use crate::cpu::BYTE; use crate::exception::Exception; +use crate::bus::Device; + +/// The size of UART. +pub const UART_SIZE: u64 = 0x100; + /// The interrupt request of UART. pub const UART_IRQ: u64 = 10; /// Receive holding register (for input bytes). -const UART_RHR: u64 = UART_BASE + 0; +const UART_RHR: u64 = 0 + 0; /// Transmit holding register (for output bytes). -const UART_THR: u64 = UART_BASE + 0; +const UART_THR: u64 = 0 + 0; /// Interrupt enable register. -const _UART_IER: u64 = UART_BASE + 1; +const _UART_IER: u64 = 0 + 1; /// FIFO control register. -const _UART_FCR: u64 = UART_BASE + 2; +const _UART_FCR: u64 = 0 + 2; /// Interrupt status register. /// ISR BIT-0: /// 0 = an interrupt is pending and the ISR contents may be used as a pointer to the appropriate /// interrupt service routine. /// 1 = no interrupt is pending. -const _UART_ISR: u64 = UART_BASE + 2; +const _UART_ISR: u64 = 0 + 2; /// Line control register. -const _UART_LCR: u64 = UART_BASE + 3; +const _UART_LCR: u64 = 0 + 3; /// Line status register. /// LSR BIT 0: /// 0 = no data in receive holding register or FIFO. @@ -40,7 +44,7 @@ const _UART_LCR: u64 = UART_BASE + 3; /// LSR BIT 5: /// 0 = transmit holding register is full. 16550 will not accept any data for transmission. /// 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. -const UART_LSR: u64 = UART_BASE + 5; +const UART_LSR: u64 = 0 + 5; /// The receiver (RX). const UART_LSR_RX: u8 = 1; @@ -51,18 +55,19 @@ const UART_LSR_TX: u8 = 1 << 5; pub struct Uart { uart: Arc<(Mutex<[u8; UART_SIZE as usize]>, Condvar)>, interrupting: Arc, + irq: u64, } impl Uart { /// Create a new UART object. - pub fn new() -> Self { + pub fn new(irq: u64) -> Self { let uart = Arc::new((Mutex::new([0; UART_SIZE as usize]), Condvar::new())); let interrupting = Arc::new(AtomicBool::new(false)); { let (uart, _cvar) = &*uart; let mut uart = uart.lock().expect("failed to get an UART object"); // Transmitter hold register is empty. It allows input anytime. - uart[(UART_LSR - UART_BASE) as usize] |= UART_LSR_TX; + uart[(UART_LSR - 0) as usize] |= UART_LSR_TX; } // Create a new thread for waiting for input. @@ -75,13 +80,13 @@ impl Uart { let (uart, cvar) = &*cloned_uart; let mut uart = uart.lock().expect("failed to get an UART object"); // Wait for the thread to start up. - while (uart[(UART_LSR - UART_BASE) as usize] & UART_LSR_RX) == 1 { + while (uart[(UART_LSR - 0) as usize] & UART_LSR_RX) == 1 { uart = cvar.wait(uart).expect("the mutex is poisoned"); } uart[0] = byte[0]; cloned_interrupting.store(true, Ordering::Release); // Data has been receive. - uart[(UART_LSR - UART_BASE) as usize] |= UART_LSR_RX; + uart[(UART_LSR - 0) as usize] |= UART_LSR_RX; } Err(e) => { println!("input via UART is error: {}", e); @@ -89,16 +94,32 @@ impl Uart { } }); - Self { uart, interrupting } + Self { + uart, + interrupting, + irq, + } + } +} + +impl Device for Uart { + fn size(&self) -> u64 { + UART_SIZE } /// Return true if an interrupt is pending. Clear the interrupting flag by swapping a value. - pub fn is_interrupting(&self) -> bool { + fn is_interrupting(&mut self) -> bool { self.interrupting.swap(false, Ordering::Acquire) } + fn irq(&self) -> Option { + Some(self.irq) + } + + fn reset(&mut self) {} + /// Read a byte from the receive holding register. - pub fn read(&mut self, index: u64, size: u8) -> Result { + fn read(&mut self, index: u64, size: u8) -> Result { if size != BYTE { return Err(Exception::LoadAccessFault); } @@ -108,15 +129,16 @@ impl Uart { match index { UART_RHR => { cvar.notify_one(); - uart[(UART_LSR - UART_BASE) as usize] &= !UART_LSR_RX; - Ok(uart[(UART_RHR - UART_BASE) as usize] as u64) + uart[(UART_LSR - 0) as usize] &= !UART_LSR_RX; + Ok(uart[(UART_RHR - 0) as usize] as u64) } - _ => Ok(uart[(index - UART_BASE) as usize] as u64), + _ => Ok(uart[(index - 0) as usize] as u64), } } /// Write a byte to the transmit holding register. - pub fn write(&mut self, index: u64, value: u8, size: u8) -> Result<(), Exception> { + fn write(&mut self, index: u64, value: u64, size: u8) -> Result<(), Exception> { + let value = (value & 0xff) as u8; if size != BYTE { return Err(Exception::StoreAMOAccessFault); } @@ -139,7 +161,7 @@ impl Uart { io::stdout().flush().expect("failed to flush stdout"); } _ => { - uart[(index - UART_BASE) as usize] = value; + uart[(index - 0) as usize] = value; } } Ok(()) diff --git a/src/devices/uart_wasm.rs b/src/devices/uart_wasm.rs index 14efec6..c42615b 100644 --- a/src/devices/uart_wasm.rs +++ b/src/devices/uart_wasm.rs @@ -6,7 +6,6 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; use web_sys::Window; -use crate::bus::{UART_BASE, UART_SIZE}; use crate::cpu::BYTE; use crate::exception::Exception; @@ -16,25 +15,28 @@ extern "C" { fn log(s: &str); } +/// The size of UART. +pub const UART_SIZE: u64 = 0x100; + /// The interrupt request of UART. pub const UART_IRQ: u64 = 10; /// Receive holding register (for input bytes). -const UART_RHR: u64 = UART_BASE + 0; +const UART_RHR: u64 = 0 + 0; /// Transmit holding register (for output bytes). -const UART_THR: u64 = UART_BASE + 0; +const UART_THR: u64 = 0 + 0; /// Interrupt enable register. -const _UART_IER: u64 = UART_BASE + 1; +const _UART_IER: u64 = 0 + 1; /// FIFO control register. -const _UART_FCR: u64 = UART_BASE + 2; +const _UART_FCR: u64 = 0 + 2; /// Interrupt status register. /// ISR BIT-0: /// 0 = an interrupt is pending and the ISR contents may be used as a pointer to the appropriate /// interrupt service routine. /// 1 = no interrupt is pending. -const UART_ISR: u64 = UART_BASE + 2; +const UART_ISR: u64 = 0 + 2; /// Line control register. -const _UART_LCR: u64 = UART_BASE + 3; +const _UART_LCR: u64 = 0 + 3; /// Line status register. /// LSR BIT 0: /// 0 = no data in receive holding register or FIFO. @@ -42,7 +44,7 @@ const _UART_LCR: u64 = UART_BASE + 3; /// LSR BIT 6: /// 0 = transmitter holding and shift registers are full. /// 1 = transmit holding register is empty. In FIFO mode this bit is set to one whenever the the transmitter FIFO and transmit shift register are empty. -const UART_LSR: u64 = UART_BASE + 5; +const UART_LSR: u64 = 0 + 5; fn get_input(window: &Window) -> u8 { let document = window.document().expect("failed to get a document object"); @@ -70,22 +72,29 @@ pub struct Uart { clock: u64, not_null: bool, window: web_sys::Window, + irq: u64, } impl Uart { /// Create a new UART object. - pub fn new() -> Self { + pub fn new(irq: u64) -> Self { let mut uart = [0; UART_SIZE as usize]; - uart[(UART_ISR - UART_BASE) as usize] |= 1; - uart[(UART_LSR - UART_BASE) as usize] |= 1 << 5; + uart[(UART_ISR - 0) as usize] |= 1; + uart[(UART_LSR - 0) as usize] |= 1 << 5; Self { uart, clock: 0, not_null: false, window: web_sys::window().expect("failed to get a global window object"), + irq, } } +} +impl Device for Uart { + fn size(&self) -> u64 { + UART_SIZE + } /// Return true if the byte buffer in UART is full. pub fn is_interrupting(&mut self) -> bool { @@ -99,7 +108,7 @@ impl Uart { return false; } self.uart[0] = b; - self.uart[(UART_LSR - UART_BASE) as usize] |= 1; + self.uart[(UART_LSR - 0) as usize] |= 1; // Found a byte in this step, so it might find a byte again in the next step. self.not_null = true; return true; @@ -107,23 +116,29 @@ impl Uart { false } + fn irq(&self) -> Option { + Some(self.irq) + } + + fn reset(&mut self) {} + /// Read a byte from the receive holding register. - pub fn read(&mut self, index: u64, size: u8) -> Result { + fn read(&mut self, index: u64, size: u8) -> Result { if size != BYTE { return Err(Exception::LoadAccessFault); } match index { UART_RHR => { - self.uart[(UART_LSR - UART_BASE) as usize] &= !1; - Ok(self.uart[(index - UART_BASE) as usize] as u64) + self.uart[(UART_LSR - 0) as usize] &= !1; + Ok(self.uart[(index - 0) as usize] as u64) } - _ => Ok(self.uart[(index - UART_BASE) as usize] as u64), + _ => Ok(self.uart[(index - 0) as usize] as u64), } } /// Write a byte to the transmit holding register. - pub fn write(&mut self, index: u64, value: u8, size: u8) -> Result<(), Exception> { + fn write(&mut self, index: u64, value: u8, size: u8) -> Result<(), Exception> { if size != BYTE { return Err(Exception::StoreAMOAccessFault); } @@ -135,7 +150,7 @@ impl Uart { .expect("failed to post message"); } _ => { - self.uart[(index - UART_BASE) as usize] = value; + self.uart[(index - 0) as usize] = value; } } Ok(()) diff --git a/src/devices/virtio_blk.rs b/src/devices/virtio_blk.rs index 86260a4..3549d2b 100644 --- a/src/devices/virtio_blk.rs +++ b/src/devices/virtio_blk.rs @@ -5,8 +5,8 @@ //! 5.2 Block Device: //! https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.html#x1-2390002 -use crate::bus::VIRTIO_BASE; -use crate::cpu::{Cpu, BYTE, DOUBLEWORD, HALFWORD, WORD}; +use crate::bus::Device; +use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::exception::Exception; /// The interrupt request of virtio. @@ -29,70 +29,70 @@ const _VIRTQ_DESC_F_INDIRECT: u64 = 4; // 4.2.2 MMIO Device Register Layout // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 /// Magic value. Always return 0x74726976 (a Little Endian equivalent of the "virt" string). -const MAGIC: u64 = VIRTIO_BASE; -const MAGIC_END: u64 = VIRTIO_BASE + 0x3; +const MAGIC: u64 = 0; +const MAGIC_END: u64 = 0 + 0x3; /// Device version number. 1 is legacy. -const VERSION: u64 = VIRTIO_BASE + 0x4; -const VERSION_END: u64 = VIRTIO_BASE + 0x7; +const VERSION: u64 = 0 + 0x4; +const VERSION_END: u64 = 0 + 0x7; /// Virtio Subsystem Device ID. 1 is network, 2 is block device. -const DEVICE_ID: u64 = VIRTIO_BASE + 0x8; -const DEVICE_ID_END: u64 = VIRTIO_BASE + 0xb; +const DEVICE_ID: u64 = 0 + 0x8; +const DEVICE_ID_END: u64 = 0 + 0xb; /// Virtio Subsystem Vendor ID. Always return 0x554d4551 -const VENDOR_ID: u64 = VIRTIO_BASE + 0xc; -const VENDOR_ID_END: u64 = VIRTIO_BASE + 0xf; +const VENDOR_ID: u64 = 0 + 0xc; +const VENDOR_ID_END: u64 = 0 + 0xf; /// Flags representing features the device supports. Access to this register returns bits /// DeviceFeaturesSel ∗ 32 to (DeviceFeaturesSel ∗ 32) + 31. -const DEVICE_FEATURES: u64 = VIRTIO_BASE + 0x10; -const DEVICE_FEATURES_END: u64 = VIRTIO_BASE + 0x13; +const DEVICE_FEATURES: u64 = 0 + 0x10; +const DEVICE_FEATURES_END: u64 = 0 + 0x13; /// Device (host) features word selection. -const DEVICE_FEATURES_SEL: u64 = VIRTIO_BASE + 0x14; -const DEVICE_FEATURES_SEL_END: u64 = VIRTIO_BASE + 0x17; +const DEVICE_FEATURES_SEL: u64 = 0 + 0x14; +const DEVICE_FEATURES_SEL_END: u64 = 0 + 0x17; /// Flags representing device features understood and activated by the driver. Access to this /// register sets bits DriverFeaturesSel ∗ 32 to (DriverFeaturesSel ∗ 32) + 31. -const DRIVER_FEATURES: u64 = VIRTIO_BASE + 0x20; -const DRIVER_FEATURES_END: u64 = VIRTIO_BASE + 0x23; +const DRIVER_FEATURES: u64 = 0 + 0x20; +const DRIVER_FEATURES_END: u64 = 0 + 0x23; /// Activated (guest) features word selection. -const DRIVER_FEATURES_SEL: u64 = VIRTIO_BASE + 0x24; -const DRIVER_FEATURES_SEL_END: u64 = VIRTIO_BASE + 0x27; +const DRIVER_FEATURES_SEL: u64 = 0 + 0x24; +const DRIVER_FEATURES_SEL_END: u64 = 0 + 0x27; // 4.2.4 Legacy interface // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1560004 /// Guest page size. The driver writes the guest page size in bytes to the register during /// initialization, before any queues are used. This value should be a power of 2 and is used by /// the device to calculate the Guest address of the first queue page. Write-only. -const GUEST_PAGE_SIZE: u64 = VIRTIO_BASE + 0x28; -const GUEST_PAGE_SIZE_END: u64 = VIRTIO_BASE + 0x2b; +const GUEST_PAGE_SIZE: u64 = 0 + 0x28; +const GUEST_PAGE_SIZE_END: u64 = 0 + 0x2b; /// Virtual queue index. Writing to this register selects the virtual queue that the following /// operations on the QueueNumMax, QueueNum, QueueAlign and QueuePFN registers apply to. The index /// number of the first queue is zero (0x0). Write-only. -const QUEUE_SEL: u64 = VIRTIO_BASE + 0x30; -const QUEUE_SEL_END: u64 = VIRTIO_BASE + 0x33; +const QUEUE_SEL: u64 = 0 + 0x30; +const QUEUE_SEL_END: u64 = 0 + 0x33; /// Maximum virtual queue size. Reading from the register returns the maximum size of the queue the /// device is ready to process or zero (0x0) if the queue is not available. This applies to the /// queue selected by writing to QueueSel and is allowed only when QueuePFN is set to zero (0x0), /// so when the queue is not actively used. Read-only. In QEMU, `VIRTIO_COUNT = 8`. -const QUEUE_NUM_MAX: u64 = VIRTIO_BASE + 0x34; -const QUEUE_NUM_MAX_END: u64 = VIRTIO_BASE + 0x37; +const QUEUE_NUM_MAX: u64 = 0 + 0x34; +const QUEUE_NUM_MAX_END: u64 = 0 + 0x37; /// Virtual queue size. Queue size is the number of elements in the queue, therefore size of the /// descriptor table and both available and used rings. Writing to this register notifies the /// device what size of the queue the driver will use. This applies to the queue selected by /// writing to QueueSel. Write-only. -const QUEUE_NUM: u64 = VIRTIO_BASE + 0x38; -const QUEUE_NUM_END: u64 = VIRTIO_BASE + 0x3b; +const QUEUE_NUM: u64 = 0 + 0x38; +const QUEUE_NUM_END: u64 = 0 + 0x3b; /// Used Ring alignment in the virtual queue. -const QUEUE_ALIGN: u64 = VIRTIO_BASE + 0x3c; -const QUEUE_ALIGN_END: u64 = VIRTIO_BASE + 0x3f; +const QUEUE_ALIGN: u64 = 0 + 0x3c; +const QUEUE_ALIGN_END: u64 = 0 + 0x3f; /// Guest physical page number of the virtual queue. Writing to this register notifies the device /// about location of the virtual queue in the Guest’s physical address space. This value is the @@ -101,35 +101,35 @@ const QUEUE_ALIGN_END: u64 = VIRTIO_BASE + 0x3f; /// writes zero (0x0) to this register. Reading from this register returns the currently used page /// number of the queue, therefore a value other than zero (0x0) means that the queue is in use. /// Both read and write accesses apply to the queue selected by writing to QueueSel. -const QUEUE_PFN: u64 = VIRTIO_BASE + 0x40; -const QUEUE_PFN_END: u64 = VIRTIO_BASE + 0x43; +const QUEUE_PFN: u64 = 0 + 0x40; +const QUEUE_PFN_END: u64 = 0 + 0x43; // 4.2.2 MMIO Device Register Layout // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 /// Queue notifier. Writing a queue index to this register notifies the device that there are new /// buffers to process in the queue. Write-only. -const QUEUE_NOTIFY: u64 = VIRTIO_BASE + 0x50; -const QUEUE_NOTIFY_END: u64 = VIRTIO_BASE + 0x53; +const QUEUE_NOTIFY: u64 = 0 + 0x50; +const QUEUE_NOTIFY_END: u64 = 0 + 0x53; /// Interrupt status. Reading from this register returns a bit mask of events that caused the /// device interrupt to be asserted. -const INTERRUPT_STATUS: u64 = VIRTIO_BASE + 0x60; -const INTERRUPT_STATUS_END: u64 = VIRTIO_BASE + 0x63; +const INTERRUPT_STATUS: u64 = 0 + 0x60; +const INTERRUPT_STATUS_END: u64 = 0 + 0x63; /// Interrupt acknowledge. Writing a value with bits set as defined in InterruptStatus to this /// register notifies the device that events causing the interrupt have been handled. -const INTERRUPT_ACK: u64 = VIRTIO_BASE + 0x64; -const INTERRUPT_ACK_END: u64 = VIRTIO_BASE + 0x67; +const INTERRUPT_ACK: u64 = 0 + 0x64; +const INTERRUPT_ACK_END: u64 = 0 + 0x67; /// Device status. Reading from this register returns the current device status flags. Writing /// non-zero values to this register sets the status flags, indicating the driver progress. Writing /// zero (0x0) to this register triggers a device reset. -const STATUS: u64 = VIRTIO_BASE + 0x70; -const STATUS_END: u64 = VIRTIO_BASE + 0x73; +const STATUS: u64 = 0 + 0x70; +const STATUS_END: u64 = 0 + 0x73; /// Configuration space. -const CONFIG: u64 = VIRTIO_BASE + 0x100; -const CONFIG_END: u64 = VIRTIO_BASE + 0x107; +const CONFIG: u64 = 0 + 0x100; +const CONFIG_END: u64 = 0 + 0x107; /// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-230005 /// "Each virtqueue can consist of up to 3 parts: @@ -221,12 +221,12 @@ struct VirtqDesc { impl VirtqDesc { /// Creates a new virtqueue descriptor based on the address that stores the content of the /// descriptor. - fn new(cpu: &mut Cpu, addr: u64) -> Result { + fn new(virtio: &mut Virtio, addr: u64) -> Result { Ok(Self { - addr: cpu.bus.read(addr, DOUBLEWORD)?, - len: cpu.bus.read(addr.wrapping_add(8), WORD)?, - flags: cpu.bus.read(addr.wrapping_add(12), HALFWORD)?, - next: cpu.bus.read(addr.wrapping_add(14), HALFWORD)?, + addr: virtio.read(addr, DOUBLEWORD)?, + len: virtio.read(addr.wrapping_add(8), WORD)?, + flags: virtio.read(addr.wrapping_add(12), HALFWORD)?, + next: virtio.read(addr.wrapping_add(14), HALFWORD)?, }) } } @@ -253,10 +253,10 @@ struct VirtqAvail { } impl VirtqAvail { - fn new(cpu: &mut Cpu, addr: u64) -> Result { + fn new(virtio: &mut Virtio, addr: u64) -> Result { Ok(Self { - flags: cpu.bus.read(addr, HALFWORD)? as u16, - idx: cpu.bus.read(addr.wrapping_add(2), HALFWORD)? as u16, + flags: virtio.read(addr, HALFWORD)? as u16, + idx: virtio.read(addr.wrapping_add(2), HALFWORD)? as u16, ring_start_addr: addr.wrapping_add(4), }) } @@ -279,11 +279,12 @@ pub struct Virtio { config: [u8; 8], disk: Vec, virtqueue: Option, + irq: u64, } impl Virtio { /// Creates a new virtio object. - pub fn new() -> Self { + pub fn new(irq: u64) -> Self { let mut config = [0; 8]; // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2440004 // 5.2.4 Device configuration layout @@ -314,6 +315,7 @@ impl Virtio { config, disk: Vec::new(), virtqueue: None, + irq: irq, } } @@ -342,17 +344,124 @@ impl Virtio { } } - /// Resets the device when `status` is written to 0. - fn reset(&mut self) { - self.id = 0; - // 4.2.2.1 Device Requirements: MMIO Device Register Layout - // "Upon reset, the device MUST clear all bits in InterruptStatus and ready bits in the - // QueueReady register for all queues in the device." - self.interrupt_status = 0; + /// Sets the binary in the virtio disk. + pub fn initialize(&mut self, binary: Vec) { + self.disk.extend(binary.iter().cloned()); + } + + fn read_disk(&self, addr: u64) -> u64 { + self.disk[addr as usize] as u64 + } + + fn write_disk(&mut self, addr: u64, value: u64) { + self.disk[addr as usize] = value as u8 + } + + /// Accesses the disk via virtio. This is an associated function which takes a `cpu` object to + /// read and write with a memory directly (DMA). + pub fn disk_access(&mut self) -> Result<(), Exception> { + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 + // "Used Buffer Notification + // - bit 0 - the interrupt was asserted because the device has used a buffer in at + // least one of the active virtual queues." + self.interrupt_status |= 0x1; + + let virtq = self.virtqueue(); + + let avail = VirtqAvail::new(self, virtq.avail_addr)?; + + let head_index = self.read( + avail.ring_start_addr + avail.idx as u64 % QUEUE_SIZE, + HALFWORD, + )?; + + // First descriptor. + let desc0 = VirtqDesc::new(self, virtq.desc_addr + VRING_DESC_SIZE * head_index)?; + assert_eq!(desc0.flags & VIRTQ_DESC_F_NEXT, 1); + + // Second descriptor. + let desc1 = VirtqDesc::new(self, virtq.desc_addr + VRING_DESC_SIZE * desc0.next)?; + assert_eq!(desc1.flags & VIRTQ_DESC_F_NEXT, 1); + + // 5.2.6 Device Operation + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2500006 + // struct virtio_blk_req { + // le32 type; + // le32 reserved; + // le64 sector; + // u8 data[][512]; + // u8 status; + // }; + let sector = self.read(desc0.addr.wrapping_add(8), DOUBLEWORD)?; + + // Write to a device if the second bit of `flags` is set. + match (desc1.flags & VIRTQ_DESC_F_WRITE) == 0 { + true => { + // Read memory data and write it to a disk. + for i in 0..desc1.len { + let data = self.read(desc1.addr + i, BYTE)?; + self.write_disk(sector * SECTOR_SIZE + i, data); + } + } + false => { + // Read disk data and write it to memory. + for i in 0..desc1.len { + let data = self.read_disk(sector * SECTOR_SIZE + i); + self.write(desc1.addr + i, data, BYTE)?; + } + } + }; + + // Third descriptor address. + let desc2 = VirtqDesc::new(self, virtq.desc_addr + VRING_DESC_SIZE * desc1.next)?; + assert_eq!(desc2.flags & VIRTQ_DESC_F_NEXT, 0); + // Tell success. + self.write(desc2.addr, 0, BYTE)?; + + // 2.6.7.2 Device Requirements: Used Buffer Notification Suppression + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-400007 + // After the device writes a descriptor index into the used ring: + // If flags is 1, the device SHOULD NOT send a notification. + // If flags is 0, the device MUST send a notification. + // TODO: check the flags in the available ring. + + // "The used ring is where the device returns buffers once it is done with them: it is only + // written to by the device, and read by the driver." + // + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-430008 + // + // ```c + // #define VIRTQ_USED_F_NO_NOTIFY 1 + // struct virtq_used { + // le16 flags; + // le16 idx; + // struct virtq_used_elem ring[ /* Queue Size */]; + // le16 avail_event; /* Only if VIRTIO_F_EVENT_IDX */ + // }; + // ``` + self.write( + virtq + .used_addr + .wrapping_add(4) + .wrapping_add((self.id as u64 % QUEUE_SIZE) * 8), + head_index, + WORD, + )?; + + self.id = self.id.wrapping_add(1); + self.write(virtq.used_addr.wrapping_add(2), self.id, HALFWORD)?; + + Ok(()) + } +} + +impl Device for Virtio { + fn size(&self) -> u64 { + 0x1000 } /// Returns true if an interrupt is pending. - pub fn is_interrupting(&mut self) -> bool { + fn is_interrupting(&mut self) -> bool { if self.queue_notify != u32::MAX { self.queue_notify = u32::MAX; return true; @@ -360,13 +469,21 @@ impl Virtio { false } - /// Sets the binary in the virtio disk. - pub fn initialize(&mut self, binary: Vec) { - self.disk.extend(binary.iter().cloned()); + fn irq(&self) -> Option { + Some(self.irq) + } + + /// Resets the device when `status` is written to 0. + fn reset(&mut self) { + self.id = 0; + // 4.2.2.1 Device Requirements: MMIO Device Register Layout + // "Upon reset, the device MUST clear all bits in InterruptStatus and ready bits in the + // QueueReady register for all queues in the device." + self.interrupt_status = 0; } /// Loads `size`-bit data from a register located at `addr` in the virtio block device. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { // `reg` is the value of a target register in the virtio block device and `offset` is the // byte of the start position in the register. let (reg, offset) = match addr { @@ -409,7 +526,8 @@ impl Virtio { } /// Stores `size`-bit data to a register located at `addr` in the virtio block device. - pub fn write(&mut self, addr: u64, value: u32, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + let value = (value & 0xffffffff) as u32; // `reg` is the value of a target register in the virtio block device and `offset` is the // byte of the start position in the register. let (mut reg, offset) = match addr { @@ -508,110 +626,4 @@ impl Virtio { Ok(()) } - - fn read_disk(&self, addr: u64) -> u64 { - self.disk[addr as usize] as u64 - } - - fn write_disk(&mut self, addr: u64, value: u64) { - self.disk[addr as usize] = value as u8 - } - - /// Accesses the disk via virtio. This is an associated function which takes a `cpu` object to - /// read and write with a memory directly (DMA). - pub fn disk_access(cpu: &mut Cpu) -> Result<(), Exception> { - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 - // "Used Buffer Notification - // - bit 0 - the interrupt was asserted because the device has used a buffer in at - // least one of the active virtual queues." - cpu.bus.virtio.interrupt_status |= 0x1; - - let virtq = cpu.bus.virtio.virtqueue(); - - let avail = VirtqAvail::new(cpu, virtq.avail_addr)?; - - let head_index = cpu.bus.read( - avail.ring_start_addr + avail.idx as u64 % QUEUE_SIZE, - HALFWORD, - )?; - - // First descriptor. - let desc0 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * head_index)?; - assert_eq!(desc0.flags & VIRTQ_DESC_F_NEXT, 1); - - // Second descriptor. - let desc1 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * desc0.next)?; - assert_eq!(desc1.flags & VIRTQ_DESC_F_NEXT, 1); - - // 5.2.6 Device Operation - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2500006 - // struct virtio_blk_req { - // le32 type; - // le32 reserved; - // le64 sector; - // u8 data[][512]; - // u8 status; - // }; - let sector = cpu.bus.read(desc0.addr.wrapping_add(8), DOUBLEWORD)?; - - // Write to a device if the second bit of `flags` is set. - match (desc1.flags & VIRTQ_DESC_F_WRITE) == 0 { - true => { - // Read memory data and write it to a disk. - for i in 0..desc1.len { - let data = cpu.bus.read(desc1.addr + i, BYTE)?; - cpu.bus.virtio.write_disk(sector * SECTOR_SIZE + i, data); - } - } - false => { - // Read disk data and write it to memory. - for i in 0..desc1.len { - let data = cpu.bus.virtio.read_disk(sector * SECTOR_SIZE + i); - cpu.bus.write(desc1.addr + i, data, BYTE)?; - } - } - }; - - // Third descriptor address. - let desc2 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * desc1.next)?; - assert_eq!(desc2.flags & VIRTQ_DESC_F_NEXT, 0); - // Tell success. - cpu.bus.write(desc2.addr, 0, BYTE)?; - - // 2.6.7.2 Device Requirements: Used Buffer Notification Suppression - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-400007 - // After the device writes a descriptor index into the used ring: - // If flags is 1, the device SHOULD NOT send a notification. - // If flags is 0, the device MUST send a notification. - // TODO: check the flags in the available ring. - - // "The used ring is where the device returns buffers once it is done with them: it is only - // written to by the device, and read by the driver." - // - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-430008 - // - // ```c - // #define VIRTQ_USED_F_NO_NOTIFY 1 - // struct virtq_used { - // le16 flags; - // le16 idx; - // struct virtq_used_elem ring[ /* Queue Size */]; - // le16 avail_event; /* Only if VIRTIO_F_EVENT_IDX */ - // }; - // ``` - cpu.bus.write( - virtq - .used_addr - .wrapping_add(4) - .wrapping_add((cpu.bus.virtio.id as u64 % QUEUE_SIZE) * 8), - head_index, - WORD, - )?; - - cpu.bus.virtio.id = cpu.bus.virtio.id.wrapping_add(1); - cpu.bus - .write(virtq.used_addr.wrapping_add(2), cpu.bus.virtio.id, HALFWORD)?; - - Ok(()) - } } diff --git a/src/emulator.rs b/src/emulator.rs index 14af18b..5fe7bbb 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,8 +1,44 @@ //! The emulator module represents an entire computer. -use crate::cpu::Cpu; +use crate::cpu::{Cpu, REG_SP}; +use crate::devices::dram::Dram; +//use crate::devices::rom::Rom; +use crate::devices::uart::Uart; +use crate::devices::virtio_blk::Virtio; use crate::exception::Trap; +// QEMU virt machine: +// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c#L46-L63 + +/// The address which the mask ROM starts. +pub const MROM_BASE: u64 = 0x1000; + +/// The address which the core-local interruptor (CLINT) starts. It contains the timer and generates +/// per-hart software interrupts and timer interrupts. +pub const CLINT_BASE: u64 = 0x200_0000; + +/// The address which the platform-level interrupt controller (PLIC) starts. The PLIC connects all +/// external interrupts in the system to all hart contexts in the system, via the external interrupt +/// source in each hart. +pub const PLIC_BASE: u64 = 0xc00_0000; + +/// The address which UART starts. QEMU puts UART registers here in physical memory. +pub const UART_BASE: u64 = 0x1000_0000; +/// The size of UART. +pub const UART_SIZE: u64 = 0x100; + +pub const UART_IRQ: u64 = 10; + +/// The address which virtio starts. +pub const VIRTIO_BASE: u64 = 0x1000_1000; + +pub const VIRTIO_IRQ: u64 = 1; + +/// +pub const DRAM_SIZE: u64 = 0x40000000; +/// The address which DRAM starts. +pub const DRAM_BASE: u64 = 0x8000_0000; + /// The emulator to hold a CPU. pub struct Emulator { /// The CPU which is the core implementation of this emulator. @@ -14,8 +50,11 @@ pub struct Emulator { impl Emulator { /// Constructor for an emulator. pub fn new() -> Emulator { + let mut cpu = Cpu::new(); + let uart = Box::new(Uart::new(UART_IRQ)); + cpu.bus.mount(UART_BASE, uart); Self { - cpu: Cpu::new(), + cpu: cpu, is_debug: false, } } @@ -27,12 +66,16 @@ impl Emulator { /// Set binary data to the beginning of the DRAM from the emulator console. pub fn initialize_dram(&mut self, data: Vec) { - self.cpu.bus.initialize_dram(data); + let mut dram = Box::new(Dram::new(DRAM_SIZE)); + dram.initialize(data); + self.cpu.bus.mount(DRAM_BASE, dram); } /// Set binary data to the virtio disk from the emulator console. pub fn initialize_disk(&mut self, data: Vec) { - self.cpu.bus.initialize_disk(data); + let mut dram = Box::new(Virtio::new(VIRTIO_IRQ)); + dram.initialize(data); + self.cpu.bus.mount(DRAM_BASE, dram); } /// Set the program counter to the CPU field. @@ -40,6 +83,10 @@ impl Emulator { self.cpu.pc = pc; } + pub fn initialize_sp(&mut self, sp: u64) { + self.cpu.xregs.write(REG_SP, sp); + } + /// Start executing the emulator with limited range of program. This method is for test. /// No interrupts happen. pub fn test_start(&mut self, start: u64, end: u64) { diff --git a/src/lib.rs b/src/lib.rs index edaba06..5bca14a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,8 +35,6 @@ pub mod bus; pub mod cpu; pub mod csr; pub mod devices; -pub mod dram; pub mod emulator; pub mod exception; pub mod interrupt; -pub mod rom; From e6ab55e879a822516cf02255ad750c55f28f7ea2 Mon Sep 17 00:00:00 2001 From: Jim Keener Date: Sat, 31 May 2025 02:52:19 -0400 Subject: [PATCH 2/5] fixing a bug in the jump handler I will break this out into a new branch. --- src/cpu.rs | 29 +++++++++++++++++++++++------ src/csr.rs | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index a5e3040..86a7703 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -810,9 +810,10 @@ impl Cpu { // offset[5:3|7:6] = isnt[12:10|6:5] let offset = ((inst << 1) & 0xc0) // imm[7:6] | ((inst >> 7) & 0x38); // imm[5:3] - let val = f64::from_bits( - self.read(self.xregs.read(rs1).wrapping_add(offset), DOUBLEWORD)?, - ); + let rs1_val = self.xregs.read(rs1); + let read_addr = rs1_val.wrapping_add(offset); + + let val = f64::from_bits(self.read(read_addr, DOUBLEWORD)?); self.fregs.write(rd, val); } 0x2 => { @@ -1721,7 +1722,17 @@ impl Cpu { inst_count!(self, "sd"); self.debug(inst, "sd"); - self.write(addr, self.xregs.read(rs2), DOUBLEWORD)? + //inst[31:25,11:7] = imm[11:5,4:0] + let offset_top = (inst >> 25) << 5; + let offset_bottom = (inst >> 7) & 0x1f; + let offset = offset_top | offset_bottom; + let rs2_val = self.xregs.read(rs2); + let read_addr = rs2_val + offset; + + //JSK + + let read_bytes = self.read(read_addr, DOUBLEWORD)?; + self.write(addr, read_bytes, DOUBLEWORD)? } _ => { return Err(Exception::IllegalInstruction(inst)); @@ -3369,11 +3380,14 @@ impl Cpu { let target = ((self.xregs.read(rs1) as i64).wrapping_add(offset)) & !1; let new_pc = target as u64; + let mut handled = false; if let Some(jh) = &self.jump_handler { if jh.should_handle(new_pc) { (self.xregs, self.fregs) = jh.handle(new_pc, self); + handled = true; } - } else { + } + if !handled { self.pc = new_pc; self.xregs.write(rd, self.pc.wrapping_add(4)); } @@ -3390,11 +3404,14 @@ impl Cpu { | ((inst >> 20) & 0x7fe); // imm[10:1] let new_pc = self.pc.wrapping_add(offset); + let mut handled = false; if let Some(jh) = &self.jump_handler { if jh.should_handle(new_pc) { (self.xregs, self.fregs) = jh.handle(new_pc, self); + handled = true; } - } else { + } + if !handled { self.pc = new_pc; self.xregs.write(rd, self.pc.wrapping_add(4)); } diff --git a/src/csr.rs b/src/csr.rs index 8e6e804..04bb394 100644 --- a/src/csr.rs +++ b/src/csr.rs @@ -37,7 +37,7 @@ pub const FCSR: CsrAddress = 0x003; // User Counter/Timers. /// Timer for RDTIME instruction. -const TIME: CsrAddress = 0xc01; +pub const TIME: CsrAddress = 0xc01; ///////////////////////////////////// // Supervisor-level CSR addresses // From a45b655e943d612ab87ed6f40a56fa57fa5caf73 Mon Sep 17 00:00:00 2001 From: Jim Keener Date: Sat, 31 May 2025 05:23:45 -0400 Subject: [PATCH 3/5] Fixing bugs introduced while refactoring --- src/cpu.rs | 45 ++++++++++++++++++++++++++------------------- src/emulator.rs | 4 ++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 86a7703..168ef80 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -392,7 +392,7 @@ impl Cpu { println!("pc: {:#x} prev_instr: {:#x}", self.pc, self.pre_inst); } - pub fn cycle(&mut self) -> Result<(Option, u64), Exception> { + pub fn cycle(&mut self) -> Result<(Option, (u64, u64, u64)), Exception> { let interrupt = self.check_pending_interrupt(); self.devices_increment(); Ok((interrupt, self.execute()?)) @@ -710,7 +710,7 @@ impl Cpu { } /// Fetch the `size`-bit next instruction from the memory at the current program counter. - pub fn fetch(&mut self, size: u8) -> Result { + pub fn fetch(&mut self, size: u8) -> Result<(u64, u64, u64), Exception> { if size != HALFWORD && size != WORD { return Err(Exception::InstructionAccessFault); } @@ -720,7 +720,7 @@ impl Cpu { // The result of the read method can be `Exception::LoadAccessFault`. In fetch(), an error // should be `Exception::InstructionAccessFault`. match self.bus.read(p_pc, size) { - Ok(value) => Ok(value), + Ok(value) => Ok((self.pc, p_pc, value)), Err(_) => Err(Exception::InstructionAccessFault), } } @@ -736,14 +736,14 @@ impl Cpu { /// Execute an instruction. Raises an exception if something is wrong, otherwise, returns /// the instruction executed in this cycle. - pub fn execute(&mut self) -> Result { + pub fn execute(&mut self) -> Result<(u64, u64, u64), Exception> { // WFI is called and pending interrupts don't exist. if self.idle { - return Ok(0); + return Ok((0, 0, 0)); } // Fetch. - let inst16 = self.fetch(HALFWORD)?; + let (mut fetch_pc, mut fetch_p_pc, inst16) = self.fetch(HALFWORD)?; let inst; match inst16 & 0b11 { 0 | 1 | 2 => { @@ -757,14 +757,14 @@ impl Cpu { self.pc += 2; } _ => { - inst = self.fetch(WORD)?; + (fetch_pc, fetch_p_pc, inst) = self.fetch(WORD)?; self.execute_general(inst)?; // Add 4 bytes to the program counter. self.pc += 4; } } self.pre_inst = inst; - Ok(inst) + Ok((fetch_pc, fetch_p_pc, inst)) } /// Execute a compressed instruction. Raised an exception if something is wrong, otherwise, @@ -1311,13 +1311,16 @@ impl Cpu { let rs1 = (inst >> 7) & 0x1f; let new_pc = self.xregs.read(rs1).wrapping_sub(2); + let mut handled = false; if let Some(jh) = &self.jump_handler { if jh.should_handle(new_pc) { (self.xregs, self.fregs) = jh.handle(new_pc, self); + handled = true; } - } else { + } + if !handled { + self.xregs.write(REG_RA, self.pc.wrapping_add(2)); self.pc = new_pc; - self.xregs.write(1, self.pc.wrapping_add(2)); } } } @@ -1351,7 +1354,8 @@ impl Cpu { // offset[5:3|8:6] = isnt[12:10|9:7] let offset = ((inst >> 1) & 0x1c0) // offset[8:6] | ((inst >> 7) & 0x38); // offset[5:3] - let addr = self.xregs.read(2).wrapping_add(offset); + let sp = self.xregs.read(2); + let addr = sp.wrapping_add(offset); self.write(addr, self.fregs.read(rs2).to_bits(), DOUBLEWORD)?; } 0x6 => { @@ -3379,7 +3383,7 @@ impl Cpu { let offset = (inst as i32 as i64) >> 20; let target = ((self.xregs.read(rs1) as i64).wrapping_add(offset)) & !1; - let new_pc = target as u64; + let new_pc = (target as u64).wrapping_sub(4); let mut handled = false; if let Some(jh) = &self.jump_handler { if jh.should_handle(new_pc) { @@ -3388,8 +3392,8 @@ impl Cpu { } } if !handled { + self.xregs.write(REG_RA, self.pc.wrapping_add(4)); self.pc = new_pc; - self.xregs.write(rd, self.pc.wrapping_add(4)); } } 0x6F => { @@ -3398,13 +3402,16 @@ impl Cpu { self.debug(inst, "jal"); // imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12] - let offset = (((inst & 0x80000000) as i32 as i64 >> 11) as u64) // imm[20] - | (inst & 0xff000) // imm[19:12] - | ((inst >> 9) & 0x800) // imm[11] - | ((inst >> 20) & 0x7fe); // imm[10:1] - let new_pc = self.pc.wrapping_add(offset); + let imm20 = ((inst & 0x80000000) as i32 as i64 >> 11) as u64; // imm[20] + let imm19 = inst & 0xff000; // imm[19:12] + let imm11 = (inst >> 9) & 0x800; // imm[11] + let imm10 = (inst >> 20) & 0x7fe; // imm[10:1] + let offset = imm20 | imm19 | imm11 | imm10; + + let new_pc = self.pc.wrapping_add(offset).wrapping_sub(4); let mut handled = false; + if let Some(jh) = &self.jump_handler { if jh.should_handle(new_pc) { (self.xregs, self.fregs) = jh.handle(new_pc, self); @@ -3412,8 +3419,8 @@ impl Cpu { } } if !handled { + self.xregs.write(REG_RA, self.pc.wrapping_add(4)); self.pc = new_pc; - self.xregs.write(rd, self.pc.wrapping_add(4)); } } 0x73 => { diff --git a/src/emulator.rs b/src/emulator.rs index 5fe7bbb..3c5976e 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -103,7 +103,7 @@ impl Emulator { } match self.cpu.execute() { - Ok(inst) => { + Ok((_, _, inst)) => { println!("pc: {:#x}, inst: {:#x}", self.cpu.pc.wrapping_sub(4), inst); Trap::Requested } @@ -135,7 +135,7 @@ impl Emulator { // Execute an instruction. let trap = match self.cpu.execute() { - Ok(inst) => { + Ok((_, _, inst)) => { if self.is_debug { println!( "pc: {:#x}, inst: {:#x}, is_inst 16? {} pre_inst: {:#x}", From 94ad2f860790006e0055c95e1f635ead179587e6 Mon Sep 17 00:00:00 2001 From: Jim Keener Date: Sat, 31 May 2025 05:34:42 -0400 Subject: [PATCH 4/5] Fixing bug with jump handler. This needs to be broken out. --- src/cpu.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 168ef80..b3d8833 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -3383,7 +3383,7 @@ impl Cpu { let offset = (inst as i32 as i64) >> 20; let target = ((self.xregs.read(rs1) as i64).wrapping_add(offset)) & !1; - let new_pc = (target as u64).wrapping_sub(4); + let new_pc = (target as u64); let mut handled = false; if let Some(jh) = &self.jump_handler { if jh.should_handle(new_pc) { @@ -3393,7 +3393,7 @@ impl Cpu { } if !handled { self.xregs.write(REG_RA, self.pc.wrapping_add(4)); - self.pc = new_pc; + self.pc = new_pc.wrapping_sub(4); } } 0x6F => { @@ -3409,7 +3409,7 @@ impl Cpu { let imm10 = (inst >> 20) & 0x7fe; // imm[10:1] let offset = imm20 | imm19 | imm11 | imm10; - let new_pc = self.pc.wrapping_add(offset).wrapping_sub(4); + let new_pc = self.pc.wrapping_add(offset); let mut handled = false; if let Some(jh) = &self.jump_handler { @@ -3420,7 +3420,7 @@ impl Cpu { } if !handled { self.xregs.write(REG_RA, self.pc.wrapping_add(4)); - self.pc = new_pc; + self.pc = new_pc.wrapping_sub(4) } } 0x73 => { From 0980ab0d3103ceef25e9710bef6cbecc1270001d Mon Sep 17 00:00:00 2001 From: Jim Keener Date: Fri, 13 Jun 2025 18:09:39 -0400 Subject: [PATCH 5/5] Remove jump handler code. Ensure tests work. --- examples/jumplinkhandler/main.rs | 70 --------------- src/bus.rs | 11 ++- src/cpu.rs | 145 ++++++++++--------------------- src/devices/virtio_blk.rs | 3 + src/emulator.rs | 33 ++++--- src/lib.rs | 2 +- tests/exception.rs | 2 +- tests/helper.rs | 4 +- tests/rv32i.rs | 2 +- tests/rv64_user_p.rs | 2 +- 10 files changed, 86 insertions(+), 188 deletions(-) delete mode 100644 examples/jumplinkhandler/main.rs diff --git a/examples/jumplinkhandler/main.rs b/examples/jumplinkhandler/main.rs deleted file mode 100644 index be5a12f..0000000 --- a/examples/jumplinkhandler/main.rs +++ /dev/null @@ -1,70 +0,0 @@ -extern crate rvemu; - -use rvemu::cpu; -use rvemu::cpu::{Cpu, FRegisters, JumpLinkHandler, XRegisters}; -use rvemu::devices::dram::Dram; -use rvemu::exception::Exception; - -pub const SPF_MIN_ADDR: u64 = 0x800_0000; -pub const SPF_FABS: u64 = 0x800_bc40; -pub const SPF_FCN: u64 = 0x800_0400; - -pub const DRAM_BASE: u64 = 0x800_0000; -pub const DRAM_SIZE: u64 = 0xff; - -struct SysProvided {} - -const SPF_FCN_TARGET_VALUE: f64 = 654.321; - -impl JumpLinkHandler for SysProvided { - fn should_handle(&self, new_pc: u64) -> bool { - new_pc >= SPF_MIN_ADDR - } - fn handle(&self, new_pc: u64, cpu: &Cpu) -> (XRegisters, FRegisters) { - let xregs = cpu.xregs.clone(); - let mut fregs = cpu.fregs.clone(); - match new_pc { - SPF_FABS => { - let f0 = fregs.read(cpu::REG_FA0); - fregs.write(cpu::REG_FA0, f0.abs()); - } - SPF_FCN => { - fregs.write(cpu::REG_FA1, SPF_FCN_TARGET_VALUE); - } - _ => {} - } - - (xregs, fregs) - } -} - -fn main() -> Result<(), Exception> { - #[rustfmt::skip] - let prog: Vec = vec![ - // 80000000: f2000553 fmv.d.x fa0,zero - 0x53, 0x05, 0x00, 0xf2, - // 80000004: 3fc000ef jal 80000400 - 0xef, 0x00, 0xc0, 0x3f, - ]; - - let jh = SysProvided {}; - let mut cpu = Cpu::new(); - cpu.with_jump_link_handler(Box::new(jh)); - let mut dram = Box::new(Dram::new(DRAM_SIZE)); - dram.initialize(prog); - cpu.bus.mount(DRAM_BASE, dram); - - cpu.reset(); - cpu.pc = DRAM_BASE; - cpu.xregs.write(cpu::REG_SP, DRAM_BASE + DRAM_SIZE); - - println!("instr: {:x?}", cpu.cycle()?); - println!("instr: {:x?}", cpu.cycle()?); - cpu.print_registers(); - - println!( - "Was the handler successful? {:?}", - cpu.fregs.read(cpu::REG_FA1) == SPF_FCN_TARGET_VALUE - ); - Ok(()) -} diff --git a/src/bus.rs b/src/bus.rs index 32240f3..23e9169 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -4,6 +4,7 @@ use crate::devices::clint::Clint; use crate::devices::plic::Plic; use crate::exception::Exception; +use std::sync::{Arc, Mutex}; use std::ops::RangeInclusive; @@ -33,7 +34,7 @@ pub trait Device { /// The system bus. pub struct Bus { - devices: Vec<(RangeInclusive, Box)>, + devices: Vec<(RangeInclusive, Arc>)>, pub clint: Clint, pub plic: Plic, @@ -58,6 +59,7 @@ impl Bus { pub fn is_interrupting(&mut self) -> bool { let mut saw_interrupt = false; for (_, device) in self.devices.iter_mut() { + let mut device = device.lock().unwrap(); if device.is_interrupting() { if let Some(irq) = device.irq() { self.plic.update_pending(irq); @@ -68,8 +70,9 @@ impl Bus { saw_interrupt } - pub fn mount(&mut self, memory_start: u64, device: Box) { - let addr_range = memory_start..=(memory_start + device.size()); + pub fn mount(&mut self, memory_start: u64, device: Arc>) { + let size = device.lock().unwrap().size(); + let addr_range = memory_start..=(memory_start + size); self.devices.push((addr_range, device)); } @@ -82,6 +85,7 @@ impl Bus { } else { for (addr_range, device) in self.devices.iter_mut() { if addr_range.contains(&addr) { + let mut device = device.lock().unwrap(); return device.read(addr - addr_range.start(), size); } } @@ -100,6 +104,7 @@ impl Bus { } else { for (addr_range, device) in self.devices.iter_mut() { if addr_range.contains(&addr) { + let mut device = device.lock().unwrap(); return device.write(addr - addr_range.start(), value, size); } } diff --git a/src/cpu.rs b/src/cpu.rs index b3d8833..efe8062 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -122,7 +122,7 @@ pub enum Mode { } /// The integer registers. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct XRegisters { xregs: [u64; REGISTERS_COUNT], } @@ -195,7 +195,7 @@ impl fmt::Display for XRegisters { } /// The floating-point registers. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct FRegisters { fregs: [f64; REGISTERS_COUNT], } @@ -263,9 +263,23 @@ impl fmt::Display for FRegisters { } } -pub trait JumpLinkHandler { - fn should_handle(&self, new_pc: u64) -> bool; - fn handle(&self, new_pc: u64, cpu: &Cpu) -> (XRegisters, FRegisters); +pub struct ExecutedOp { + pub opcode: u64, + pub pc: u64, +} + +pub enum ExecCycle { + Idle, + Opcode(ExecutedOp), +} + +impl ExecutedOp { + pub fn new(opcode: u64, pc: u64) -> ExecutedOp { + ExecutedOp { + opcode: opcode, + pc: pc, + } + } } /// The CPU to contain registers, a program counter, status, and a privileged mode. @@ -282,9 +296,6 @@ pub struct Cpu { pub mode: Mode, /// System bus. pub bus: Bus, - - pub jump_handler: Option>, - /// SV39 paging flag. enable_paging: bool, /// Physical page number (PPN) × PAGE_SIZE (4096). @@ -302,10 +313,6 @@ pub struct Cpu { pub pre_inst: u64, } -// impl fmt::Debug for Cpu { -// -// } - impl Cpu { /// Create a new `Cpu` object. pub fn new() -> Cpu { @@ -316,7 +323,6 @@ impl Cpu { state: State::new(), mode: Mode::Machine, bus: Bus::new(), - jump_handler: None, enable_paging: false, page_table: 0, reservation_set: Vec::new(), @@ -327,10 +333,6 @@ impl Cpu { } } - pub fn with_jump_link_handler(&mut self, jh: Box) { - self.jump_handler = Some(jh); - } - fn debug(&self, _inst: u64, _name: &str) { /* if (((0x20_0000_0000 & self.pc) >> 37) == 1) && (self.pc & 0xf0000000_00000000) == 0 { @@ -385,19 +387,6 @@ impl Cpu { } } - pub fn print_registers(&self) { - println!("{}", self.xregs); - println!("{}", self.fregs); - println!("{}", self.state); - println!("pc: {:#x} prev_instr: {:#x}", self.pc, self.pre_inst); - } - - pub fn cycle(&mut self) -> Result<(Option, (u64, u64, u64)), Exception> { - let interrupt = self.check_pending_interrupt(); - self.devices_increment(); - Ok((interrupt, self.execute()?)) - } - /// Check interrupt flags for all devices that can interrupt. pub fn check_pending_interrupt(&mut self) -> Option { // global interrupt: PLIC (Platform Local Interrupt Controller) dispatches global @@ -426,7 +415,6 @@ impl Cpu { // TODO: Take interrupts based on priorities. - // Check external interrupt for uart and virtio. if self.bus.is_interrupting() { // TODO: assume that hart is 0 // TODO: write a value to MCLAIM if the mode is machine @@ -710,7 +698,7 @@ impl Cpu { } /// Fetch the `size`-bit next instruction from the memory at the current program counter. - pub fn fetch(&mut self, size: u8) -> Result<(u64, u64, u64), Exception> { + pub fn fetch(&mut self, size: u8) -> Result { if size != HALFWORD && size != WORD { return Err(Exception::InstructionAccessFault); } @@ -720,7 +708,7 @@ impl Cpu { // The result of the read method can be `Exception::LoadAccessFault`. In fetch(), an error // should be `Exception::InstructionAccessFault`. match self.bus.read(p_pc, size) { - Ok(value) => Ok((self.pc, p_pc, value)), + Ok(value) => Ok(value), Err(_) => Err(Exception::InstructionAccessFault), } } @@ -736,15 +724,16 @@ impl Cpu { /// Execute an instruction. Raises an exception if something is wrong, otherwise, returns /// the instruction executed in this cycle. - pub fn execute(&mut self) -> Result<(u64, u64, u64), Exception> { + pub fn execute(&mut self) -> Result { // WFI is called and pending interrupts don't exist. if self.idle { - return Ok((0, 0, 0)); + return Ok(ExecCycle::Idle); } // Fetch. - let (mut fetch_pc, mut fetch_p_pc, inst16) = self.fetch(HALFWORD)?; + let inst16 = self.fetch(HALFWORD)?; let inst; + let prev_pc = self.pc; match inst16 & 0b11 { 0 | 1 | 2 => { if inst16 == 0 { @@ -757,14 +746,14 @@ impl Cpu { self.pc += 2; } _ => { - (fetch_pc, fetch_p_pc, inst) = self.fetch(WORD)?; + inst = self.fetch(WORD)?; self.execute_general(inst)?; // Add 4 bytes to the program counter. self.pc += 4; } } self.pre_inst = inst; - Ok((fetch_pc, fetch_p_pc, inst)) + Ok(ExecCycle::Opcode(ExecutedOp::new(inst, prev_pc))) } /// Execute a compressed instruction. Raised an exception if something is wrong, otherwise, @@ -810,10 +799,9 @@ impl Cpu { // offset[5:3|7:6] = isnt[12:10|6:5] let offset = ((inst << 1) & 0xc0) // imm[7:6] | ((inst >> 7) & 0x38); // imm[5:3] - let rs1_val = self.xregs.read(rs1); - let read_addr = rs1_val.wrapping_add(offset); - - let val = f64::from_bits(self.read(read_addr, DOUBLEWORD)?); + let val = f64::from_bits( + self.read(self.xregs.read(rs1).wrapping_add(offset), DOUBLEWORD)?, + ); self.fregs.write(rd, val); } 0x2 => { @@ -1310,18 +1298,9 @@ impl Cpu { self.debug(inst, "c.jalr"); let rs1 = (inst >> 7) & 0x1f; - let new_pc = self.xregs.read(rs1).wrapping_sub(2); - let mut handled = false; - if let Some(jh) = &self.jump_handler { - if jh.should_handle(new_pc) { - (self.xregs, self.fregs) = jh.handle(new_pc, self); - handled = true; - } - } - if !handled { - self.xregs.write(REG_RA, self.pc.wrapping_add(2)); - self.pc = new_pc; - } + let t = self.pc.wrapping_add(2); + self.pc = self.xregs.read(rs1).wrapping_sub(2); + self.xregs.write(1, t); } } (1, _) => { @@ -1354,8 +1333,7 @@ impl Cpu { // offset[5:3|8:6] = isnt[12:10|9:7] let offset = ((inst >> 1) & 0x1c0) // offset[8:6] | ((inst >> 7) & 0x38); // offset[5:3] - let sp = self.xregs.read(2); - let addr = sp.wrapping_add(offset); + let addr = self.xregs.read(2).wrapping_add(offset); self.write(addr, self.fregs.read(rs2).to_bits(), DOUBLEWORD)?; } 0x6 => { @@ -1726,17 +1704,7 @@ impl Cpu { inst_count!(self, "sd"); self.debug(inst, "sd"); - //inst[31:25,11:7] = imm[11:5,4:0] - let offset_top = (inst >> 25) << 5; - let offset_bottom = (inst >> 7) & 0x1f; - let offset = offset_top | offset_bottom; - let rs2_val = self.xregs.read(rs2); - let read_addr = rs2_val + offset; - - //JSK - - let read_bytes = self.read(read_addr, DOUBLEWORD)?; - self.write(addr, read_bytes, DOUBLEWORD)? + self.write(addr, self.xregs.read(rs2), DOUBLEWORD)? } _ => { return Err(Exception::IllegalInstruction(inst)); @@ -3380,48 +3348,29 @@ impl Cpu { inst_count!(self, "jalr"); self.debug(inst, "jalr"); + let t = self.pc.wrapping_add(4); + let offset = (inst as i32 as i64) >> 20; let target = ((self.xregs.read(rs1) as i64).wrapping_add(offset)) & !1; - let new_pc = (target as u64); - let mut handled = false; - if let Some(jh) = &self.jump_handler { - if jh.should_handle(new_pc) { - (self.xregs, self.fregs) = jh.handle(new_pc, self); - handled = true; - } - } - if !handled { - self.xregs.write(REG_RA, self.pc.wrapping_add(4)); - self.pc = new_pc.wrapping_sub(4); - } + self.pc = (target as u64).wrapping_sub(4); + + self.xregs.write(rd, t); } 0x6F => { // jal inst_count!(self, "jal"); self.debug(inst, "jal"); - // imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12] - - let imm20 = ((inst & 0x80000000) as i32 as i64 >> 11) as u64; // imm[20] - let imm19 = inst & 0xff000; // imm[19:12] - let imm11 = (inst >> 9) & 0x800; // imm[11] - let imm10 = (inst >> 20) & 0x7fe; // imm[10:1] - let offset = imm20 | imm19 | imm11 | imm10; + self.xregs.write(rd, self.pc.wrapping_add(4)); - let new_pc = self.pc.wrapping_add(offset); - let mut handled = false; + // imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12] + let offset = (((inst & 0x80000000) as i32 as i64 >> 11) as u64) // imm[20] + | (inst & 0xff000) // imm[19:12] + | ((inst >> 9) & 0x800) // imm[11] + | ((inst >> 20) & 0x7fe); // imm[10:1] - if let Some(jh) = &self.jump_handler { - if jh.should_handle(new_pc) { - (self.xregs, self.fregs) = jh.handle(new_pc, self); - handled = true; - } - } - if !handled { - self.xregs.write(REG_RA, self.pc.wrapping_add(4)); - self.pc = new_pc.wrapping_sub(4) - } + self.pc = self.pc.wrapping_add(offset).wrapping_sub(4); } 0x73 => { // RV32I, RVZicsr, and supervisor ISA diff --git a/src/devices/virtio_blk.rs b/src/devices/virtio_blk.rs index 3549d2b..49647c8 100644 --- a/src/devices/virtio_blk.rs +++ b/src/devices/virtio_blk.rs @@ -247,6 +247,9 @@ impl VirtqDesc { /// ``` #[derive(Debug)] struct VirtqAvail { + // JSK: I am not sure where this is used, but also don't know enough + // to feel comfortable getting rid of it. + #[allow(dead_code)] flags: u16, idx: u16, ring_start_addr: u64, diff --git a/src/emulator.rs b/src/emulator.rs index 3c5976e..7f90b5e 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,11 +1,12 @@ //! The emulator module represents an entire computer. -use crate::cpu::{Cpu, REG_SP}; +use crate::cpu::{Cpu, ExecCycle, REG_SP}; use crate::devices::dram::Dram; //use crate::devices::rom::Rom; use crate::devices::uart::Uart; use crate::devices::virtio_blk::Virtio; use crate::exception::Trap; +use std::sync::{Arc, Mutex}; // QEMU virt machine: // https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c#L46-L63 @@ -51,7 +52,7 @@ impl Emulator { /// Constructor for an emulator. pub fn new() -> Emulator { let mut cpu = Cpu::new(); - let uart = Box::new(Uart::new(UART_IRQ)); + let uart = Arc::new(Mutex::new(Uart::new(UART_IRQ))); cpu.bus.mount(UART_BASE, uart); Self { cpu: cpu, @@ -66,16 +67,17 @@ impl Emulator { /// Set binary data to the beginning of the DRAM from the emulator console. pub fn initialize_dram(&mut self, data: Vec) { - let mut dram = Box::new(Dram::new(DRAM_SIZE)); - dram.initialize(data); + let dram = Arc::new(Mutex::new(Dram::new(DRAM_SIZE))); + dram.lock().unwrap().initialize(data); self.cpu.bus.mount(DRAM_BASE, dram); + self.initialize_sp(DRAM_BASE + DRAM_SIZE); } /// Set binary data to the virtio disk from the emulator console. pub fn initialize_disk(&mut self, data: Vec) { - let mut dram = Box::new(Virtio::new(VIRTIO_IRQ)); - dram.initialize(data); - self.cpu.bus.mount(DRAM_BASE, dram); + let disk = Arc::new(Mutex::new(Virtio::new(VIRTIO_IRQ))); + disk.lock().unwrap().initialize(data); + self.cpu.bus.mount(VIRTIO_BASE, disk); } /// Set the program counter to the CPU field. @@ -103,8 +105,12 @@ impl Emulator { } match self.cpu.execute() { - Ok((_, _, inst)) => { - println!("pc: {:#x}, inst: {:#x}", self.cpu.pc.wrapping_sub(4), inst); + Ok(ExecCycle::Idle) => { + println!("Idle"); + Trap::Requested + } + Ok(ExecCycle::Opcode(eop)) => { + println!("pc: {:#x}, inst: {:#x}", eop.pc, eop.opcode); Trap::Requested } Err(exception) => { @@ -135,11 +141,16 @@ impl Emulator { // Execute an instruction. let trap = match self.cpu.execute() { - Ok((_, _, inst)) => { + Ok(ExecCycle::Idle) => { + println!("Idle"); + Trap::Requested + } + Ok(ExecCycle::Opcode(eop)) => { if self.is_debug { + let inst = eop.opcode; println!( "pc: {:#x}, inst: {:#x}, is_inst 16? {} pre_inst: {:#x}", - self.cpu.pc.wrapping_sub(4), + eop.pc, inst, // Check if an instruction is one of the compressed instructions. inst & 0b11 == 0 || inst & 0b11 == 1 || inst & 0b11 == 2, diff --git a/src/lib.rs b/src/lib.rs index 5bca14a..1cbfbab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! Create an `Emulator` object, place a binary data in DRAM and set the program counter to //! `DRAM_BASE`. The binary data must contain no headers for now. The example is here: //! ```rust -//! use rvemu::bus::DRAM_BASE; +//! use rvemu::emulator::DRAM_BASE; //! use rvemu::emulator::Emulator; //! //! fn main() { diff --git a/tests/exception.rs b/tests/exception.rs index 6bf2cbe..92efb1f 100644 --- a/tests/exception.rs +++ b/tests/exception.rs @@ -1,6 +1,6 @@ -use rvemu::bus::DRAM_BASE; use rvemu::csr::MEPC; use rvemu::emulator::Emulator; +use rvemu::emulator::DRAM_BASE; #[test] fn illegal_isa() { diff --git a/tests/helper.rs b/tests/helper.rs index e0ea3d9..fcd843a 100644 --- a/tests/helper.rs +++ b/tests/helper.rs @@ -1,7 +1,7 @@ -use rvemu::bus::DRAM_BASE; use rvemu::cpu::{POINTER_TO_DTB, REGISTERS_COUNT}; -use rvemu::dram::DRAM_SIZE; use rvemu::emulator::Emulator; +use rvemu::emulator::DRAM_BASE; +use rvemu::emulator::DRAM_SIZE; pub const DEFAULT_SP: u64 = DRAM_BASE + DRAM_SIZE; diff --git a/tests/rv32i.rs b/tests/rv32i.rs index c7ea9c5..3ed0e0d 100644 --- a/tests/rv32i.rs +++ b/tests/rv32i.rs @@ -1,7 +1,7 @@ mod helper; use rvemu::emulator::Emulator; -use rvemu::bus::DRAM_BASE; +use rvemu::emulator::DRAM_BASE; #[test] fn lb_rd_offset_rs1() { diff --git a/tests/rv64_user_p.rs b/tests/rv64_user_p.rs index db2036e..22449d6 100644 --- a/tests/rv64_user_p.rs +++ b/tests/rv64_user_p.rs @@ -5,7 +5,7 @@ use std::io; use std::io::prelude::*; use std::path::PathBuf; -use rvemu::{bus::DRAM_BASE, cpu::Mode, emulator::Emulator}; +use rvemu::{cpu::Mode, emulator::Emulator, emulator::DRAM_BASE}; #[macro_export] macro_rules! add_test {