Browse Source

relative jumps can now jump 16 insns in both directions

pull/9/head
Murad Karammaev 2 years ago
parent
commit
dbcde1e0cc
Signed by: foxpy GPG Key ID: 78BE32418B0C8450
  1. 6
      ASM-SYNTAX.md
  2. 40
      DESIGN.md
  3. 2
      acceptance-tests/add_four_ints.pgm
  4. 80
      toy_cpu_4bit/src/assembler.rs
  5. 161
      toy_cpu_4bit/src/cpu.rs
  6. 60
      toy_cpu_4bit/src/instruction.rs

6
ASM-SYNTAX.md

@ -65,8 +65,10 @@ mov
'MOV' register ',' register 'MOV' register ',' register
jmp jmp
'JMP' digit(-3, 7) 'JMP' digit(-16, -1)
'JMPC' digit(-3, 7) 'JMP' digit(1, 16)
'JMPC' digit(-16, -1)
'JMPC' digit(1, 16)
'AJMP' register 'AJMP' register
'AJMPC' register 'AJMPC' register

40
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), `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). `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 `D` — jump direction. `0` to jump forward and `1` to jump backward.
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|
+—+—+—+—+—+—+———+
```
`C``0` to ignore carry flag and jump unconditionally, `C``0` to ignore carry flag and jump unconditionally,
`1` to only jump if carry flag is set. `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, `H``0` to use 4 least significant bits, `1` to use 4 most
`1` to only jump if carry flag is set. significant bits.
`OOO` — jump offset. `R``0` to use `R0` register, `1` to use `R1` register.
`IIII` — 4-bit immediate value.
### FAR JUMP ### FAR JUMP

2
acceptance-tests/add_four_ints.pgm

@ -11,5 +11,5 @@ P2
117 # MOV R1.h, 5 117 # MOV R1.h, 5
228 # ADD 228 # ADD
95 # MOV R1.l, 0xf 95 # MOV R1.l, 0xf
32 # STORE 137 # STORE
245 # HALT 245 # HALT

80
toy_cpu_4bit/src/assembler.rs

