Browse Source

Merge pull request 'Add assembler library' (#6) from standalone-assembler into master

Reviewed-on: https://git.rfnull.com/ToyPC/ToyCPU-4bit/pulls/6
master
foxpy 2 years ago
parent
commit
b1bff1b3bf
  1. 1
      .gitignore
  2. 18
      ASM-SYNTAX.md
  3. 40
      Cargo.lock
  4. 2
      Cargo.toml
  5. 16
      MISTAKES.md
  6. 717
      src/assembler.rs
  7. 125
      src/instruction.rs
  8. 25
      src/main.rs

1
.gitignore

@ -1 +1,2 @@
target/
.idea/

18
ASM-SYNTAX.md

@ -33,8 +33,8 @@ instruction
'LE'
'NOT'
shift
'INC'
'DEC'
'INC' register
'DEC' register
'ADD'
'SUB'
'AND'
@ -42,7 +42,7 @@ instruction
'XOR'
'XNOR'
'COMPL' register
'SETC' digit(0, 1)
'SETC' carry
'NOP'
'HALT'
'CLOAD' register
@ -54,6 +54,10 @@ instruction
'PRTRD'
'PRTWR'
carry
'0'
'1'
mov
'MOV' register ',' memory
'MOV' memory ',' register
@ -61,12 +65,10 @@ mov
'MOV' register ',' register
jmp
'JMP' digit(-4, 8)
'JE' digit(-4, 8)
'JNE' digit(-4, 8)
'JMP' digit(-3, 7)
'JMPC' digit(-3, 7)
'AJMP' register
'AJE' register
'AJNE' register
'AJMPC' register
shift
'SHR'

40
Cargo.lock

@ -3,9 +3,49 @@
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "toy_cpu_4bit"
version = "0.1.0"
dependencies = [
"lazy_static",
"regex",
"ux",
]

2
Cargo.toml

@ -5,3 +5,5 @@ edition = "2021"
[dependencies]
ux = "0.1.3"
regex = "1.5.4"
lazy_static = "1.4.0"

16
MISTAKES.md

@ -6,8 +6,24 @@ You WILL frick 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.
Turned out this mistake propogates further when implementing
assembler.
## Load/store instructions MUST be able to use register as address
I have no idea how we didn't notice this mistake.
I guess it is not too bad for a CPU with 16 bytes of memory,
but for something bigger it is unacceptable.
## Jump instructions need more love
Jumping 3 instructions back and 7 forward is too limiting.
Seriously, that's stupid.
Also, in my current design, jumping offset of 0 will
move CPU to an infinite loop.
## Implementing assembler with regex is not fancy
It is probably easier to implement than writing proper
lexer/parser, but error reporting can be weird.

717
src/assembler.rs

@ -0,0 +1,717 @@
// SPDX-License-Identifier: MIT
// Copyright Murad Karammaev, Nikita Kuzmin
use crate::instruction::{decode, encode, Instruction, Register, ShiftMode};
use regex::Regex;
use std::error::Error;
use ux::{u2, u3, u4};
type InstructionDecoder = fn(&str, &Regex) -> Result<Instruction, Box<dyn Error>>;
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])";
macro_rules! asm_entry {
($regex:expr, $handler:expr) => {
(Regex::new(&$regex).unwrap(), $handler)
};
}
pub struct Assembler {
table: Vec<(Regex, InstructionDecoder)>,
}
impl Assembler {
pub fn new() -> Self {
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
),
asm_entry!(
format!(r"^MOV\s{}{}{}", REGEX_REG, REGEX_COMMA, REGEX_REG),
parse_mov_reg_reg
),
asm_entry!(format!(r"^JMP\s{}$", REGEX_INUM), parse_jmp),
asm_entry!(format!(r"^JMPC\s{}$", REGEX_INUM), parse_jmpc),
asm_entry!(format!(r"^AJMP\s{}$", REGEX_REG), parse_ajmp),
asm_entry!(format!(r"^AJMPC\s{}$", REGEX_REG), parse_ajmpc),
asm_entry!(r"^EQ$", |_, _| Ok(Instruction::CmpEqual)),
asm_entry!(r"^GT$", |_, _| Ok(Instruction::CmpGreater)),
asm_entry!(r"^LE$", |_, _| Ok(Instruction::CmpLess)),
asm_entry!(r"^NOT$", |_, _| Ok(Instruction::Not)),
asm_entry!(r"^SHR$", |_, _| Ok(Instruction::BitShiftVar {
mode: ShiftMode::Logical,
right: true
})),
asm_entry!(r"^SHL$", |_, _| Ok(Instruction::BitShiftVar {
mode: ShiftMode::Logical,
right: false
})),
asm_entry!(r"^ROTR$", |_, _| Ok(Instruction::BitShiftVar {
mode: ShiftMode::Circular,
right: true
})),
asm_entry!(r"^ROTL$", |_, _| Ok(Instruction::BitShiftVar {
mode: ShiftMode::Circular,
right: false
})),
asm_entry!(
format!(r"^SHR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM),
parse_shr
),
asm_entry!(
format!(r"^SHL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM),
parse_shl
),
asm_entry!(
format!(r"^ROTR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM),
parse_rotr
),
asm_entry!(
format!(r"^ROTL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM),
parse_rotl
),
asm_entry!(format!(r"^INC\s{}$", REGEX_REG), parse_inc),
asm_entry!(format!(r"^DEC\s{}$", REGEX_REG), parse_dec),
asm_entry!(r"^ADD$", |_, _| Ok(Instruction::Add)),
asm_entry!(r"^SUB$", |_, _| Ok(Instruction::Sub)),
asm_entry!(r"^AND$", |_, _| Ok(Instruction::And)),
asm_entry!(r"^OR$", |_, _| Ok(Instruction::Or)),
asm_entry!(r"^XOR$", |_, _| Ok(Instruction::Xor)),
asm_entry!(r"^XNOR$", |_, _| Ok(Instruction::Xnor)),
asm_entry!(format!(r"^COMPL\s{}$", REGEX_REG), parse_compl),
asm_entry!(format!(r"^SETC\s{}$", REGEX_CARRY), parse_setc),
asm_entry!(r"^NOP$", |_, _| Ok(Instruction::Nop)),
asm_entry!(r"^HALT$", |_, _| Ok(Instruction::Halt)),
asm_entry!(format!(r"^CLOAD\s{}$", REGEX_REG), parse_cload),
asm_entry!(format!(r"^ZERO\s{}$", REGEX_REG), parse_zero),
asm_entry!(r"^DIV$", |_, _| Ok(Instruction::Div)),
asm_entry!(r"^MUL$", |_, _| Ok(Instruction::Mul)),
asm_entry!(r"^SWAP$", |_, _| Ok(Instruction::Swap)),
asm_entry!(r"^PRTCHK$", |_, _| Ok(Instruction::PortReady)),
asm_entry!(r"^PRTRD$", |_, _| Ok(Instruction::ReadPort)),
asm_entry!(r"^PRTWR$", |_, _| Ok(Instruction::WritePort)),
],
}
}
pub fn assemble(&self, input: &str) -> Result<[u8; 0x100], Box<dyn Error>> {
let mut ret = [0u8; 0x100];
let mut i: usize = 0;
for line in input.lines().map(|line| line.trim()) {
match line {
"" => (),
line => {
if i > 0xFF {
return Err("Too much instructions".into());
}
let insn = self.line_to_insn(line)?;
ret[i] = encode(insn);
i += 1;
}
}
}
Ok(ret)
}
fn line_to_insn(&self, line: &str) -> Result<Instruction, Box<dyn Error>> {
for (regex, handler) in &self.table {
if regex.is_match(line) {
return handler(line, regex);
}
}
Err("Unknown instruction".into())
}
}
fn parse_u128(src: &str) -> Result<u128, Box<dyn Error>> {
if let Some(s) = src.strip_prefix("0b") {
Ok(u128::from_str_radix(s, 2)?)
} else if let Some(s) = src.strip_prefix("0o") {
Ok(u128::from_str_radix(s, 8)?)
} else if let Some(s) = src.strip_prefix("0x") {
Ok(u128::from_str_radix(s, 16)?)
} else {
Ok(src.parse()?)
}
}
fn parse_u8(src: &str) -> Result<u8, Box<dyn Error>> {
match parse_u128(src)? {
x @ 0..=255 => Ok(x as u8),
x => Err(format!("{} is not in range [0, 255]", x).into()),
}
}
fn parse_i8(src: &str) -> Result<i8, Box<dyn Error>> {
let (sign, num): (i128, u128) = match src.strip_prefix('-') {
Some(s) => (-1, parse_u128(s)?),
None => (1, parse_u128(src)?),
};
match (sign, num) {
(1, x) if x <= 127 => Ok((x as i128 * sign) as i8),
(-1, x) if x <= 128 => Ok((x as i128 * sign) as i8),
(_, x) => Err(format!("{} is not in range [-128, 127]", x).into()),
}
}
fn parse_u4(src: &str) -> Result<u4, Box<dyn Error>> {
match parse_u8(src)? {
x @ 0..=15 => Ok(u4::new(x)),
x => Err(format!("{} is not in range [0, 15]", x).into()),
}
}
fn parse_u3(src: &str) -> Result<u3, Box<dyn Error>> {
match parse_u8(src)? {
x @ 0..=7 => Ok(u3::new(x)),
x => Err(format!("{} is not in range [0, 7]", x).into()),
}
}
fn parse_reg(src: &str) -> Result<Register, Box<dyn Error>> {
match src {
"R0" => Ok(Register::R0),
"R1" => Ok(Register::R1),
x => Err(format!("'{}' is not a valid register", x).into()),
}
}
fn parse_reg_half(src: &str) -> Result<(Register, bool), Box<dyn Error>> {
match src {
"R0.l" => Ok((Register::R0, true)),
"R0.h" => Ok((Register::R0, false)),
"R1.l" => Ok((Register::R1, true)),
"R1.h" => Ok((Register::R1, false)),
x => Err(format!("'{}' is not a valid register half", x).into()),
}
}
fn parse_carry(src: &str) -> Result<bool, Box<dyn Error>> {
match src {
"0" => Ok(false),
"1" => Ok(true),
x => Err(format!("'{}' is an invalid carry value", x).into()),
}
}
fn parse_byte(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
Ok(decode(parse_u8(
regex.captures(line).unwrap().get(1).unwrap().as_str(),
)?))
}
fn parse_mov_reg_mem(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
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, Box<dyn Error>> {
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, Box<dyn Error>> {
let captures = regex.captures(line).unwrap();
let (reg, low) = parse_reg_half(captures.get(1).unwrap().as_str())?;
let val = parse_u4(captures.get(2).unwrap().as_str())?;
Ok(Instruction::LoadImmediate { low, reg, val })
}
fn parse_mov_reg_reg(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let captures = regex.captures(line).unwrap();
let reg_to = parse_reg(captures.get(1).unwrap().as_str())?;
let reg_from = parse_reg(captures.get(2).unwrap().as_str())?;
match (reg_to, reg_from) {
(Register::R0, Register::R1) => Ok(Instruction::Copy {
reg_from: Register::R1,
}),
(Register::R1, Register::R0) => Ok(Instruction::Copy {
reg_from: Register::R0,
}),
_ => Err("Malformed register to register copy".into()),
}
}
fn parse_jmp_impl(
line: &str,
regex: &Regex,
use_carry: bool,
) -> Result<Instruction, Box<dyn Error>> {
match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? {
x @ -3..=-1 => Ok(Instruction::NearJumpBackward {
use_carry,
offset: u2::new(-x as u8),
}),
x @ 0..=7 => Ok(Instruction::NearJumpForward {
use_carry,
offset: u3::new(x as u8),
}),
x => Err(format!("{} is not a valid relative jump offset", x).into()),
}
}
fn parse_jmp(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_jmp_impl(line, regex, false)
}
fn parse_jmpc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_jmp_impl(line, regex, true)
}
fn parse_ajmp_impl(
line: &str,
regex: &Regex,
use_carry: bool,
) -> Result<Instruction, Box<dyn Error>> {
let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::FarJump { reg, use_carry })
}
fn parse_ajmp(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_ajmp_impl(line, regex, false)
}
fn parse_ajmpc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_ajmp_impl(line, regex, true)
}
fn parse_shift(
line: &str,
regex: &Regex,
right: bool,
mode: ShiftMode,
) -> Result<Instruction, Box<dyn Error>> {
let captures = regex.captures(line).unwrap();
let reg = parse_reg(captures.get(1).unwrap().as_str())?;
let len = parse_u3(captures.get(2).unwrap().as_str())?;
Ok(Instruction::BitShift {
right,
reg,
mode,
len,
})
}
fn parse_shr(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_shift(line, regex, true, ShiftMode::Logical)
}
fn parse_shl(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_shift(line, regex, false, ShiftMode::Logical)
}
fn parse_rotr(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_shift(line, regex, true, ShiftMode::Circular)
}
fn parse_rotl(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
parse_shift(line, regex, false, ShiftMode::Circular)
}
fn parse_inc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::Inc { reg })
}
fn parse_dec(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::Dec { reg })
}
fn parse_compl(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::Complement { reg })
}
fn parse_setc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let carry = parse_carry(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::Setc { carry })
}
fn parse_cload(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::Cload { reg })
}
fn parse_zero(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?;
Ok(Instruction::Zero { reg })
}
#[cfg(test)]
mod tests {
use crate::assembler::Assembler;
use crate::instruction::{decode, Instruction, Register, ShiftMode};
use lazy_static::lazy_static;
use std::fmt::{Binary, Display, LowerHex, Octal};
use ux::{u2, u3, u4};
lazy_static! {
static ref ASM: Assembler = Assembler::new();
}
enum IntegerFormat {
Decimal,
Binary,
Octal,
Hexadecimal,
None,
}
struct PrintIntegers<T: Display + Binary + Octal + LowerHex> {
num: T,
format: IntegerFormat,
}
impl<T: Display + Binary + Octal + LowerHex> PrintIntegers<T> {
fn new(num: T) -> Self {
PrintIntegers {
num,
format: IntegerFormat::Decimal,
}
}
}
impl<T: Display + Binary + Octal + LowerHex> Iterator for PrintIntegers<T> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
match self.format {
IntegerFormat::Decimal => {
self.format = IntegerFormat::Binary;
Some(format!("{}", self.num))
}
IntegerFormat::Binary => {
self.format = IntegerFormat::Octal;
Some(format!("{:#b}", self.num))
}
IntegerFormat::Octal => {
self.format = IntegerFormat::Hexadecimal;
Some(format!("{:#o}", self.num))
}
IntegerFormat::Hexadecimal => {
self.format = IntegerFormat::None;
Some(format!("{:#x}", self.num))
}
IntegerFormat::None => None,
}
}
}
macro_rules! make_test_insn_no_args {
($name:ident, $line:expr, $insn:expr) => {
#[test]
fn $name() {
assert_eq!(ASM.line_to_insn($line).unwrap(), $insn);
}
};
}
make_test_insn_no_args!(asm_line_eq, "EQ", Instruction::CmpEqual);
make_test_insn_no_args!(asm_line_gt, "GT", Instruction::CmpGreater);
make_test_insn_no_args!(asm_line_le, "LE", Instruction::CmpLess);
make_test_insn_no_args!(asm_line_not, "NOT", Instruction::Not);
make_test_insn_no_args!(asm_line_add, "ADD", Instruction::Add);
make_test_insn_no_args!(asm_line_sub, "SUB", Instruction::Sub);
make_test_insn_no_args!(asm_line_and, "AND", Instruction::And);
make_test_insn_no_args!(asm_line_or, "OR", Instruction::Or);
make_test_insn_no_args!(asm_line_xor, "XOR", Instruction::Xor);
make_test_insn_no_args!(asm_line_xnor, "XNOR", Instruction::Xnor);
make_test_insn_no_args!(asm_line_nop, "NOP", Instruction::Nop);
make_test_insn_no_args!(asm_line_halt, "HALT", Instruction::Halt);
make_test_insn_no_args!(asm_line_div, "DIV", Instruction::Div);
make_test_insn_no_args!(asm_line_mul, "MUL", Instruction::Mul);
make_test_insn_no_args!(asm_line_swap, "SWAP", Instruction::Swap);
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_shr,
"SHR",
Instruction::BitShiftVar {
mode: ShiftMode::Logical,
right: true
}
);
make_test_insn_no_args!(
asm_line_shl,
"SHL",
Instruction::BitShiftVar {
mode: ShiftMode::Logical,
right: false
}
);
make_test_insn_no_args!(
asm_line_rotr,
"ROTR",
Instruction::BitShiftVar {
mode: ShiftMode::Circular,
right: true
}
);
make_test_insn_no_args!(
asm_line_rotl,
"ROTL",
Instruction::BitShiftVar {
mode: ShiftMode::Circular,
right: false
}
);
#[test]
fn asm_line_byte() {
for i in 0..=255u8 {
for j in PrintIntegers::new(i) {
assert_eq!(ASM.line_to_insn(&format!("BYTE {}", j)).unwrap(), decode(i));
}
}
}
#[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)] {
for i in 0..=15 {
for j in PrintIntegers::new(i) {
assert_eq!(
ASM.line_to_insn(&format!("MOV {}.{}, {}", reg_str, low_str, j))
.unwrap(),
Instruction::LoadImmediate {
low,
reg: reg.clone(),
val: u4::new(i)
}
);
}
}
}
}
}
#[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)
}
)
}
}
}
#[test]
fn asm_lime_ajmp() {
assert_eq!(
ASM.line_to_insn("AJMP R0").unwrap(),
Instruction::FarJump {
reg: Register::R0,
use_carry: false
}
);
assert_eq!(
ASM.line_to_insn("AJMP R1").unwrap(),
Instruction::FarJump {
reg: Register::R1,
use_carry: false
}
);
}
#[test]
fn asm_lime_ajmpc() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
assert_eq!(
ASM.line_to_insn(&format!("AJMPC {}", reg_str)).unwrap(),
Instruction::FarJump {
reg,
use_carry: true
}
);
}
}
#[test]
fn asm_line_shift_rotate() {
for (insn, right, mode) in [
("SHR", true, ShiftMode::Logical),
("SHL", false, ShiftMode::Logical),
("ROTR", true, ShiftMode::Circular),
("ROTL", false, ShiftMode::Circular),
] {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
for i in 0..7u8 {
for j in PrintIntegers::new(i) {
assert_eq!(
ASM.line_to_insn(&format!("{} {}, {}", insn, reg_str, j))
.unwrap(),
Instruction::BitShift {
right,
reg: reg.clone(),
mode: mode.clone(),
len: u3::new(i)
}
)
}
}
}
}
}
#[test]
fn asm_line_inc() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
assert_eq!(
ASM.line_to_insn(&format!("INC {}", reg_str)).unwrap(),
Instruction::Inc { reg }
);
}
}
#[test]
fn asm_line_dec() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
assert_eq!(
ASM.line_to_insn(&format!("DEC {}", reg_str)).unwrap(),
Instruction::Dec { reg }
);
}
}
#[test]
fn asm_line_compl() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
assert_eq!(
ASM.line_to_insn(&format!("COMPL {}", reg_str)).unwrap(),
Instruction::Complement { reg }
);
}
}
#[test]
fn asm_line_setc() {
for (i, carry) in [(0, false), (1, true)] {
assert_eq!(
ASM.line_to_insn(&format!("SETC {}", i)).unwrap(),
Instruction::Setc { carry }
)
}
}
#[test]
fn asm_line_cload() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
assert_eq!(
ASM.line_to_insn(&format!("CLOAD {}", reg_str)).unwrap(),
Instruction::Cload { reg }
);
}
}
#[test]
fn asm_line_zero() {
for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] {
assert_eq!(
ASM.line_to_insn(&format!("ZERO {}", reg_str)).unwrap(),
Instruction::Zero { reg }
);
}
}
}

