Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0ebb5df
read memory from a file path
tomip01 Dec 26, 2024
6af98b6
better vm error explanation
tomip01 Dec 26, 2024
b4cbd92
convert numbers to opcode instructions
tomip01 Dec 26, 2024
53ec0b5
use std method to convert big endian
tomip01 Dec 26, 2024
6700edb
Merge pull request #1 from tomip01/feature/initial_vm
tomip01 Dec 26, 2024
b3106a4
include add instruction
tomip01 Dec 26, 2024
8cddad8
add instructions and & not
tomip01 Dec 26, 2024
a58acf0
add instruction jmp & branch
tomip01 Dec 26, 2024
f37dc9d
add instruction lea & jsr
tomip01 Dec 26, 2024
ed2cca0
change mask from hex to binary
tomip01 Dec 26, 2024
2a38d70
add instruction ld & ldr
tomip01 Dec 26, 2024
9c8e85f
add instructions st & ldi
tomip01 Dec 27, 2024
498e986
add instruction sti & str
tomip01 Dec 27, 2024
24f3feb
add combined test and refactor of functions
tomip01 Dec 27, 2024
35c776b
refactor VMError names and uses
tomip01 Dec 27, 2024
2b0f9d0
refactor memory
tomip01 Dec 27, 2024
c5f8222
remove reduntant error returns
tomip01 Dec 27, 2024
772e04a
Merge pull request #2 from tomip01/feature/implementing_instructions
tomip01 Dec 27, 2024
d399d42
add first trap instr
tomip01 Dec 27, 2024
bbde259
add documentation for instructions
tomip01 Dec 27, 2024
4f4e154
add trap instr out & puts
tomip01 Dec 27, 2024
650e5e3
refactor instructions to separate file
tomip01 Dec 27, 2024
e29e6a8
finish all trap instr
tomip01 Dec 27, 2024
87893c4
simplify writing in stdout
tomip01 Dec 30, 2024
ef7db7f
add doc comments
tomip01 Dec 30, 2024
6eea5cc
Merge pull request #3 from tomip01/feature/memory_operations
tomip01 Dec 30, 2024
dda3f85
remove private call methods on memory
tomip01 Dec 30, 2024
1403228
Merge branch 'dev' into feature/traps
tomip01 Dec 30, 2024
217213e
Merge pull request #4 from tomip01/feature/traps
tomip01 Dec 30, 2024
f48406d
add chech_key() method
tomip01 Dec 30, 2024
dd5eb3c
fix old merge conflict files
tomip01 Dec 30, 2024
3718c25
add test and move to correct modules
tomip01 Dec 30, 2024
a2e2ba0
handles args from launching process
tomip01 Dec 30, 2024
325a401
refactor README.md
tomip01 Dec 30, 2024
f7b375c
add termios config for correct getc function
tomip01 Dec 30, 2024
33437f4
refactor main
tomip01 Dec 30, 2024
a290579
add comments and refactors
tomip01 Dec 30, 2024
9f91c24
Merge branch 'dev' into feature/run_programs
tomip01 Jan 2, 2025
9ee23b5
change error prints to the standard error
tomip01 Jan 2, 2025
0791b64
Merge branch 'feature/run_programs' of github.com:tomip01/lc3-vm into…
tomip01 Jan 2, 2025
e7996fc
Merge pull request #5 from tomip01/feature/run_programs
tomip01 Jan 2, 2025
93bf9c9
add example to README
tomip01 Jan 2, 2025
a8e3acb
Improve usage comment when incorrect amount of arguments
tomip01 Jan 6, 2025
51cbd35
Update main loop execution comments
tomip01 Jan 6, 2025
b37e99d
Remove old public members in the VM and clarify comments on
tomip01 Jan 6, 2025
2cad29e
Add comments for the use of args in set and get register
tomip01 Jan 6, 2025
ea89cfd
Improve
tomip01 Jan 6, 2025
3b71fca
Add usage of 'instr' in 'execute' method
tomip01 Jan 6, 2025
04491ec
Explain why RTI and RES are invalid Opcodes
tomip01 Jan 6, 2025
b5d8659
Make documentation explicit on AND and ADD instructions
tomip01 Jan 6, 2025
e87d92c
Make explicit and improve the documentation of 'br'
tomip01 Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ unwrap_used = "deny"
expect_used = "deny"

