From dbcde1e0ccdc90331066eaf84030f5a632f189c6 Mon Sep 17 00:00:00 2001 From: Murad Karammaev Date: Sun, 14 Aug 2022 03:24:43 +0300 Subject: [PATCH] relative jumps can now jump 16 insns in both directions --- ASM-SYNTAX.md | 6 +- DESIGN.md | 40 ++++----- acceptance-tests/add_four_ints.pgm | 2 +- toy_cpu_4bit/src/assembler.rs | 80 +++++++----------- toy_cpu_4bit/src/cpu.rs | 161 ++++++++++++++++++------------------- toy_cpu_4bit/src/instruction.rs | 60 +++++++------- 6 files changed, 157 insertions(+), 192 deletions(-) diff --git a/ASM-SYNTAX.md b/ASM-SYNTAX.md index 0aa07a4..24e1fdd 100644 --- a/ASM-SYNTAX.md +++ b/ASM-SYNTAX.md @@ -65,8 +65,10 @@ mov 'MOV' register ',' register jmp - 'JMP' digit(-3, 7) - 'JMPC' digit(-3, 7) + 'JMP' digit(-16, -1) + 'JMP' digit(1, 16) + 'JMPC' digit(-16, -1) + 'JMPC' digit(1, 16) 'AJMP' register 'AJMPC' register diff --git a/DESIGN.md b/DESIGN.md index aa623b6..d3d0527 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -31,7 +31,7 @@ ALU operations. ``` +—+—+—+—+—+—+—+—+ -|0|0|M|0|0|0|0|0| +|1|0|0|0|1|0|0|M| +—+—+—+—+—+—+—+—+ ``` @@ -41,46 +41,36 @@ 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). -### LOAD IMMEDIATE +### NEAR JUMP ``` +—+—+—+—+———————+ -|0|1|H|R|I I I I| +|0|0|D|C|O O O O| +—+—+—+—+———————+ ``` -`H` — `0` to use 4 least significant bits, `1` to use 4 most -significant bits. - -`R` — `0` to use `R0` register, `1` to use `R1` register. - -`IIII` — 4-bit immediate value. - -### NEAR JUMP BACKWARD - -``` -+—+—+—+—+—+—+———+ -|1|0|0|0|1|C|O O| -+—+—+—+—+—+—+———+ -``` +`D` — jump direction. `0` to jump forward and `1` to jump backward. `C` — `0` to ignore carry flag and jump unconditionally, `1` to only jump if carry flag is set. -`OO` — jump offset. +`OOOO` — jump offset. Actual jump offset is higher than encoded +offset by 1, since offset of 0 doesn't make any sense. -### NEAR JUMP FORWARD +### LOAD IMMEDIATE ``` -+—+—+—+—+—+—————+ -|1|0|0|1|C|O O O| -+—+—+—+—+—+—————+ ++—+—+—+—+———————+ +|0|1|H|R|I I I I| ++—+—+—+—+———————+ ``` -`C` — `0` to ignore carry flag and jump unconditionally, -`1` to only jump if carry flag is set. +`H` — `0` to use 4 least significant bits, `1` to use 4 most +significant bits. -`OOO` — jump offset. +`R` — `0` to use `R0` register, `1` to use `R1` register. + +`IIII` — 4-bit immediate value. ### FAR JUMP diff --git a/acceptance-tests/add_four_ints.pgm b/acceptance-tests/add_four_ints.pgm index 1ba434f..59927ea 100644 --- a/acceptance-tests/add_four_ints.pgm +++ b/acceptance-tests/add_four_ints.pgm @@ -11,5 +11,5 @@ P2 117 # MOV R1.h, 5 228 # ADD 95 # MOV R1.l, 0xf -32 # STORE +137 # STORE 245 # HALT diff --git a/toy_cpu_4bit/src/assembler.rs b/toy_cpu_4bit/src/assembler.rs index c04be55..04cb1d8 100644 --- a/toy_cpu_4bit/src/assembler.rs +++ b/toy_cpu_4bit/src/assembler.rs @@ -9,7 +9,7 @@ use std::{ num, ops::RangeInclusive, }; -use ux::{u2, u3, u4}; +use ux::{u3, u4}; type InstructionDecoder = fn(&str, &Regex) -> Result; @@ -429,13 +429,15 @@ fn parse_jmp_impl( use_carry: bool, ) -> Result { match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? { - x @ -3..=-1 => Ok(Instruction::NearJumpBackward { + x @ -16..=-1 => Ok(Instruction::NearJump { use_carry, - offset: u2::new(-x as u8), + offset: u4::new(-(x + 1) as u8), + forward: false, }), - x @ 0..=7 => Ok(Instruction::NearJumpForward { + x @ 1..=16 => Ok(Instruction::NearJump { use_carry, - offset: u3::new(x as u8), + offset: u4::new((x - 1) as u8), + forward: true, }), x => Err(AssemblerErrorKind::invalid_relative_jump_offset(x)), } @@ -537,7 +539,7 @@ mod tests { }; use lazy_static::lazy_static; use std::fmt::{Binary, Display, LowerHex, Octal}; - use ux::{u2, u3, u4}; + use ux::{u3, u4}; lazy_static! { static ref ASM: Assembler = Assembler::new(); @@ -684,53 +686,25 @@ mod tests { } #[test] - fn asm_line_jmp() { - for i in 0..=7u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMP {}", j)).unwrap(), - Instruction::NearJumpForward { - use_carry: false, - offset: u3::new(i) - } - ) - } - } - for i in 1..=3u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMP -{}", j)).unwrap(), - Instruction::NearJumpBackward { - use_carry: false, - offset: u2::new(i) - } - ) - } - } - } - - #[test] - fn asm_line_jmpc() { - for i in 0..=7u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMPC {}", j)).unwrap(), - Instruction::NearJumpForward { - use_carry: true, - offset: u3::new(i) - } - ) - } - } - for i in 1..=3u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMPC -{}", j)).unwrap(), - Instruction::NearJumpBackward { - use_carry: true, - offset: u2::new(i) - } - ) + fn asm_line_jmp_jmpc() { + for (insn, use_carry, sign, forward) in [ + ("JMP", false, "", true), + ("JMP", false, "-", false), + ("JMPC", true, "", true), + ("JMPC", true, "-", false), + ] { + for i in 0..=15u8 { + for j in PrintIntegers::new(i + 1) { + assert_eq!( + ASM.line_to_insn(&format!("{} {}{}", insn, sign, j)) + .unwrap(), + Instruction::NearJump { + use_carry, + offset: u4::new(i), + forward, + } + ) + } } } } diff --git a/toy_cpu_4bit/src/cpu.rs b/toy_cpu_4bit/src/cpu.rs index 6759a28..fd281af 100644 --- a/toy_cpu_4bit/src/cpu.rs +++ b/toy_cpu_4bit/src/cpu.rs @@ -69,15 +69,15 @@ impl Cpu { self.IP += Wrapping(1); } - fn near_jump(&mut self, use_carry: bool, forward: bool, offset: u8) { + fn near_jump(&mut self, forward: bool, use_carry: bool, offset: u4) { if use_carry && !self.C { self.IP += Wrapping(1); return; } if forward { - self.IP += Wrapping(offset); + self.IP += Wrapping(u8::from(offset) + 1); } else { - self.IP -= Wrapping(offset); + self.IP -= Wrapping(u8::from(offset) + 1); } } @@ -331,13 +331,12 @@ impl Cpu { match insn { Instruction::Load => self.load(), Instruction::Store => self.store(), + Instruction::NearJump { + forward, + use_carry, + offset, + } => self.near_jump(forward, use_carry, offset), 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)) - } - Instruction::NearJumpForward { use_carry, offset } => { - self.near_jump(use_carry, true, u8::from(offset)) - } Instruction::FarJump { reg, use_carry } => self.far_jump(use_carry, reg), Instruction::CmpEqual => self.cmp_equal(), Instruction::CmpGreater => self.cmp_gt(), @@ -408,8 +407,74 @@ mod tests { use std::num::Wrapping; #[test] + fn insn_near_jump_backward_unconditional() { + let mut cpu = Cpu::new(&[]); + cpu.code[20] = 0b00100010; + cpu.IP = Wrapping(20); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 17); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_backward_conditional_must() { + let mut cpu = Cpu::new(&[]); + cpu.code[40] = 0b00110010; + cpu.IP = Wrapping(40); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 37); // instruction pointer had advanced + assert!(cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_backward_conditional_must_not() { + let mut cpu = Cpu::new(&[]); + cpu.code[40] = 0b00110010; + cpu.IP = Wrapping(40); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 41); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_unconditional() { + let mut cpu = Cpu::new(&[0b00000010]); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0b11); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + #[test] + fn insn_near_jump_forward_conditional_must() { + let mut cpu = Cpu::new(&[0b00010010]); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0b11); // instruction pointer had advanced + assert!(cpu.C); // carry is not affected + } + } + #[test] + fn insn_near_jump_forward_conditional_must_not() { + let mut cpu = Cpu::new(&[0b00010010]); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 1); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] fn insn_load() { - let mut cpu = Cpu::new(&[0b00000000]); + let mut cpu = Cpu::new(&[0b10001000]); cpu.R1 = Wrapping(0x2); cpu.data[0x2] = 0x77; { @@ -422,7 +487,7 @@ mod tests { #[test] fn insn_load_ignores_high_order_bytes_in_address() { - let mut cpu = Cpu::new(&[0b00000000]); + let mut cpu = Cpu::new(&[0b10001000]); cpu.R1 = Wrapping(0x72); cpu.data[0x2] = 0x77; { @@ -435,7 +500,7 @@ mod tests { #[test] fn insn_store() { - let mut cpu = Cpu::new(&[0b00100000]); + let mut cpu = Cpu::new(&[0b10001001]); cpu.R0 = Wrapping(0x12); cpu.R1 = Wrapping(0x3); { @@ -448,7 +513,7 @@ mod tests { #[test] fn insn_store_ignores_high_order_bytes_in_address() { - let mut cpu = Cpu::new(&[0b00100000]); + let mut cpu = Cpu::new(&[0b10001001]); cpu.R0 = Wrapping(0x12); cpu.R1 = Wrapping(0xD3); { @@ -461,7 +526,7 @@ mod tests { #[test] fn insn_store_load() { - let mut cpu = Cpu::new(&[0b00100000, 0b00000000]); + let mut cpu = Cpu::new(&[0b10001001, 0b10001000]); { cpu.R0 = Wrapping(0x44); cpu.R1 = Wrapping(0x1); @@ -514,74 +579,6 @@ mod tests { } #[test] - fn insn_near_jump_backward_unconditional() { - let mut cpu = Cpu::new(&[]); - cpu.code[20] = 0b10001010; - cpu.IP = Wrapping(20); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 18); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_backward_conditional_must() { - let mut cpu = Cpu::new(&[]); - cpu.code[40] = 0b10001110; - cpu.IP = Wrapping(40); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 38); // instruction pointer had advanced - assert!(cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_backward_conditional_must_not() { - let mut cpu = Cpu::new(&[]); - cpu.code[40] = 0b10001110; - cpu.IP = Wrapping(40); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 41); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_forward_unconditional() { - let mut cpu = Cpu::new(&[0b10010101]); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0b101); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_forward_conditional_must() { - let mut cpu = Cpu::new(&[0b10011011]); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0b011); // instruction pointer had advanced - assert!(cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_forward_conditional_must_not() { - let mut cpu = Cpu::new(&[0b10011011]); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 1); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] fn insn_far_jump_unconditional_r0() { let mut cpu = Cpu::new(&[0b10000000]); cpu.R0 = Wrapping(0xA0); diff --git a/toy_cpu_4bit/src/instruction.rs b/toy_cpu_4bit/src/instruction.rs index bd531e1..f613e21 100644 --- a/toy_cpu_4bit/src/instruction.rs +++ b/toy_cpu_4bit/src/instruction.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright Murad Karammaev, Nikita Kuzmin -use ux::{u2, u3, u4}; +use ux::{u3, u4}; #[derive(Debug, PartialEq, Clone)] pub enum Register { @@ -19,19 +19,16 @@ pub enum ShiftMode { pub enum Instruction { Load, Store, + NearJump { + forward: bool, + use_carry: bool, + offset: u4, + }, LoadImmediate { low: bool, reg: Register, val: u4, }, - NearJumpBackward { - use_carry: bool, - offset: u2, - }, - NearJumpForward { - use_carry: bool, - offset: u3, - }, FarJump { reg: Register, use_carry: bool, @@ -90,8 +87,13 @@ pub enum Instruction { pub fn decode(insn: u8) -> Instruction { match insn { - 0b00000000 => Instruction::Load, - 0b00100000 => Instruction::Store, + 0b00000000..=0b00111111 => Instruction::NearJump { + forward: insn & 0b00100000 == 0, + use_carry: insn & 0b00010000 != 0, + offset: u4::new(insn & 0b00001111), + }, + 0b10001000 => Instruction::Load, + 0b10001001 => Instruction::Store, 0b01000000..=0b01111111 => Instruction::LoadImmediate { low: insn & 0b00100000 == 0, reg: if insn & 0b00010000 == 0 { @@ -101,14 +103,6 @@ pub fn decode(insn: u8) -> Instruction { }, val: u4::new(insn & 0b00001111), }, - 0b10001000..=0b10001111 => Instruction::NearJumpBackward { - use_carry: insn & 0b00000100 != 0, - offset: u2::new(insn & 0b00000011), - }, - 0b10010000..=0b10011111 => Instruction::NearJumpForward { - use_carry: insn & 0b00001000 != 0, - offset: u3::new(insn & 0b00000111), - }, 0b10000000..=0b10000011 => Instruction::FarJump { reg: if insn & 0b00000010 == 0 { Register::R0 @@ -222,8 +216,24 @@ pub fn decode(insn: u8) -> Instruction { pub fn encode(insn: Instruction) -> u8 { match insn { - Instruction::Load => 0b00000000, - Instruction::Store => 0b00100000, + Instruction::Load => 0b10001000, + Instruction::Store => 0b10001001, + Instruction::NearJump { + forward, + use_carry, + offset, + } => { + let mut ret = 0u8; + ret |= match forward { + true => 0, + false => 0b00100000, + }; + ret |= match use_carry { + true => 0b00010000, + false => 0, + }; + ret | u8::from(offset) + } Instruction::LoadImmediate { low, reg, val } => { let mut ret = 0b01000000u8; ret |= match low { @@ -236,14 +246,6 @@ pub fn encode(insn: Instruction) -> u8 { }; ret | u8::from(val) } - Instruction::NearJumpBackward { use_carry, offset } => match use_carry { - false => 0b10001000 | u8::from(offset), - true => 0b10001100 | u8::from(offset), - }, - Instruction::NearJumpForward { use_carry, offset } => match use_carry { - false => 0b10010000 | u8::from(offset), - true => 0b10011000 | u8::from(offset), - }, Instruction::FarJump { reg, use_carry } => match reg { Register::R0 => 0b10000000 | if use_carry { 1 } else { 0 }, Register::R1 => 0b10000010 | if use_carry { 1 } else { 0 },