125
src/instruction.rs

@ -3,19 +3,19 @@
use ux::{u2, u3, u4};
#[derive(Debug)]
#[derive(Debug, PartialEq, Clone)]
pub enum Register {
R0,
R1,
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Clone)]
pub enum ShiftMode {
Logical,
Circular,
}
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum Instruction {
Load {
reg: Register,
@ -233,3 +233,122 @@ pub fn decode(insn: u8) -> Instruction {
0b11111111 => Instruction::WritePort,
}
}
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::LoadImmediate { low, reg, val } => {
let mut ret = 0b01000000u8;
ret |= match low {
true => 0,
false => 0b00100000,
};
ret |= match reg {
Register::R0 => 0,
Register::R1 => 0b00010000,
};
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 },
},
Instruction::CmpEqual => 0b10000100,
Instruction::CmpGreater => 0b10000101,
Instruction::CmpLess => 0b10000110,
Instruction::Not => 0b10000111,
Instruction::BitShift {
right,
reg,
mode,
len,
} => {
let mut ret = if right { 0b10100000 } else { 0b11000000 };
ret |= match reg {
Register::R0 => 0,
Register::R1 => 0b00010000,
};
ret |= match mode {
ShiftMode::Logical => 0,
ShiftMode::Circular => 0b00001000,
};
ret | u8::from(len)
}
Instruction::Inc { reg } => match reg {
Register::R0 => 0b11100000,
Register::R1 => 0b11100001,
},
Instruction::Dec { reg } => match reg {
Register::R0 => 0b11100010,
Register::R1 => 0b11100011,
},
Instruction::Add => 0b11100100,
Instruction::Sub => 0b11100101,
Instruction::And => 0b11100110,
Instruction::Or => 0b11100111,
Instruction::Xor => 0b11101000,
Instruction::Xnor => 0b11101001,
Instruction::Complement { reg } => match reg {
Register::R0 => 0b11101010,
Register::R1 => 0b11101011,
},
Instruction::BitShiftVar { right, mode } => {
let mut ret = 0b11101100;
ret |= if right { 0 } else { 0b00000010 };
ret |= match mode {
ShiftMode::Logical => 0,
ShiftMode::Circular => 1,
};
ret
}
Instruction::Copy { reg_from } => match reg_from {
Register::R0 => 0b11110000,
Register::R1 => 0b11110001,
},
Instruction::Setc { carry } => 0b11110010 | u8::from(carry),
Instruction::Nop => 0b11110100,
Instruction::Halt => 0b11110101,
Instruction::Cload { reg } => match reg {
Register::R0 => 0b11110110,
Register::R1 => 0b11110111,
},
Instruction::Zero { reg } => match reg {
Register::R0 => 0b11111000,
Register::R1 => 0b11111001,
},
Instruction::Div => 0b11111010,
Instruction::Mul => 0b11111011,
Instruction::Swap => 0b11111100,
Instruction::PortReady => 0b11111101,
Instruction::ReadPort => 0b11111110,
Instruction::WritePort => 0b11111111,
}
}
// This crate really needs more tests, but I can't be bothered
#[cfg(test)]
mod tests {
use crate::instruction::{decode, encode};
#[test]
fn deterministic_instruction_encoding() {
for i in 0..=255u8 {
assert_eq!(i, encode(decode(i)));
}
}
}

25
src/main.rs

@ -1,20 +1,29 @@
// SPDX-License-Identifier: MIT
// Copyright Murad Karammaev, Nikita Kuzmin
mod assembler;
mod cpu;
mod instruction;
use assembler::Assembler;
use cpu::*;
fn main() {
let mut cpu = Cpu::new(&[
0b01001111, // R0 = 0xF
0b01010011, // R1.l = 0x3
0b11100100, // ADD
0b00100000, // [0] = R0
0b11111000, // R0 = 0
0b11110101, // HALT
]);
let asm = Assembler::new();
let code = asm
.assemble(
r#"
MOV R0.h, 0x0
MOV R0.l, 0xF
MOV R1.l, 0x3
ADD
MOV [0], R0
ZERO R0
HALT
"#,
)
.unwrap();
let mut cpu = Cpu::new(&code);
loop {
if cpu.step() {
break;

Loading…
Cancel
Save