arithmetic_side_effects = "deny"
overflow_check_conditional = "warn"
panicking_overflow_checks = "warn"
manual_saturating_arithmetic = "warn"

[dependencies]
termios = "0.3.3"
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
build:
cargo build
run:
cargo run $(FILEPATH)
test:
cargo test
check:
cargo check
lint:
cargo clippy -- -D warnings

.PHONY: all run test check lint
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

An implementation of the LC3 (Little Computer 3) Virtual Machine in Rust

A VM is a program that acts like a computer. It simulates a CPU along with a few other hardware components, allowing it to perform arithmetic, read and write to memory, and interact with I/O devices, just like a physical computer. Most importantly, it can understand a machine language which you can use to program it.

## Reference

This was made by following this guide: https://www.jmeiners.com/lc3-vm/

## Installation

1. Clone the repository
```bash
git clone https://github.com/tomip01/lc3-vm.git
```
2. Change to the directory:
```bash
cd lc3-vm
```
3. Build project:
```bash
make build
```

## Usage
**Run object file**
```bash
make run FILEPATH=<path/to/file>
```

**Run example**
```bash
make run FILEPATH=./images/2048.obj
```
Binary file added images/2048.obj
Binary file not shown.
Binary file added images/rogue.obj
Binary file not shown.
Binary file added images/test-image-load-big-endian
Binary file not shown.
52 changes: 52 additions & 0 deletions src/lc3/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use super::vm::VMError;

pub fn sign_extend(mut value: u16, bit_count: u16) -> Result<u16, VMError> {
let last_bit_position = bit_count.checked_sub(1).ok_or(VMError::Overflow)?;
if (value >> (last_bit_position) & 1) == 1 {
value |= 0xFFFF << bit_count;
}
Ok(value)
}

pub fn concatenate_bytes(bytes: &[u8]) -> Result<u16, VMError> {
// maybe this check is redundant
if bytes.len() != 2 {
return Err(VMError::ConcatenatingBytes(String::from(
"Image file is not made from words of 16 bits",
)));
}
let first_byte: u8 = *bytes
.first()
.ok_or(VMError::ConcatenatingBytes(String::from(
"Non existing first byte",
)))?;
let second_byte: u8 = *bytes
.get(1)
.ok_or(VMError::ConcatenatingBytes(String::from(
"Non existing second byte",
)))?;
let res = u16::from_be_bytes([first_byte, second_byte]);
Ok(res)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn sign_extend_mantains_sign() -> Result<(), VMError> {
let x = 0b11111;
let y = 0b01111;
assert_eq!(sign_extend(x, 5)?, 0xFFFF);
assert_eq!(sign_extend(y, 5)?, 0x000F);
Ok(())
}

#[test]
fn concatenates_two_bytes() -> Result<(), VMError> {
let buffer: [u8; 2] = [0x12, 0x34];
let concatenated = concatenate_bytes(&buffer)?;
assert_eq!(concatenated, 0x1234);
Ok(())
}
}
117 changes: 117 additions & 0 deletions src/lc3/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::{fs, io::Read};

use super::{bytes::concatenate_bytes, vm::VMError};

const MEMORY_MAX: usize = 1 << 16;
// Keyboard status register
const MR_KBSR: usize = 0xFE00;
// Keyboard data register
const MR_KBDR: usize = 0xFE02;

pub struct Memory {
memory: [u16; MEMORY_MAX],
}

