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
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

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),
`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

2
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

80
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<Instruction, AssemblerErrorKind>;
@ -429,13 +429,15 @@ fn parse_jmp_impl(
use_carry: bool,
) -> Result<Instruction, AssemblerErrorKind> {
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,
}
)
}
}
}
}

161
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);

60
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 },

Loading…
Cancel
Save