@ -9,7 +9,7 @@ use std::{
num, num,
ops::RangeInclusive, ops::RangeInclusive,
}; };
use ux::{u2, u3, u4}; use ux::{u3, u4};
type InstructionDecoder = fn(&str, &Regex) -> Result<Instruction, AssemblerErrorKind>; type InstructionDecoder = fn(&str, &Regex) -> Result<Instruction, AssemblerErrorKind>;
@ -429,13 +429,15 @@ fn parse_jmp_impl(
use_carry: bool, use_carry: bool,
) -> Result<Instruction, AssemblerErrorKind> { ) -> Result<Instruction, AssemblerErrorKind> {
match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? { 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, 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, 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)), x => Err(AssemblerErrorKind::invalid_relative_jump_offset(x)),
} }
@ -537,7 +539,7 @@ mod tests {
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::fmt::{Binary, Display, LowerHex, Octal}; use std::fmt::{Binary, Display, LowerHex, Octal};
use ux::{u2, u3, u4}; use ux::{u3, u4};
lazy_static! { lazy_static! {
static ref ASM: Assembler = Assembler::new(); static ref ASM: Assembler = Assembler::new();
@ -684,53 +686,25 @@ mod tests {
} }
#[test] #[test]
fn asm_line_jmp() { fn asm_line_jmp_jmpc() {
for i in 0..=7u8 { for (insn, use_carry, sign, forward) in [
for j in PrintIntegers::new(i) { ("JMP", false, "", true),
assert_eq!( ("JMP", false, "-", false),
ASM.line_to_insn(&format!("JMP {}", j)).unwrap(), ("JMPC", true, "", true),
Instruction::NearJumpForward { ("JMPC", true, "-", false),
use_carry: false, ] {
offset: u3::new(i) for i in 0..=15u8 {
} for j in PrintIntegers::new(i + 1) {
) assert_eq!(
} ASM.line_to_insn(&format!("{} {}{}", insn, sign, j))
} .unwrap(),
for i in 1..=3u8 { Instruction::NearJump {
for j in PrintIntegers::new(i) { use_carry,
assert_eq!( offset: u4::new(i),
ASM.line_to_insn(&format!("JMP -{}", j)).unwrap(), forward,
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)
}
)
} }
} }
} }

161
toy_cpu_4bit/src/cpu.rs

@ -69,15 +69,15 @@ impl Cpu {
self.IP += Wrapping(1); 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 { if use_carry && !self.C {
self.IP += Wrapping(1); self.IP += Wrapping(1);
return; return;
} }
if forward { if forward {
self.IP += Wrapping(offset); self.IP += Wrapping(u8::from(offset) + 1);
} else { } else {
self.IP -= Wrapping(offset); self.IP -= Wrapping(u8::from(offset) + 1);
} }
} }
@ -331,13 +331,12 @@ impl Cpu {
match insn { match insn {
Instruction::Load => self.load(), Instruction::Load => self.load(),
Instruction::Store => self.store(), 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::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::FarJump { reg, use_carry } => self.far_jump(use_carry, reg),
Instruction::CmpEqual => self.cmp_equal(), Instruction::CmpEqual => self.cmp_equal(),
Instruction::CmpGreater => self.cmp_gt(), Instruction::CmpGreater => self.cmp_gt(),
@ -408,8 +407,74 @@ mod tests {
use std::num::Wrapping; use std::num::Wrapping;
#[test] #[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() { fn insn_load() {
let mut cpu = Cpu::new(&[0b00000000]); let mut cpu = Cpu::new(&[0b10001000]);
cpu.R1 = Wrapping(0x2); cpu.R1 = Wrapping(0x2);
cpu.data[0x2] = 0x77; cpu.data[0x2] = 0x77;
{ {
@ -422,7 +487,7 @@ mod tests {
#[test] #[test]
fn insn_load_ignores_high_order_bytes_in_address() { 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.R1 = Wrapping(0x72);
cpu.data[0x2] = 0x77; cpu.data[0x2] = 0x77;
{ {
@ -435,7 +500,7 @@ mod tests {
#[test] #[test]
fn insn_store() { fn insn_store() {
let mut cpu = Cpu::new(&[0b00100000]); let mut cpu = Cpu::new(&[0b10001001]);
cpu.R0 = Wrapping(0x12); cpu.R0 = Wrapping(0x12);
cpu.R1 = Wrapping(0x3); cpu.R1 = Wrapping(0x3);
{ {
@ -448,7 +513,7 @@ mod tests {
#[test] #[test]
fn insn_store_ignores_high_order_bytes_in_address() { 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.R0 = Wrapping(0x12);
cpu.R1 = Wrapping(0xD3); cpu.R1 = Wrapping(0xD3);
{ {
@ -461,7 +526,7 @@ mod tests {
#[test] #[test]
fn insn_store_load() { fn insn_store_load() {
let mut cpu = Cpu::new(&[0b00100000, 0b00000000]); let mut cpu = Cpu::new(&[0b10001001, 0b10001000]);
{ {
cpu.R0 = Wrapping(0x44); cpu.R0 = Wrapping(0x44);
cpu.R1 = Wrapping(0x1); cpu.R1 = Wrapping(0x1);
@ -514,74 +579,6 @@ mod tests {
} }
#[test] #[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() { fn insn_far_jump_unconditional_r0() {
let mut cpu = Cpu::new(&[0b10000000]); let mut cpu = Cpu::new(&[0b10000000]);
cpu.R0 = Wrapping(0xA0); cpu.R0 = Wrapping(0xA0);

60
toy_cpu_4bit/src/instruction.rs

@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright Murad Karammaev, Nikita Kuzmin // Copyright Murad Karammaev, Nikita Kuzmin
use ux::{u2, u3, u4}; use ux::{u3, u4};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Register { pub enum Register {
@ -19,19 +19,16 @@ pub enum ShiftMode {
pub enum Instruction { pub enum Instruction {
Load, Load,
Store, Store,
NearJump {
forward: bool,
use_carry: bool,
offset: u4,
},
LoadImmediate { LoadImmediate {
low: bool, low: bool,
reg: Register, reg: Register,
val: u4, val: u4,
}, },
NearJumpBackward {
use_carry: bool,
offset: u2,
},
NearJumpForward {
use_carry: bool,
offset: u3,
},
FarJump { FarJump {
reg: Register, reg: Register,
use_carry: bool, use_carry: bool,
@ -90,8 +87,13 @@ pub enum Instruction {
pub fn decode(insn: u8) -> Instruction { pub fn decode(insn: u8) -> Instruction {
match insn { match insn {
0b00000000 => Instruction::Load, 0b00000000..=0b00111111 => Instruction::NearJump {
0b00100000 => Instruction::Store, forward: insn & 0b00100000 == 0,
use_carry: insn & 0b00010000 != 0,
offset: u4::new(insn & 0b00001111),
},
0b10001000 => Instruction::Load,
0b10001001 => Instruction::Store,
0b01000000..=0b01111111 => Instruction::LoadImmediate { 0b01000000..=0b01111111 => Instruction::LoadImmediate {
low: insn & 0b00100000 == 0, low: insn & 0b00100000 == 0,
reg: if insn & 0b00010000 == 0 { reg: if insn & 0b00010000 == 0 {
@ -101,14 +103,6 @@ pub fn decode(insn: u8) -> Instruction {
}, },
val: u4::new(insn & 0b00001111), 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 { 0b10000000..=0b10000011 => Instruction::FarJump {
reg: if insn & 0b00000010 == 0 { reg: if insn & 0b00000010 == 0 {
Register::R0 Register::R0
@ -222,8 +216,24 @@ pub fn decode(insn: u8) -> Instruction {
pub fn encode(insn: Instruction) -> u8 { pub fn encode(insn: Instruction) -> u8 {
match insn { match insn {
Instruction::Load => 0b00000000, Instruction::Load => 0b10001000,
Instruction::Store => 0b00100000, 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 } => { Instruction::LoadImmediate { low, reg, val } => {
let mut ret = 0b01000000u8; let mut ret = 0b01000000u8;
ret |= match low { ret |= match low {
@ -236,14 +246,6 @@ pub fn encode(insn: Instruction) -> u8 {
}; };
ret | u8::from(val) 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 { Instruction::FarJump { reg, use_carry } => match reg {
Register::R0 => 0b10000000 | if use_carry { 1 } else { 0 }, Register::R0 => 0b10000000 | if use_carry { 1 } else { 0 },
Register::R1 => 0b10000010 | if use_carry { 1 } else { 0 }, Register::R1 => 0b10000010 | if use_carry { 1 } else { 0 },

Loading…
Cancel
Save