impl Memory {
pub fn new() -> Memory {
Memory {
memory: [0; MEMORY_MAX],
}
}

pub fn read_image(&mut self, image_path: &str) -> Result<(), VMError> {
let content = &fs::read(image_path).map_err(|e| {
VMError::ReadingFile(format!("Failed to read file {}: {}", image_path, e))
})?;
self.read_image_bytes(content)?;
Ok(())
}

fn read_image_bytes(&mut self, bytes: &[u8]) -> Result<(), VMError> {
let mut collected: Vec<u16> = Vec::new();
let mut chunks_of_two_bytes = bytes.chunks_exact(2);
let origin: usize = concatenate_bytes(chunks_of_two_bytes.next().ok_or(
VMError::ConcatenatingBytes(String::from("No valid origin position from image")),
)?)?
.into();
for chunk in chunks_of_two_bytes {
let concatenated = concatenate_bytes(chunk)?;
collected.push(concatenated);
}

for (i, word) in collected.iter().enumerate() {
let index = i
.checked_add(origin)
.ok_or(VMError::MemoryIndex(String::from(
"Invalid index to access memory on loading",
)))?;
self.mem_write(*word, index)?;
}

Ok(())
}

pub fn mem_read(&mut self, index: usize) -> Result<u16, VMError> {
if index == MR_KBSR {
self.check_key()?;
}
let value = self
.memory
.get(index)
.ok_or(VMError::MemoryIndex(String::from(
"Invalid out of bounds when reading from memory",
)))?;
Ok(*value)
}

pub fn mem_write(&mut self, value: u16, index: usize) -> Result<(), VMError> {
let cell = self
.memory
.get_mut(index)
.ok_or(VMError::MemoryIndex(String::from(
"Index out of bound when writing memory",
)))?;
*cell = value;
Ok(())
}

fn check_key(&mut self) -> Result<(), VMError> {
let mut buffer: [u8; 1] = [0];
std::io::stdin()
.read_exact(&mut buffer)
.map_err(|e| VMError::StandardIO(format!("Cannot read from Standard Input: {}", e)))?;

if buffer[0] != 0 {
self.mem_write(1 << 15, MR_KBSR)?;
self.mem_write(buffer[0].into(), MR_KBDR)?;
} else {
self.mem_write(0, MR_KBSR)?;
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn read_and_write() -> Result<(), VMError> {
let mut mem = Memory::new();
mem.mem_write(0x4242, 0x2424)?;
assert_eq!(0x4242, mem.mem_read(0x2424)?);
Ok(())
}

#[test]
fn correct_image_read() -> Result<(), VMError> {
let mut mem = Memory::new();
// file containing 0x00 0x30 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7
mem.read_image("images/test-image-load-big-endian")?;
assert_eq!(mem.mem_read(0x3000)?, 0xf3f2);
assert_eq!(mem.mem_read(0x3001)?, 0xf5f4);
assert_eq!(mem.mem_read(0x3002)?, 0xf7f6);
Ok(())
}
}
5 changes: 5 additions & 0 deletions src/lc3/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod bytes;
pub mod memory;
pub mod opcode;
pub mod trap;
pub mod vm;
47 changes: 47 additions & 0 deletions src/lc3/opcode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::convert::TryFrom;

use super::vm::VMError;
pub enum Opcode {
BR = 0, /* branch */
Add, /* add */
LD, /* load */
ST, /* store */
Jsr, /* jump register */
And, /* bitwise and */
Ldr, /* load register */
Str, /* store register */
Rti, /* unused */
Not, /* bitwise not */
Ldi, /* load indirect */
Sti, /* store indirect */
Jmp, /* jump */
Res, /* reserved (unused) */
Lea, /* load effective address */
Trap, /* execute trap */
}

impl TryFrom<u16> for Opcode {
type Error = VMError;

fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0b0000 => Ok(Opcode::BR),
0b0001 => Ok(Opcode::Add),
0b0010 => Ok(Opcode::LD),
0b0011 => Ok(Opcode::ST),
0b0100 => Ok(Opcode::Jsr),
0b0101 => Ok(Opcode::And),
0b0110 => Ok(Opcode::Ldr),
0b0111 => Ok(Opcode::Str),
0b1000 => Ok(Opcode::Rti),
0b1001 => Ok(Opcode::Not),
0b1010 => Ok(Opcode::Ldi),
0b1011 => Ok(Opcode::Sti),
0b1100 => Ok(Opcode::Jmp),
0b1101 => Ok(Opcode::Res),
0b1110 => Ok(Opcode::Lea),
0b1111 => Ok(Opcode::Trap),
_ => Err(VMError::InvalidOpcode),
}
}
}
27 changes: 27 additions & 0 deletions src/lc3/trap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::vm::VMError;

pub enum TrapCode {
Getc,
Out,
Puts,
IN,
Putsp,
Halt,
}

impl TryFrom<u16> for TrapCode {
type Error = VMError;

fn try_from(value: u16) -> Result<Self, Self::Error> {
let trap = match value {
0x20 => TrapCode::Getc,
0x21 => TrapCode::Out,
0x22 => TrapCode::Puts,
0x23 => TrapCode::IN,
0x24 => TrapCode::Putsp,
0x25 => TrapCode::Halt,
_ => return Err(VMError::InvalidTrapCode),
};
Ok(trap)
}
}
Loading