diff --git a/Cargo.lock b/Cargo.lock index e2a4988..026ecf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,67 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "bits" version = "0.1.0" @@ -16,6 +71,59 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "clap" +version = "4.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "enum_dispatch" version = "0.3.11" @@ -28,12 +136,62 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + [[package]] name = "modular-bitfield" version = "0.11.2" @@ -160,12 +318,26 @@ name = "riscy-rust" version = "0.1.0" dependencies = [ "bits", + "clap", "enum_dispatch", "modular-bitfield", "num", "strum", ] +[[package]] +name = "rustix" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.13" @@ -178,6 +350,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.25.0" @@ -227,3 +405,75 @@ name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 122eeb1..3c5668a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "lib/bits" [dependencies] +clap = { version = "4.3.19", features = ["derive"] } enum_dispatch = "0.3.11" modular-bitfield = "0.11.2" num = "0.4.0" diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 7ca784c..0a0d949 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -1,3 +1,5 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + use crate::ext::decode; use crate::system::bus::*; use crate::system::ram; @@ -31,37 +33,44 @@ pub struct CPUState { // // We will also not implement all of them, just the ones we need for the Linux // TODO: Do all of the CSRs exist on each hart? Or are they per hart? - mstatus: rv32::Word, // Machine status reg to disable interrupts + pub mstatus: rv32::Word, // Machine status reg to disable interrupts // Timers - cyclel: rv32::Word, // Lower 32 bits of the cycle counter - cycleh: rv32::Word, // Upper 32 bits of the cycle counter - timel: rv32::Word, // Lower 32 bits of the timer - timeh: rv32::Word, // Upper 32 bits of the timer - timecmpl: rv32::Word, // Lower 32 bits of the timer compare register - timecmph: rv32::Word, // Upper 32 bits of the timer compare register + pub cyclel: rv32::Word, // Lower 32 bits of the cycle counter + pub cycleh: rv32::Word, // Upper 32 bits of the cycle counter + pub timel: rv32::Word, // Lower 32 bits of the timer + pub timeh: rv32::Word, // Upper 32 bits of the timer + pub timecmpl: rv32::Word, // Lower 32 bits of the timer compare register + pub timecmph: rv32::Word, // Upper 32 bits of the timer compare register // Machine Information Registers - mvendorid: rv32::Word, // Vendor ID of the hart - marchid: rv32::Word, // Architecture ID of the hart - mimpid: rv32::Word, // Implementation ID of the hart - mhartid: rv32::Word, // Hardware thread ID of the hart + pub mvendorid: rv32::Word, // Vendor ID of the hart + pub marchid: rv32::Word, // Architecture ID of the hart + pub mimpid: rv32::Word, // Implementation ID of the hart + pub mhartid: rv32::Word, // Hardware thread ID of the hart // Machine Trap Stuffs - mscratch: rv32::Word, // Scratch register for machine trap handlers - mtvec: rv32::Word, // Address of trap handler - mie: rv32::Word, // Machine interrupt enable - mip: rv32::Word, // Machine interrupt pending + pub mscratch: rv32::Word, // Scratch register for machine trap handlers + pub mtvec: rv32::Word, // Address of trap handler + pub mie: rv32::Word, // Machine interrupt enable + pub mip: rv32::Word, // Machine interrupt pending - mepc: rv32::Word, // Machine exception program counter - mtval: rv32::Word, // Machine trap value - mcause: rv32::Word, // Machine trap cause + pub mepc: rv32::Word, // Machine exception program counter + pub mtval: rv32::Word, // Machine trap value + pub mcause: rv32::Word, // Machine trap cause + + // Note: only a few bits are used. (Machine = 3, User = 0) + // Bits 0..1 = privilege. + // Bit 2 = WFI (Wait for interrupt) + // Bit 3+ = Load/Store reservation LSBs. + pub extraflags: rv32::Word, } pub struct CPU { state: CPUState, instruction_decoder: Rc>, extensions: Vec, + last_it_time: u128, } impl CPU { @@ -94,9 +103,11 @@ impl CPU { mepc: 0, mtval: 0, mcause: 0, + extraflags: 0, }, instruction_decoder, extensions, + last_it_time: 0, } } @@ -106,9 +117,19 @@ impl CPU { println!("-----------------"); println!("VM > Initializing CPU"); + self.last_it_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_micros(); + self.state.pc = DRAM_BASE as rv32::Word; self.state.x[0] = 0x00000000; // x0 is tied to ground - self.state.x[2] = DRAM_BASE + ram::DRAM_SIZE as u32; // x2 the stack pointer + self.state.x[2] = DRAM_BASE + DRAM_SIZE as u32; // x2 the stack pointer + self.state.mvendorid = 0x696969; // Vendor ID of the hart + self.state.marchid = 0x285700; // Architecture ID of the hart + self.state.mimpid = 0; // Implementation ID of the hart + self.state.mhartid = 0; // Hardware thread ID of the hart + println!("VM > CPU Initialisd with extensions {:?}", self.extensions); self.dump_reg(); } @@ -121,33 +142,119 @@ impl CPU { self.state.bus.borrow_mut().load_32(self.state.pc) } - pub fn step(&mut self) -> Result<(), String> { + pub fn step(&mut self, elapsed_micros: u128) -> Result<(), String> { // CSR stuff before fetch execute - // + let new_timer = ((self.state.timel as u128) + elapsed_micros) as rv32::Word; + if new_timer > self.state.timel { + self.state.timeh += 1; + } + self.state.timel = new_timer; - // TODO: We can execute multiple instructions per cycle - let inst = self.fetch(); - println!("VM > Fetched 0x{:08x}: 0x{:08x}", self.state.pc, inst); - self.state.x[0] = 0x00000000; + // handle time interrupt + if self.state.timel >= self.state.timecmpl + && self.state.timeh <= self.state.timecmph + && (self.state.timecmpl, self.state.timecmph) != (0, 0) + { + self.state.extraflags &= !4; + self.state.mip |= 1 << 7; // https://stackoverflow.com/a/61916199/2926815 Fire interrupt. + } else { + self.state.mip &= !(1 << 7); + } - self.state.pc = self.state.pc + rv32::WORD as u32; + // if WFI is set, we exit early + if self.state.extraflags & 4 != 0 { + return Ok(()); + } - self.instruction_decoder - .borrow_mut() - .decode_exec_inst(inst, &mut self.state)?; + let mut trap: rv32::Word = 0; + let mut rval: rv32::Word = 0; + let cycle: rv32::Word = self.state.cyclel; + + if (self.state.mip & (1 << 7) != 0) + && (self.state.mie & (1 << 7) != 0/*mtie*/) + && (self.state.mstatus & 0x8 /*mie*/ != 0) + { + // stall + trap = 0x80000007; + self.state.pc = self.state.pc - rv32::WORD as u32; + } else { + // TODO: We can execute multiple instructions per cycle + + // fetch + let inst = self.fetch(); + println!("VM > Fetched 0x{:08x}: 0x{:08x}", self.state.pc, inst); + self.state.x[0] = 0x00000000; + + // decode and execute + self.instruction_decoder + .borrow_mut() + .decode_exec_inst(inst, &mut self.state)?; + + self.state.pc = self.state.pc + rv32::WORD as u32; + } + + // handle trap and interrupt + if trap != 0 { + if trap & 0x80000000 != 0 { + // interrupt + self.state.mcause = trap; + self.state.mtval = 0; + self.state.pc = self.state.pc + rv32::WORD as u32; + } else { + // trap + self.state.mcause = trap - 1; + self.state.mtval &= if trap > 5 && trap <= 8 { + rval + } else { + self.state.pc + }; + } + self.state.mepc = self.state.pc; // the kernel may advance mepc on it's own + // on an interrupt, the system will move MIE into MPIE + self.state.mstatus = + (((self.state.mstatus) & 0x08) << 4) | (((self.state.extraflags) & 3) << 11); + self.state.pc = self.state.mtvec - rv32::WORD as u32; + // enter machine mode + self.state.extraflags |= 3; + self.state.pc = self.state.pc + rv32::WORD as u32; + } + + if self.state.cyclel > cycle { + self.state.cycleh += 1; + } + self.state.cyclel = cycle; + + Ok(()) + } + + pub fn exec_step(&mut self) -> Result<(), String> { + let since_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + let us = since_epoch.as_micros(); + let elapsed = us - self.last_it_time; + self.last_it_time += elapsed; + + self.step(elapsed)?; Ok(()) } pub fn exec(&mut self) -> Result<(), String> { - while self.state.pc - DRAM_BASE < ram::DRAM_SIZE as u32 { - self.step()?; - self.dump_reg(); + while self.state.pc - DRAM_BASE < DRAM_SIZE as u32 { + let since_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + let us = since_epoch.as_micros(); + let elapsed = us - self.last_it_time; + self.last_it_time += elapsed; + + self.step(elapsed)?; } Ok(()) } - fn dump_reg(&mut self) { + pub fn dump_reg(&mut self) { println!("VM > Dumping registers"); println!(" > PC : 0x{:08x}", self.state.pc); for i in 0..8 { diff --git a/src/ext/a/mod.rs b/src/ext/a/mod.rs index e69de29..635d37c 100644 --- a/src/ext/a/mod.rs +++ b/src/ext/a/mod.rs @@ -0,0 +1,69 @@ +use std::usize; + +use bits::match_mask; +use enum_dispatch::*; +use strum::EnumIter; + +use super::encoding::{GenInstruction, Instruction}; +use crate::cpu; +use crate::helpers::sext; +use crate::system::rv32; + +// FOR BRANCH INSTRUCTIONS ITS IMPERATIVE TO REMEMBER +// THAT WE INCREMENT PC AFTER THE EXECUTION + +#[derive(Default, Copy, Clone)] +pub struct LRW; // LR.W rd, rs1 - Load Reserved Word + // Load a word from memory into rd + // and set the extraflags to rs1 + // rd = mem[rs1] +impl Instruction for LRW { + fn name(&self) -> &'static str { + "LR.W" + } + + fn match_inst(&self, inst: rv32::Word) -> bool { + match_mask!(inst, "00010xx00000xxxxx010xxxxx0101111") + } + + fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { + let inst = unsafe { inst.R }; + let rs1 = state.x[inst.rs1() as usize]; + state.extraflags = (state.extraflags & 0x07) | (rs1 << 3); + state.x[inst.rd() as usize] = sext(state.bus.borrow_mut().load_32(rs1), 32); + } +} + +#[derive(Default, Copy, Clone)] +pub struct SCW; // SC.W rd, rs1 - Store Conditional Word + // Store a word from rd into memory + // if the extraflags match rs1 + // mem[rs1] = rd +impl Instruction for SCW { + fn name(&self) -> &'static str { + "SC.W" + } + + fn match_inst(&self, inst: rv32::Word) -> bool { + match_mask!(inst, "00011xxxxxxxxxxxx010xxxxx0101111") + } + + fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { + let inst = unsafe { inst.R }; + let rs1 = state.x[inst.rs1() as usize]; + let write_flag = (state.extraflags >> 3) == (rs1 & 0x1fffffff); + if write_flag { + state.bus.borrow_mut().store_32(rs1, state.x[inst.rs2() as usize]); + state.x[inst.rd() as usize] = 0; + } else { + state.x[inst.rd() as usize] = 1; + } + } +} + +#[derive(EnumIter)] +#[enum_dispatch(Instruction)] +pub enum ExtensionA { + LRW(LRW), + SCW(SCW), +} diff --git a/src/ext/decode.rs b/src/ext/decode.rs index 3746ed7..81e7f64 100644 --- a/src/ext/decode.rs +++ b/src/ext/decode.rs @@ -4,7 +4,10 @@ use super::encoding::{GenInstruction, Instruction}; use crate::cpu; use crate::system::rv32; +use crate::ext::a; use crate::ext::i; +use crate::ext::m; +use crate::ext::z; pub struct DecodeCycle { extensions: Vec, @@ -47,6 +50,21 @@ impl DecodeCycle { return Ok(()); } } + 'a' => { + if let Some(()) = enumerate_extension::(inst, state) { + return Ok(()); + } + } + 'm' => { + if let Some(()) = enumerate_extension::(inst, state) { + return Ok(()); + } + } + 'z' => { + if let Some(()) = enumerate_extension::(inst, state) { + return Ok(()); + } + } _ => println!("VM > Unknown Extension"), } } diff --git a/src/ext/encoding.rs b/src/ext/encoding.rs index 9a819c1..253c34c 100644 --- a/src/ext/encoding.rs +++ b/src/ext/encoding.rs @@ -159,3 +159,4 @@ pub union GenInstruction { pub U: std::mem::ManuallyDrop, pub J: std::mem::ManuallyDrop, } + diff --git a/src/ext/i/mod.rs b/src/ext/i/mod.rs index 02c0d81..8a1421f 100644 --- a/src/ext/i/mod.rs +++ b/src/ext/i/mod.rs @@ -27,7 +27,6 @@ impl Instruction for LUI { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing LUI"); let inst = unsafe { inst.U }; let val = inst.full_imm() << 12; state.x[inst.rd() as usize] = val; @@ -47,7 +46,6 @@ impl Instruction for AUIPC { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing AUIPC"); let inst = unsafe { inst.U }; let val = inst.full_imm() << 12; let pc_add = state.pc.wrapping_add(val); @@ -69,7 +67,6 @@ impl Instruction for JAL { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing JAL"); let inst = unsafe { inst.J }; let offset = sext(inst.full_imm() << 1, 32); let pc = offset.wrapping_add(state.pc); @@ -93,7 +90,6 @@ impl Instruction for JALR { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing JALR"); let inst = unsafe { inst.I }; let offset = sext(inst.full_imm(), 32); let pc = offset.wrapping_add(state.x[inst.rs1() as usize]); @@ -116,7 +112,6 @@ impl Instruction for BRANCH { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing BEQ, BNE, BLT, BGE, BLTU, BGEU"); let inst = unsafe { inst.B }; let offset = state.pc + (inst.sext_imm() << 1) - 4; match inst.funct3() { @@ -174,7 +169,6 @@ impl Instruction for LOAD { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing LOAD"); let inst = unsafe { inst.I }; let offset = inst.sext_imm(); let addr = state.x[inst.rs1() as usize].wrapping_add(offset); @@ -213,7 +207,6 @@ impl Instruction for STORE { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing STORE"); let inst = unsafe { inst.S }; let offset = inst.sext_imm(); let addr = state.x[inst.rs1() as usize].wrapping_add(offset); @@ -266,7 +259,6 @@ impl Instruction for IMM { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing ADDI, SLTI, SLTIU, XORI, ORI, ANDI"); let inst = unsafe { inst.I }; let mut retval = 0; let rs1 = state.x[inst.rs1() as usize]; @@ -305,7 +297,6 @@ impl Instruction for SHIFTI { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing SLLI, SRLI, SRAI"); let inst = unsafe { inst.R }; // fun7 is the L/A selector // rs2 is shamt let mut retval = 0; @@ -340,7 +331,6 @@ impl Instruction for OP { } fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { - println!("VM > Executing ADD, SUB, SLI, SLT, SLTU, XOR, SRL, SRA, OR, AND"); let inst = unsafe { inst.R }; let mut retval = 0; let rs1 = state.x[inst.rs1() as usize]; diff --git a/src/ext/m/mod.rs b/src/ext/m/mod.rs index e69de29..a78cc0c 100644 --- a/src/ext/m/mod.rs +++ b/src/ext/m/mod.rs @@ -0,0 +1,40 @@ +use std::usize; + +use bits::match_mask; +use enum_dispatch::*; +use strum::EnumIter; + +use super::encoding::{GenInstruction, Instruction}; +use crate::cpu; +use crate::ext::encoding::ImmediateMode; +use crate::helpers::sext; +use crate::system::rv32; + +// FOR BRANCH INSTRUCTIONS ITS IMPERATIVE TO REMEMBER +// THAT WE INCREMENT PC AFTER THE EXECUTION + +#[derive(Default, Copy, Clone)] +pub struct MULW; // MULW rd, rs1, rs2 - Multiply Word + // Multiply rs1 and rs2 and store the lower 32 bits in rd +impl Instruction for MULW { + fn name(&self) -> &'static str { + "MULW" + } + + fn match_inst(&self, inst: rv32::Word) -> bool { + match_mask!(inst, "xxxxxxxxxxxxxxxxxxxxxxxxx0110111") + } + + fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { + let inst = unsafe { inst.U }; + let val = inst.full_imm() << 12; + state.x[inst.rd() as usize] = val; + } +} + +#[derive(EnumIter)] +#[enum_dispatch(Instruction)] +pub enum ExtensionM { + MULW(MULW), +} + diff --git a/src/ext/mod.rs b/src/ext/mod.rs index 1fe3e72..694abd1 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -3,6 +3,9 @@ use crate::system::rv32; pub mod encoding; pub mod decode; pub mod i; +pub mod a; +pub mod m; +pub mod z; // Instruction bitmasks // This will be awkward as the instruction types diff --git a/src/ext/z/mod.rs b/src/ext/z/mod.rs new file mode 100644 index 0000000..cde9ca2 --- /dev/null +++ b/src/ext/z/mod.rs @@ -0,0 +1,46 @@ +use std::usize; + +use bits::match_mask; +use enum_dispatch::*; +use strum::EnumIter; + +use super::encoding::{GenInstruction, Instruction}; +use crate::cpu; +use crate::ext::encoding::ImmediateMode; +use crate::helpers::sext; +use crate::system::rv32; + +// ZiCSR - Control and Status Register Instructions +// WILL ALSO MATCH ZIENCEI INSTRUCTIONS +// ALTHOUGH THEY ARE NOT IMPLEMENTED YET + +// FOR BRANCH INSTRUCTIONS ITS IMPERATIVE TO REMEMBER +// THAT WE INCREMENT PC AFTER THE EXECUTION + +#[derive(Default, Copy, Clone)] +pub struct CSRRW; // CSRRW rd, rs1, csr - Atomic Read/Write CSR + // Read the CSR into rd, then write rs1 into the CSR + // rd = csr + // csr = rs1 +impl Instruction for CSRRW { + fn name(&self) -> &'static str { + "CSRRW" + } + + fn match_inst(&self, inst: rv32::Word) -> bool { + match_mask!(inst, "xxxxxxxxxxxxxxxxxxxxxxxxx0110111") + } + + fn step(&self, inst: GenInstruction, state: &mut cpu::CPUState) { + let inst = unsafe { inst.U }; + let val = inst.full_imm() << 12; + state.x[inst.rd() as usize] = val; + } +} + +#[derive(EnumIter)] +#[enum_dispatch(Instruction)] +pub enum ExtensionZ { + CSRRW(CSRRW), +} + diff --git a/src/main.rs b/src/main.rs index 6955238..af3b3de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,11 @@ use std::io::BufReader; use std::io::Read; use std::{cell::RefCell, rc::Rc}; -mod management; mod cpu; mod err; mod ext; mod helpers; +mod management; mod system; use crate::cpu::*; @@ -21,12 +21,11 @@ struct VMRV32I { bus: Rc>, cpu: cpu::CPU, instruction_decoder: Rc>, - manager: management::Management, } impl VMRV32I { fn new() -> VMRV32I { - let extensions = vec!['i']; + let extensions = vec!['i', 'm', 'a', 'z']; let bus = Rc::new(RefCell::new(bus::Bus::new())); let instruction_decoder = @@ -42,7 +41,6 @@ impl VMRV32I { cpu, bus, instruction_decoder, - manager: management::Management::new(), } } @@ -67,6 +65,10 @@ impl VMRV32I { buffer.len() as u32 } + fn has_load_prog(&self) -> bool { + self.bus.borrow_mut().load_32(bus::DRAM_BASE) != 0 + } + fn dump_prog(&mut self, size: u32) { println!("VM > Dumping program (virtual addresses)"); for i in 0..size { @@ -81,7 +83,16 @@ impl VMRV32I { } fn dump_relavent_memory(&self) { - println!(""); + println!("VM > Dumping relavent memory"); + } + + fn dispatch_step(&mut self) { + match self.cpu.exec_step() { + Ok(_) => (), + Err(e) => println!("VM > Program exited violently with error: {}", e), + } + + self.cpu.dump_reg(); } fn dispatch(&mut self) { @@ -90,20 +101,78 @@ impl VMRV32I { Err(e) => println!("VM > Program exited violently with error: {}", e), } - loop { - println!("VM > CPU has stalled"); - std::thread::sleep(std::time::Duration::from_secs(10)); - } + self.cpu.dump_reg(); + + println!("VM > CPU has stalled"); } } fn main() { - println!("VM Starting Up"); - println!("VM Loading CPU Management Engine"); + println!("VM > Loading CPU Management Engine"); + let manager = management::Management::new(); + println!("VM > Starting Up"); let mut vm = VMRV32I::new(); - vm.manager.prompt(); - let size = vm.load_prog("./test/test.bin"); - vm.dump_prog(size); - vm.dispatch(); + let mut should_run = false; + manager + .vm_params() + .iter() + .for_each(|action| match action.action { + management::Action::Load => { + println!("VM > Loading file: {}", action.arg); + vm.load_prog(&action.arg); + } + management::Action::Run => { + println!("VM > Running program"); + should_run = true; + } + _ => (), + }); + + if should_run && vm.has_load_prog() { + vm.dispatch(); + vm.dump_relavent_memory(); + return; + } else if should_run { + println!("VM > CPU has stalled"); + return; + } + + println!("VM > No program loaded"); + + // event loop for interactive mode + loop { + let action = manager.prompt(); + match action.action { + management::Action::Load => { + println!("VM > Loading file: {}", action.arg); + vm.load_prog(&action.arg); + } + management::Action::Run => { + println!("VM > Running program"); + if !vm.has_load_prog() { + println!("VM > No program loaded"); + continue; + } + vm.dispatch(); + } + management::Action::Step => { + println!("VM > Stepping program"); + if !vm.has_load_prog() { + println!("VM > No program loaded"); + continue; + } + vm.dispatch_step(); + } + management::Action::Dump => { + println!("VM > Dumping program"); + vm.dump_prog(0x100); + } + management::Action::Quit => { + println!("VM > Quitting"); + break; + } + _ => (), + } + } } diff --git a/src/management.rs b/src/management.rs index d0a9675..2a80c84 100644 --- a/src/management.rs +++ b/src/management.rs @@ -1,9 +1,14 @@ use std::io::{stdin, Write}; +use clap::Parser; + +#[derive(Debug)] pub struct VMAction { - action: Action, + pub action: Action, + pub arg: String, } +#[derive(Debug)] pub enum Action { Load, Step, @@ -11,22 +16,51 @@ pub enum Action { Inspect, Dump, Reg, + Quit, } pub struct Management { pause: bool, } +#[derive(Parser, Debug)] +#[command(name = "Riscy-Rust, a RV32ima Virtual Machine")] +#[command(version = "0.1.0")] +#[command(author = "Ben Kyd ")] +struct Cli { + #[arg(short, long, value_name = "FILE")] + load: Option, + #[arg(short, long)] + run: bool, +} + impl Management { pub fn new() -> Management { Management { pause: true } } - pub fn vm_params() -> Vec { - vec![VMAction { action: Action::Load }] + pub fn vm_params(&self) -> Vec { + let cli = Cli::parse(); + let mut actions = Vec::new(); + + match cli.load { + Some(file) => actions.push(VMAction { + action: Action::Load, + arg: file, + }), + None => (), + } + + if cli.run { + actions.push(VMAction { + action: Action::Run, + arg: String::new(), + }) + } + actions } - pub fn prompt(&self) -> Action { + pub fn prompt(&self) -> VMAction { print!("VM >> "); std::io::stdout().flush().unwrap(); @@ -35,12 +69,33 @@ impl Management { let mut parts = input.trim().split_whitespace(); let command = parts.next().unwrap(); - let args = parts; + let mut args = parts; match command { - "load" => Action::Load, - "step" => Action::Step, - "run" => Action::Run, + "load" => VMAction { + action: Action::Load, + arg: args.next().unwrap().to_string(), + }, + "step" => VMAction { + action: Action::Step, + arg: String::new(), + }, + "run" => VMAction { + action: Action::Run, + arg: String::new(), + }, + "quit" | "q" => VMAction { + action: Action::Quit, + arg: String::new(), + }, + "help" | "h" => { + println!("VM > Commands:"); + println!("VM > load - load a program into memory"); + println!("VM > step - step through the program"); + println!("VM > run - run the program"); + println!("VM > quit - quit the program"); + self.prompt() + } _ => { println!("VM > Command {} not found", command); self.prompt() diff --git a/src/system/bus.rs b/src/system/bus.rs index 7cd525f..21c09e8 100644 --- a/src/system/bus.rs +++ b/src/system/bus.rs @@ -1,62 +1,68 @@ pub const DRAM_BASE: u32 = 0x80000000; +pub const DRAM_SIZE: u32 = 1 * 1024 * 1024 * 1024; // 1GBram +pub const DRAM_TOP: u32 = DRAM_BASE + DRAM_SIZE; + +pub const UART_BASE: u32 = 0x10000000; +pub const UART_SIZE: u32 = 0x100; +pub const UART_TOP: u32 = UART_BASE + UART_SIZE; use crate::system::ram; use crate::system::rv32; +use crate::system::uart; pub struct Bus { memory: ram::RAM, + uart: uart::UART, } impl Bus { pub fn new() -> Bus { Bus { memory: ram::RAM::new(), + uart: uart::UART::new(), } } pub fn load_8(&mut self, address: rv32::XLen) -> rv32::Byte { match address { - DRAM_BASE.. => self.memory.read_8(address), + DRAM_BASE..=DRAM_TOP => self.memory.read_8(address), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); - rv32::Byte::default() } } } pub fn load_16(&mut self, address: rv32::XLen) -> rv32::HalfWord { match address { - DRAM_BASE.. => self.memory.read_16(address), + DRAM_BASE..=DRAM_TOP => self.memory.read_16(address), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); - rv32::HalfWord::default() } } } pub fn load_32(&mut self, address: rv32::XLen) -> rv32::Word { match address { - DRAM_BASE.. => self.memory.read_32(address), + UART_BASE..=UART_TOP => self.uart.read_kb(address), + DRAM_BASE..=DRAM_TOP => self.memory.read_32(address), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); - rv32::Word::default() } } } pub fn load_64(&mut self, address: rv32::XLen) -> rv32::DoubleWord { match address { - DRAM_BASE.. => self.memory.read_64(address), + DRAM_BASE..=DRAM_TOP => self.memory.read_64(address), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); - rv32::DoubleWord::default() } } } pub fn store_8(&mut self, address: rv32::XLen, data: rv32::Byte) { match address { - DRAM_BASE.. => self.memory.write_8(address, data), + DRAM_BASE..=DRAM_TOP => self.memory.write_8(address, data), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); } @@ -65,7 +71,7 @@ impl Bus { pub fn store_16(&mut self, address: rv32::XLen, data: rv32::HalfWord) { match address { - DRAM_BASE.. => self.memory.write_16(address, data), + DRAM_BASE..=DRAM_TOP => self.memory.write_16(address, data), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); } @@ -74,7 +80,8 @@ impl Bus { pub fn store_32(&mut self, address: rv32::XLen, data: rv32::Word) { match address { - DRAM_BASE.. => self.memory.write_32(address, data), + UART_BASE..=UART_TOP => self.uart.write(address, data), + DRAM_BASE..=DRAM_TOP => self.memory.write_32(address, data), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); } @@ -83,7 +90,7 @@ impl Bus { pub fn store_64(&mut self, address: rv32::XLen, data: rv32::DoubleWord) { match address { - DRAM_BASE.. => self.memory.write_64(address, data), + DRAM_BASE..=DRAM_TOP => self.memory.write_64(address, data), _ => { panic!("VM > BUS > Peripheral at 0x{:08x} does not exist", address); } diff --git a/src/system/mod.rs b/src/system/mod.rs index 5695648..fc1fe80 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -1,3 +1,4 @@ pub mod bus; +pub mod uart; pub mod ram; pub mod rv32; diff --git a/src/system/ram.rs b/src/system/ram.rs index 6ad0144..1410323 100644 --- a/src/system/ram.rs +++ b/src/system/ram.rs @@ -1,15 +1,12 @@ use crate::system::bus; use crate::system::rv32; -pub const DRAM_SIZE: u32 = 1 * 1024 * 1024 * 1024; // 1GB -// pub const DRAM_SIZE: u32 = 1 * 1024; // 1KB - pub struct RAM(pub Vec); impl RAM { pub fn new() -> RAM { - println!("VM > Initialised RAM with size: {} bytes", DRAM_SIZE); - RAM(vec![0; DRAM_SIZE as usize]) + println!("VM > Initialised RAM with size: {} bytes", bus::DRAM_SIZE); + RAM(vec![0; bus::DRAM_SIZE as usize]) } pub fn len(&mut self) -> usize { diff --git a/src/system/uart.rs b/src/system/uart.rs new file mode 100644 index 0000000..b03da19 --- /dev/null +++ b/src/system/uart.rs @@ -0,0 +1,51 @@ +use crate::system::bus; +use crate::system::rv32; + +pub const UART_TXD: u32 = bus::UART_BASE + 0x00; +pub const UART_RXD: u32 = bus::UART_BASE + 0x05; + +fn didkeypress() -> bool { + use std::io::Read; + let mut stdin = std::io::stdin(); + let mut buffer = [0; 1]; + stdin.read_exact(&mut buffer).unwrap(); + buffer[0] != 0 +} + +fn getkey() -> u8 { + use std::io::Read; + let mut stdin = std::io::stdin(); + let mut buffer = [0; 1]; + stdin.read_exact(&mut buffer).unwrap(); + buffer[0] +} + + +pub struct UART(); +impl UART { + pub fn new() -> UART { + println!("VM > Initialised UART"); + UART() + } + + pub fn write(&mut self, address: rv32::XLen, value: rv32::Word) { + match address { + UART_TXD => { + print!("{}", value as u8 as char); + } + _ => { + panic!("VM > UART > Peripheral at 0x{:08x} does not exist", address); + } + } + } + + pub fn read_kb(&mut self, address: rv32::XLen) -> rv32::Word { + if address != UART_RXD { + return 0x60 | didkeypress() as u8 as rv32::Word; + } else if address == UART_TXD && didkeypress() { + return getkey() as rv32::Word; + } + 0 + } +} + diff --git a/test/linux.bin b/test/linux.bin new file mode 100644 index 0000000..751fc60 Binary files /dev/null and b/test/linux.bin differ diff --git a/test/rootfs.cpio b/test/rootfs.cpio new file mode 100644 index 0000000..085404b Binary files /dev/null and b/test/rootfs.cpio differ diff --git a/test/start-qemu.sh b/test/start-qemu.sh new file mode 100644 index 0000000..45fa29d --- /dev/null +++ b/test/start-qemu.sh @@ -0,0 +1,14 @@ +#!/bin/sh +( +BINARIES_DIR="${0%/*}/" +cd ${BINARIES_DIR} + +if [ "${1}" = "serial-only" ]; then + EXTRA_ARGS='-nographic' +else + EXTRA_ARGS='' +fi + +export PATH="/home/benk/dprog/misc/mini-rv32ima/buildroot/output/host/bin:${PATH}" +exec qemu-system-riscv32 -M virt -bios none -kernel Image -append "rootwait root=/dev/vda ro" -drive file=rootfs.ext2,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 -nographic -cpu rv32,mmu=off ${EXTRA_ARGS} +) diff --git a/test/test b/test/test index d8c5641..eee44fb 100755 Binary files a/test/test and b/test/test differ diff --git a/test/test.map b/test/test.map index 82647ec..762f95c 100644 --- a/test/test.map +++ b/test/test.map @@ -10,7 +10,7 @@ SYMBOL TABLE: 00000000 l d .debug_abbrev 00000000 .debug_abbrev 00000000 l d .debug_aranges 00000000 .debug_aranges 00000000 l d .debug_str 00000000 .debug_str -00000000 l df *ABS* 00000000 ccSaJ0Sk.o +00000000 l df *ABS* 00000000 ccKCKnvP.o 80000000 l .text 00000000 main 80000018 l .text 00000000 loop 80000034 l .text 00000000 exit