From d11531b9a23cfeb1dad43d4f21750d3993e9e3df Mon Sep 17 00:00:00 2001 From: Murad Karammaev Date: Sat, 13 Aug 2022 03:22:07 +0300 Subject: [PATCH] implement pointers also drop `MOV reg, mem` and `MOV mem, reg` instructions --- ASM-SYNTAX.md | 7 +-- DESIGN.md | 13 +++-- acceptance-tests/add_four_ints.asm | 3 +- acceptance-tests/add_four_ints.pgm | 5 +- toy_cpu_4bit/src/assembler.rs | 69 +++--------------------- toy_cpu_4bit/src/cpu.rs | 104 +++++++++++++------------------------ toy_cpu_4bit/src/instruction.rs | 44 ++++++---------- toy_cpu_4bit/src/lib.rs | 9 ++-- 8 files changed, 74 insertions(+), 180 deletions(-) diff --git a/ASM-SYNTAX.md b/ASM-SYNTAX.md index 2224f53..0aa07a4 100644 --- a/ASM-SYNTAX.md +++ b/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' diff --git a/DESIGN.md b/DESIGN.md index d2442f9..aa623b6 100644 --- a/DESIGN.md +++ b/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 ``` diff --git a/acceptance-tests/add_four_ints.asm b/acceptance-tests/add_four_ints.asm index 987a3ea..f38967e 100644 --- a/acceptance-tests/add_four_ints.asm +++ b/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 diff --git a/acceptance-tests/add_four_ints.pgm b/acceptance-tests/add_four_ints.pgm index 456bf7e..1ba434f 100644 --- a/acceptance-tests/add_four_ints.pgm +++ b/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 diff --git a/toy_cpu_4bit/src/assembler.rs b/toy_cpu_4bit/src/assembler.rs index 60e8400..c04be55 100644 --- a/toy_cpu_4bit/src/assembler.rs +++ b/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 Result { - 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 { - 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 { 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 "# diff --git a/toy_cpu_4bit/src/cpu.rs b/toy_cpu_4bit/src/cpu.rs index 86bc4b5..6759a28 100644 --- a/toy_cpu_4bit/src/cpu.rs +++ b/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]); { diff --git a/toy_cpu_4bit/src/instruction.rs b/toy_cpu_4bit/src/instruction.rs index 139dc25..bd531e1 100644 --- a/toy_cpu_4bit/src/instruction.rs +++ b/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)), + } } } } diff --git a/toy_cpu_4bit/src/lib.rs b/toy_cpu_4bit/src/lib.rs index 520cd80..1d47ab3 100644 --- a/toy_cpu_4bit/src/lib.rs +++ b/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 {