Browse Source

Merge pull request 'Replace inferior immediate memory offset load/store instructions with actual pointers' (#8) from pointers into master

Reviewed-on: https://git.rfnull.com/ToyPC/ToyCPU-4bit/pulls/8
master
foxpy 2 years ago
parent
commit
a5ece13818
  1. 7
      ASM-SYNTAX.md
  2. 13
      DESIGN.md
  3. 2
      MISTAKES.md
  4. 3
      acceptance-tests/add_four_ints.asm
  5. 5
      acceptance-tests/add_four_ints.pgm
  6. 69
      toy_cpu_4bit/src/assembler.rs
  7. 104
      toy_cpu_4bit/src/cpu.rs
  8. 44
      toy_cpu_4bit/src/instruction.rs
  9. 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
```

2
MISTAKES.md

@ -2,7 +2,7 @@
## Don't design opcodes in your head
You WILL frick up and your binary encodings WILL overlap.
You WILL screw up and your binary encodings WILL overlap.
You definitely need some tool to design instruction opcodes
for anything more complicated than just 1-byte instructions.

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