Skip to content

Commit

Permalink
Merge branch 'hardware-exceptions'
Browse files Browse the repository at this point in the history
  • Loading branch information
NoxHarmonium committed Feb 24, 2024
2 parents 3d923ee + 998f4b1 commit 218e96f
Show file tree
Hide file tree
Showing 99 changed files with 4,977 additions and 1,585 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ jobs:
strategy:
matrix:
example-name:
["boring", "byte-sieve", "software-exception", "store-load"]
[
"boring",
"byte-sieve",
"hardware-exception",
"software-exception",
"store-load",
]

steps:
- uses: actions/checkout@v3
Expand All @@ -57,5 +63,5 @@ jobs:
run: rustup update stable

- name: Run ${{ matrix.example-name }}
run: make check
run: make clean check
working-directory: ./sirc-vm/examples/${{ matrix.example-name }}
6 changes: 6 additions & 0 deletions sirc-vm/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ Cargo.lock
# Test files used in examples
actual.hex
mem.bin

# Generated by examples to assert register contents
*.register-dump

# We usually don't want to check in logs
*.log
8 changes: 5 additions & 3 deletions sirc-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

resolver = "2"

members = [
members = [ "device-debug",
"device-ram",
"device-terminal",
"peripheral-bus",
"peripheral-clock",
"peripheral-mem",
"peripheral-cpu",
"toolchain",
"sbrc-vm",
"toolchain",
]
8 changes: 8 additions & 0 deletions sirc-vm/device-debug/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "device_debug"
version = "0.1.0"
edition = "2021"

[dependencies]
peripheral_bus = { path = "../peripheral-bus" }
log = "0.4.20"
91 changes: 91 additions & 0 deletions sirc-vm/device-debug/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::any::Any;

use log::debug;
use peripheral_bus::{
device::BusAssertions, device::Device, memory_mapped_device::MemoryMappedDevice,
};

///
/// A device that just exists to do integration tests on the VM
/// Allows programs to assert interrupts etc. when they need to
///
pub struct DebugDevice {
pub trigger_bus_error: bool,
pub trigger_interrupt: u8,
}

#[must_use]
pub fn new_debug_device() -> DebugDevice {
DebugDevice {
trigger_bus_error: false,
trigger_interrupt: 0,
}
}

impl Device for DebugDevice {
fn poll(&mut self, bus_assertions: BusAssertions, selected: bool) -> BusAssertions {
let io_assertions = self.perform_bus_io(bus_assertions, selected);
// TODO: Bus error should be edge triggered I think so we don't get double faults
// if devices are slow to stop asserting
if self.trigger_bus_error {
debug!("Bus error triggered!");

self.trigger_bus_error = false;
return BusAssertions {
bus_error: true,
..io_assertions
};
}
if self.trigger_interrupt > 0 {
debug!("Interrupt L{} error triggered!", self.trigger_interrupt);

let interrupt_level = self.trigger_interrupt;
self.trigger_interrupt = 0;
return BusAssertions {
interrupt_assertion: 0x1 << (interrupt_level - 1),
..io_assertions
};
}
io_assertions
}
fn as_any(&mut self) -> &mut dyn Any {
self
}
}

impl MemoryMappedDevice for DebugDevice {
fn read_address(&self, address: u32) -> u16 {
debug!("Reading from address 0x{address:X}");
match address {
0x0 => {
if self.trigger_bus_error {
0x1
} else {
0x0
}
}
0x1..=0x5 => {
if (address as u8) == self.trigger_interrupt {
0x1
} else {
0x0
}
}
_ => 0x0,
}
}

fn write_address(&mut self, address: u32, value: u16) {
debug!("Writing 0x{value:X} to address 0x{address:X}");

match address {
0x0 => self.trigger_bus_error = value == 0x1,
0x1..=0x5 => {
if value == 0x1 {
self.trigger_interrupt = address as u8
}
}
_ => {}
}
}
}
11 changes: 11 additions & 0 deletions sirc-vm/device-ram/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "device_ram"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
memmap = "0.7.0"
peripheral_bus = { path = "../peripheral-bus" }
log = "0.4.20"
110 changes: 110 additions & 0 deletions sirc-vm/device-ram/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::{
any::Any,
cell::RefCell,
fs::{File, OpenOptions},
path::PathBuf,
};

use memmap::{MmapMut, MmapOptions};
use peripheral_bus::{
device::BusAssertions, device::Device, memory_mapped_device::MemoryMappedDevice,
};

pub enum SegmentMemCell {
// At the moment, all raw segments get the maximum allowable of memory allocated
// for a single segment (16 bit address). This is wasteful but not a huge issue
// at the moment running on a machine with GBs of memory
RawMemory(Box<[u8; (0xFFFF * 2) + 2]>),
FileMapped(Box<File>, Box<MmapMut>),
}

pub struct RamDevice {
// TODO: Does this still need to be RefCell?
pub mem_cell: RefCell<SegmentMemCell>,
}

#[must_use]
pub fn new_ram_device_standard() -> RamDevice {
RamDevice {
mem_cell: RefCell::new(SegmentMemCell::RawMemory(Box::new([0; (0xFFFF * 2) + 2]))),
}
}

pub fn new_ram_device_file_mapped(file_path: PathBuf) -> RamDevice {
// TODO: Proper error handling?
let file = OpenOptions::new()
.read(true)
.write(true)
.open(file_path)
.unwrap();
// TODO: Proper error handling here too?
let mmap = unsafe { MmapOptions::new().map_mut(&file).unwrap() };

RamDevice {
mem_cell: RefCell::new(SegmentMemCell::FileMapped(Box::new(file), Box::new(mmap))),
}
}
impl Device for RamDevice {
fn poll(&mut self, bus_assertions: BusAssertions, selected: bool) -> BusAssertions {
self.perform_bus_io(bus_assertions, selected)
}
fn as_any(&mut self) -> &mut dyn Any {
self
}
}

impl MemoryMappedDevice for RamDevice {
fn read_address(&self, address: u32) -> u16 {
let address_pointer = address as usize * 2;

// Range check?
let cell = self.mem_cell.borrow();

let raw_memory: &[u8] = match *cell {
SegmentMemCell::RawMemory(ref mem) => &mem[..],
SegmentMemCell::FileMapped(_, ref mmap) => &mmap[..],
};
let byte_pair: [u8; 2] = raw_memory[address_pointer..=address_pointer + 1]
.try_into()
.unwrap();
u16::from_be_bytes(byte_pair)
}
fn write_address(&mut self, address: u32, value: u16) {
let address_pointer = address as usize * 2;
let byte_pair: [u8; 2] = u16::to_be_bytes(value);

let mut cell = self.mem_cell.borrow_mut();

let raw_memory: &mut [u8] = match *cell {
SegmentMemCell::RawMemory(ref mut mem) => &mut mem[..],
SegmentMemCell::FileMapped(_, ref mut mmap) => &mut mmap[..],
};
raw_memory[address_pointer] = byte_pair[0];
raw_memory[address_pointer + 1] = byte_pair[1];
}

fn read_raw_bytes(&self, limit: u32) -> Vec<u8> {
let cell = self.mem_cell.borrow();

let raw_memory: &[u8] = match *cell {
SegmentMemCell::RawMemory(ref mem) => &mem[..],
SegmentMemCell::FileMapped(_, ref mmap) => &mmap[..],
};

// TODO: Is this efficient in rust? Does it get optimised?
raw_memory
.iter()
.take(limit as usize * 2)
.copied()
.collect()
}
fn write_raw_bytes(&mut self, binary_data: &[u8]) {
let mut cell = self.mem_cell.borrow_mut();
let raw_memory: &mut [u8] = match *cell {
SegmentMemCell::RawMemory(ref mut mem) => &mut mem[..],
SegmentMemCell::FileMapped(_, ref mut mmap) => &mut mmap[..],
};

raw_memory[0..binary_data.len()].copy_from_slice(binary_data);
}
}
10 changes: 10 additions & 0 deletions sirc-vm/device-terminal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "device_terminal"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
peripheral_bus = { path = "../peripheral-bus" }
log = "0.4.20"
Loading

0 comments on commit 218e96f

Please sign in to comment.