Browse Source

implement pointers

also drop `MOV reg, mem` and `MOV mem, reg` instructions
pull/8/head
Murad Karammaev 2 years ago
parent
commit
d11531b9a2
Signed by: foxpy GPG Key ID: 78BE32418B0C8450
  1. 7
      ASM-SYNTAX.md
  2. 13
      DESIGN.md
  3. 3
      acceptance-tests/add_four_ints.asm
  4. 5
      acceptance-tests/add_four_ints.pgm
  5. 69
      toy_cpu_4bit/src/assembler.rs
  6. 104
      toy_cpu_4bit/src/cpu.rs
  7. 44
      toy_cpu_4bit/src/instruction.rs
  8. 9
      toy_cpu_4bit/src/lib.rs

7
ASM-SYNTAX.md

@ -53,14 +53,14 @@ instruction
'PRTCHK'
'PRTRD'
'PRTWR'
'LOAD'
'STORE'
carry
'0'
'1'
mov
'MOV' register ',' memory
'MOV' memory ',' register
'MOV' register-half ',' digit(0, 15)
'MOV' register ',' register
@ -84,9 +84,6 @@ register
'R0'
'R1'
memory
'[' digit(0, 15) ']'
register-half
register '.l'
register '.h'

13
DESIGN.md

@ -30,18 +30,17 @@ ALU operations.
### LOAD/STORE
```
+—+—+—+—+———————+
|0|0|M|R|A A A A|
+—+—+—+—+———————+
+—+—+—+—+—+—+—+—+
|0|0|M|0|0|0|0|0|
+—+—+—+—+—+—+—+—+
```
Performs load or store operation. Address in `DATA` segment is
pointed to by lower half or `R1`. Data is written to / read from `R0`.
`M``0` for load (read byte from memory and put in ALU register),
`1` for store (read byte from ALU register and put in memory).
`R``0` to use `R0` register, `1` to use `R1` register.
`AAAA` — 4-bit address in `DATA` segment.
### LOAD IMMEDIATE
```

3
acceptance-tests/add_four_ints.asm

@ -12,5 +12,6 @@ ADD
MOV R1.l, 0
MOV R1.h, 5
ADD
MOV [0xf], R0
MOV R1.l, 0xf
STORE
HALT

5
acceptance-tests/add_four_ints.pgm

@ -1,5 +1,5 @@
P2
10 1
11 1
255
67 # MOV R0.l, 3
@ -10,5 +10,6 @@ P2
80 # MOV R1.l, 0
117 # MOV R1.h, 5
228 # ADD
47 # MOV [0xf], R0
95 # MOV R1.l, 0xf
32 # STORE
245 # HALT

69
toy_cpu_4bit/src/assembler.rs

@ -17,7 +17,6 @@ const REGEX_REG: &str = r"(R[01])";
const REGEX_REG_HALF: &str = r"(R[01]\.[lh])";
const REGEX_NUM: &str = r"(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})";
const REGEX_INUM: &str = r"(-?\d{1,3}|-?0b[01]{1,8}|-?0o[0-7]{1,3}|-?0x[\da-fA-F]{1,2})";
const REGEX_ADDR: &str = r"\[(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})\]";
const REGEX_COMMA: &str = r"\s*,\s*";
const REGEX_CARRY: &str = r"([01])";
@ -201,14 +200,6 @@ impl Assembler {
table: vec![
asm_entry!(format!(r"^BYTE\s{}$", REGEX_NUM), parse_byte),
asm_entry!(
format!(r"^MOV\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_ADDR),
parse_mov_reg_mem
),
asm_entry!(
format!(r"^MOV\s{}{}{}$", REGEX_ADDR, REGEX_COMMA, REGEX_REG),
parse_mov_mem_reg
),
asm_entry!(
format!(r"^MOV\s{}{}{}$", REGEX_REG_HALF, REGEX_COMMA, REGEX_NUM),
parse_mov_reg_half_imm
),
@ -276,6 +267,8 @@ impl Assembler {
asm_entry!(r"^PRTCHK$", |_, _| Ok(Instruction::PortReady)),
asm_entry!(r"^PRTRD$", |_, _| Ok(Instruction::ReadPort)),
asm_entry!(r"^PRTWR$", |_, _| Ok(Instruction::WritePort)),
asm_entry!(r"^LOAD$", |_, _| Ok(Instruction::Load)),
asm_entry!(r"^STORE$", |_, _| Ok(Instruction::Store)),
],
}
}
@ -408,20 +401,6 @@ fn parse_byte(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKi
)?))
}
fn parse_mov_reg_mem(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
let captures = regex.captures(line).unwrap();
let reg = parse_reg(captures.get(1).unwrap().as_str())?;
let addr = parse_u4(captures.get(2).unwrap().as_str())?;
Ok(Instruction::Load { reg, addr })
}
fn parse_mov_mem_reg(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
let captures = regex.captures(line).unwrap();
let addr = parse_u4(captures.get(1).unwrap().as_str())?;
let reg = parse_reg(captures.get(2).unwrap().as_str())?;
Ok(Instruction::Store { reg, addr })
}
fn parse_mov_reg_half_imm(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
let captures = regex.captures(line).unwrap();
let (reg, low) = parse_reg_half(captures.get(1).unwrap().as_str())?;
@ -639,6 +618,8 @@ mod tests {
make_test_insn_no_args!(asm_line_prtchk, "PRTCHK", Instruction::PortReady);
make_test_insn_no_args!(asm_line_prtrd, "PRTRD", Instruction::ReadPort);
make_test_insn_no_args!(asm_line_prtwr, "PRTWR", Instruction::WritePort);
make_test_insn_no_args!(asm_line_load, "LOAD", Instruction::Load);
make_test_insn_no_args!(asm_line_store, "STORE", Instruction::Store);
make_test_insn_no_args!(
asm_line_shr,
"SHR",
@ -682,42 +663,6 @@ mod tests {
}
#[test]
fn asm_line_mov_reg_mem() {
for (reg_str, reg) in [("R0", Register::R0)] {
for i in 0..=15u8 {
for j in PrintIntegers::new(i) {
assert_eq!(
ASM.line_to_insn(&format!("MOV {}, [{}]", reg_str, j))
.unwrap(),
Instruction::Load {
reg: reg.clone(),
addr: u4::new(i)
}
);
}
}
}
}
#[test]
fn asm_line_mov_mem_reg() {
for (reg_str, reg) in [("R0", Register::R0)] {
for i in 0..=15u8 {
for j in PrintIntegers::new(i) {
assert_eq!(
ASM.line_to_insn(&format!("MOV [{}], {}", j, reg_str))
.unwrap(),
Instruction::Store {
reg: reg.clone(),
addr: u4::new(i)
}
);
}
}
}
}
#[test]
fn asm_line_mov_reg_half_imm() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
for (low_str, low) in [("l", true), ("h", false)] {
@ -917,7 +862,7 @@ mod tests {
fn asm_assemble() {
let expected = {
let mut code = [0u8; 256];
code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]);
code[0..5].copy_from_slice(&[0x4F, 0x53, 0xE4, 0xF8, 0xF5]);
code
};
assert_eq!(
@ -926,7 +871,6 @@ mod tests {
MOV R0.l, 0xF
MOV R1.l, 0x3
ADD
MOV [0], R0
ZERO R0
HALT
"#
@ -945,7 +889,7 @@ mod tests {
fn asm_assemble_comments() {
let expected = {
let mut code = [0u8; 256];
code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]);
code[0..5].copy_from_slice(&[0x4F, 0x53, 0xE4, 0xF8, 0xF5]);
code
};
assert_eq!(
@ -954,7 +898,6 @@ mod tests {
MOV R0.l, 0xF # R0 = 15
MOV R1.l, 0x3 # R1 = 3
ADD # R0 += R1 (18)
MOV [0], R0 # MEM[0] = R0 (18)
ZERO R0 # R0 = 0
HALT # EXIT
"#

104
toy_cpu_4bit/src/cpu.rs

@ -40,19 +40,13 @@ impl Cpu {
format!("{:?}", decode(self.code[self.IP.0 as usize]))
}
fn load(&mut self, reg: Register, addr: u4) {
match reg {
Register::R0 => self.R0 = Wrapping(self.data[u8::from(addr) as usize]),
Register::R1 => self.R1 = Wrapping(self.data[u8::from(addr) as usize]),
}
fn load(&mut self) {
self.R0 = Wrapping(self.data[(self.R1.0 & 0xF) as usize]);
self.IP += Wrapping(1);
}
fn store(&mut self, reg: Register, addr: u4) {
match reg {
Register::R0 => self.data[u8::from(addr) as usize] = self.R0.0,
Register::R1 => self.data[u8::from(addr) as usize] = self.R1.0,
}
fn store(&mut self) {
self.data[(self.R1.0 & 0xF) as usize] = self.R0.0;
self.IP += Wrapping(1);
}
@ -335,8 +329,8 @@ impl Cpu {
self.num_cycles += 1;
let insn = decode(self.code[self.IP.0 as usize]);
match insn {
Instruction::Load { reg, addr } => self.load(reg, addr),
Instruction::Store { reg, addr } => self.store(reg, addr),
Instruction::Load => self.load(),
Instruction::Store => self.store(),
Instruction::LoadImmediate { low, reg, val } => self.load_imm(low, reg, val),
Instruction::NearJumpBackward { use_carry, offset } => {
self.near_jump(use_carry, false, u8::from(offset))
@ -367,8 +361,6 @@ impl Cpu {
Instruction::BitShiftVar { right, mode } => self.bit_shift_var(right, mode),
Instruction::Copy { reg_from } => self.copy(reg_from),
Instruction::Setc { carry } => self.setc(carry),
Instruction::Nop => self.nop(),
Instruction::Halt => return true,
Instruction::Cload { reg } => self.cload(reg),
Instruction::Zero { reg } => self.zero(reg),
Instruction::Div => self.div(),
@ -377,6 +369,8 @@ impl Cpu {
Instruction::PortReady => self.port_ready(),
Instruction::ReadPort => self.read_port(),
Instruction::WritePort => self.write_port(),
Instruction::Halt => return true,
Instruction::Nop | Instruction::Reserved => self.nop(),
}
false
}
@ -414,106 +408,78 @@ mod tests {
use std::num::Wrapping;
#[test]
fn insn_load_r0() {
let mut cpu = Cpu::new(&[0b00000001]);
cpu.data[0b1] = 0x45;
fn insn_load() {
let mut cpu = Cpu::new(&[0b00000000]);
cpu.R1 = Wrapping(0x2);
cpu.data[0x2] = 0x77;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R0.0, 0x45); // data is loaded
assert_eq!(cpu.R0.0, 0x77); // data is loaded
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_load_r1() {
let mut cpu = Cpu::new(&[0b00010101]);
cpu.data[0b101] = 0x54;
fn insn_load_ignores_high_order_bytes_in_address() {
let mut cpu = Cpu::new(&[0b00000000]);
cpu.R1 = Wrapping(0x72);
cpu.data[0x2] = 0x77;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R1.0, 0x54); // data is loaded
assert_eq!(cpu.R0.0, 0x77); // data is loaded
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_load_r0_r1() {
let mut cpu = Cpu::new(&[0b00001011, 0b00011100]);
cpu.data[0b1011] = 0x12;
cpu.data[0b1100] = 0x78;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R0.0, 0x12); // data is loaded
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R1.0, 0x78); // data is loaded
assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store_r0() {
let mut cpu = Cpu::new(&[0b00100100]);
cpu.R0 = Wrapping(0x42);
fn insn_store() {
let mut cpu = Cpu::new(&[0b00100000]);
cpu.R0 = Wrapping(0x12);
cpu.R1 = Wrapping(0x3);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0b0100], 0x42); // data is stored
assert_eq!(cpu.data[0x3], 0x12); // data is stored
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store_r1() {
let mut cpu = Cpu::new(&[0b00110010]);
cpu.R1 = Wrapping(0x29);
fn insn_store_ignores_high_order_bytes_in_address() {
let mut cpu = Cpu::new(&[0b00100000]);
cpu.R0 = Wrapping(0x12);
cpu.R1 = Wrapping(0xD3);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0b0010], 0x29); // data is stored
assert_eq!(cpu.data[0x3], 0x12); // data is stored
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store_r0_r1() {
let mut cpu = Cpu::new(&[0b00101111, 0b00110010]);
cpu.R0 = Wrapping(0xA4);
cpu.R1 = Wrapping(0xB3);
fn insn_store_load() {
let mut cpu = Cpu::new(&[0b00100000, 0b00000000]);
{
cpu.R0 = Wrapping(0x44);
cpu.R1 = Wrapping(0x1);
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0b1111], 0xA4); // data is stored
assert_eq!(cpu.data[0x1], 0x44); // data is stored
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
{
cpu.R0 = Wrapping(0);
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0b0010], 0xB3); // data is stored
assert_eq!(cpu.R0.0, 0x44); // data is loaded
assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store_and_load() {
let mut cpu = Cpu::new(&[0b00100101, 0b00010101]);
cpu.R0 = Wrapping(0xF3);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0b0101], 0xF3); // data is stored
}
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R1.0, 0xF3); // same data is loaded
}
}
#[test]
fn insn_load_imm_r0() {
let mut cpu = Cpu::new(&[0b01000101, 0b01100101]);
{

44
toy_cpu_4bit/src/instruction.rs

@ -17,14 +17,8 @@ pub enum ShiftMode {
#[derive(Debug, PartialEq)]
pub enum Instruction {
Load {
reg: Register,
addr: u4,
},
Store {
reg: Register,
addr: u4,
},
Load,
Store,
LoadImmediate {
low: bool,
reg: Register,
@ -91,22 +85,13 @@ pub enum Instruction {
PortReady,
ReadPort,
WritePort,
Reserved,
}
pub fn decode(insn: u8) -> Instruction {
match insn {
0b00000000..=0b00111111 => {
let reg = if insn & 0b00010000 == 0 {
Register::R0
} else {
Register::R1
};
let addr = u4::new(insn & 0b00001111);
match insn & 0b00100000 {
0 => Instruction::Load { reg, addr },
_ => Instruction::Store { reg, addr },
}
}
0b00000000 => Instruction::Load,
0b00100000 => Instruction::Store,
0b01000000..=0b01111111 => Instruction::LoadImmediate {
low: insn & 0b00100000 == 0,
reg: if insn & 0b00010000 == 0 {
@ -231,19 +216,14 @@ pub fn decode(insn: u8) -> Instruction {
0b11111101 => Instruction::PortReady,
0b11111110 => Instruction::ReadPort,
0b11111111 => Instruction::WritePort,
_ => Instruction::Reserved,
}
}
pub fn encode(insn: Instruction) -> u8 {
match insn {
Instruction::Load { reg, addr } => match reg {
Register::R0 => u8::from(addr),
Register::R1 => u8::from(addr) | 0b00010000,
},
Instruction::Store { reg, addr } => match reg {
Register::R0 => 0b00100000 | u8::from(addr),
Register::R1 => 0b00110000 | u8::from(addr),
},
Instruction::Load => 0b00000000,
Instruction::Store => 0b00100000,
Instruction::LoadImmediate { low, reg, val } => {
let mut ret = 0b01000000u8;
ret |= match low {
@ -337,6 +317,7 @@ pub fn encode(insn: Instruction) -> u8 {
Instruction::PortReady => 0b11111101,
Instruction::ReadPort => 0b11111110,
Instruction::WritePort => 0b11111111,
Instruction::Reserved => 0b00100001,
}
}
@ -344,11 +325,16 @@ pub fn encode(insn: Instruction) -> u8 {
#[cfg(test)]
mod tests {
use super::{decode, encode};
use crate::instruction::Instruction;
#[test]
fn deterministic_instruction_encoding() {
for i in 0..=255u8 {
assert_eq!(i, encode(decode(i)));
let decoded = decode(i);
match decoded {
Instruction::Reserved => (),
x => assert_eq!(i, encode(x)),
}
}
}
}

9
toy_cpu_4bit/src/lib.rs

@ -16,19 +16,20 @@ mod tests {
MOV R0.l, 0xF # R0 = 15
MOV R1.l, 0x3 # R1 = 3
ADD # R0 += R1 (18)
MOV [0], R0 # MEM[0] = R0 (18)
ZERO R1 # R1 = 0
STORE # MEM[0] = R0 (18)
ZERO R0 # R0 = 0
HALT # EXIT
"#;
let code = Assembler::new().assemble(code).unwrap();
let mut cpu = Cpu::new(&code);
for _ in 0..5 {
for _ in 0..6 {
assert!(!cpu.step()); // CPU does not halt…
}
assert!(cpu.step()); // …until it reaches halt instruction
assert_eq!(cpu.IP, Wrapping(5)); // IP points at halt instruction
assert_eq!(cpu.IP, Wrapping(6)); // IP points at halt instruction
assert_eq!(cpu.R0, Wrapping(0));
assert_eq!(cpu.R1, Wrapping(3));
assert_eq!(cpu.R1, Wrapping(0));
assert!(!cpu.C);
assert_eq!(cpu.data[0], 18);
for i in 1..=15 {

Loading…
Cancel
Save