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/ target/
.idea/

18
ASM-SYNTAX.md

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

40
Cargo.lock

@ -3,9 +3,49 @@
version = 3 version = 3
[[package]] [[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" name = "toy_cpu_4bit"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"lazy_static",
"regex",
"ux", "ux",
] ]

2
Cargo.toml

@ -5,3 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
ux = "0.1.3" 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 You definitely need some tool to design instruction opcodes
for anything more complicated than just 1-byte instructions. 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 ## Load/store instructions MUST be able to use register as address
I have no idea how we didn't notice this mistake. 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, I guess it is not too bad for a CPU with 16 bytes of memory,
but for something bigger it is unacceptable. 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}; use ux::{u2, u3, u4};
#[derive(Debug)] #[derive(Debug, PartialEq, Clone)]
pub enum Register { pub enum Register {
R0, R0,
R1, R1,
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Clone)]
pub enum ShiftMode { pub enum ShiftMode {
Logical, Logical,
Circular, Circular,
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum Instruction { pub enum Instruction {
Load { Load {
reg: Register, reg: Register,
@ -233,3 +233,122 @@ pub fn decode(insn: u8) -> Instruction {
0b11111111 => Instruction::WritePort, 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 // SPDX-License-Identifier: MIT
// Copyright Murad Karammaev, Nikita Kuzmin // Copyright Murad Karammaev, Nikita Kuzmin
mod assembler;
mod cpu; mod cpu;
mod instruction; mod instruction;
use assembler::Assembler;
use cpu::*; use cpu::*;
fn main() { fn main() {
let mut cpu = Cpu::new(&[ let asm = Assembler::new();
0b01001111, // R0 = 0xF let code = asm
0b01010011, // R1.l = 0x3 .assemble(
0b11100100, // ADD r#"
0b00100000, // [0] = R0 MOV R0.h, 0x0
0b11111000, // R0 = 0 MOV R0.l, 0xF
0b11110101, // HALT MOV R1.l, 0x3
]); ADD
MOV [0], R0
ZERO R0
HALT
"#,
)
.unwrap();
let mut cpu = Cpu::new(&code);
loop { loop {
if cpu.step() { if cpu.step() {
break; break;

Loading…
Cancel
Save