Restructuring into multiple crates and projects

This commit is contained in:
Henrik Böving
2021-03-05 19:10:18 +01:00
parent 00d0d7aba3
commit d67eda2edc
26 changed files with 6467 additions and 76 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "hm-asm-simulator"
version = "0.1.0"
authors = ["Henrik Boeving <boeving@hm.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pest = "2.0"
pest_derive = "2.0"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -0,0 +1,30 @@
program = _{ SOI ~ "\n"* ~ (stmt ~ "\n"+) * ~ stmt? ~ EOI }
stmt = { ((label ~ ":")? ~ instruction)}
instruction = {
no_arg_instruction |
arg_instruction ~ argument |
jump_instruction ~ jump_argument |
memory_location_instruction ~ memory_location |
constant_arg_instruction ~ digit_literal
}
memory_location_instruction = {"STA"}
constant_arg_instruction = {"BRZ" | "BRC" | "BRN"}
jump_instruction = {"JMP"}
arg_instruction = {"LDA" | "ADD" | "SUB"}
no_arg_instruction = { "NOP" }
jump_argument = { jump_location | label | memory_location }
argument = { memory_location | digit_literal }
memory_location = { "(" ~ ASCII_HEX_DIGIT ~")" }
digit_literal = {"#" ~ ASCII_HEX_DIGIT}
jump_location = { '0'..'9' | 'a'..'f' }
label = { ASCII_ALPHA_UPPER+ }
WHITESPACE = _{ " " | "\t" }
COMMENT = _{"//" ~ (!"\n" ~ ANY)* }

134
hm-asm-simulator/src/asm.rs Normal file
View File

@@ -0,0 +1,134 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Argument {
MemoryLocation(u8),
Constant(u8),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Label<'a> {
pub name: &'a str,
pub location: u8,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum JumpArgument<'a> {
Location(u8),
Label(&'a str),
MemoryLocation(u8),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Instruction<'a> {
NoArgumentInstruction(NoArgumentInstruction, Option<Label<'a>>),
MemoryLocationInstruction(MemoryLocationInstruction, Option<Label<'a>>),
ConstantArgumentInstruction(ConstantArgumentInstruction, Option<Label<'a>>),
ArgumentInstruction(ArgumentInstruction, Option<Label<'a>>),
Jump(JumpArgument<'a>, Option<Label<'a>>),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NoArgumentInstruction {
NOP,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MemoryLocationInstruction {
STA(u8),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ConstantArgumentInstruction {
BRZ(u8),
BRC(u8),
BRN(u8),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ArgumentInstruction {
LDA(Argument),
ADD(Argument),
SUB(Argument),
}
pub struct BinaryInstruction {
pub opcode: u8,
pub argument: u8,
}
impl<'a> Into<BinaryInstruction> for NoArgumentInstruction {
fn into(self) -> BinaryInstruction {
match self {
NoArgumentInstruction::NOP => BinaryInstruction {
opcode: 0,
argument: 0,
},
}
}
}
impl<'a> Into<BinaryInstruction> for MemoryLocationInstruction {
fn into(self) -> BinaryInstruction {
match self {
MemoryLocationInstruction::STA(arg) => BinaryInstruction {
opcode: 3,
argument: arg,
},
}
}
}
impl<'a> Into<BinaryInstruction> for ConstantArgumentInstruction {
fn into(self) -> BinaryInstruction {
match self {
ConstantArgumentInstruction::BRZ(arg) => BinaryInstruction {
opcode: 9,
argument: arg,
},
ConstantArgumentInstruction::BRC(arg) => BinaryInstruction {
opcode: 10,
argument: arg,
},
ConstantArgumentInstruction::BRN(arg) => BinaryInstruction {
opcode: 11,
argument: arg,
},
}
}
}
impl<'a> Into<BinaryInstruction> for ArgumentInstruction {
fn into(self) -> BinaryInstruction {
match self {
ArgumentInstruction::LDA(arg) => match arg {
Argument::MemoryLocation(arg) => BinaryInstruction {
opcode: 2,
argument: arg,
},
Argument::Constant(arg) => BinaryInstruction {
opcode: 1,
argument: arg,
},
},
ArgumentInstruction::ADD(arg) => match arg {
Argument::MemoryLocation(arg) => BinaryInstruction {
opcode: 5,
argument: arg,
},
Argument::Constant(arg) => BinaryInstruction {
opcode: 4,
argument: arg,
},
},
ArgumentInstruction::SUB(arg) => match arg {
Argument::MemoryLocation(arg) => BinaryInstruction {
opcode: 7,
argument: arg,
},
Argument::Constant(arg) => BinaryInstruction {
opcode: 6,
argument: arg,
},
},
}
}
}

View File

@@ -0,0 +1,98 @@
use crate::asm::*;
use std::collections::HashMap;
use std::fmt;
use serde::Serialize;
#[derive(Serialize)]
pub struct Program {
pub data_memory: [u8; 16],
pub program_memory: [u8; 16],
}
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Data Memory:")?;
for chunk in self.data_memory.chunks(4) {
writeln!(
f,
"{:x} {:x} {:x} {:x}",
chunk[0], chunk[1], chunk[2], chunk[3]
)?;
}
writeln!(f, "Program Memory:")?;
for chunk in self.program_memory.chunks(4) {
writeln!(
f,
"{:x} {:x} {:x} {:x}",
chunk[0], chunk[1], chunk[2], chunk[3]
)?;
}
writeln!(f, "And that's your program!")
}
}
pub fn generate_binary(instructions: Vec<Instruction>) -> Program {
let mut labels: HashMap<&str, u8> = HashMap::new();
let mut data_memory: [u8; 16] = [0; 16];
let mut program_memory: [u8; 16] = [0; 16];
// collect all labels
for instruction in instructions.iter() {
match instruction {
Instruction::NoArgumentInstruction(_, label)
| Instruction::MemoryLocationInstruction(_, label)
| Instruction::ConstantArgumentInstruction(_, label)
| Instruction::ArgumentInstruction(_, label)
| Instruction::Jump(_, label) => {
insert_label(&mut labels, label);
}
}
}
for (c, instruction) in instructions.iter().enumerate() {
let binary_instruction: BinaryInstruction = match instruction {
Instruction::NoArgumentInstruction(instruction, _) => (*instruction).into(),
Instruction::MemoryLocationInstruction(instruction, _) => (*instruction).into(),
Instruction::ConstantArgumentInstruction(instruction, _) => (*instruction).into(),
Instruction::ArgumentInstruction(instruction, _) => (*instruction).into(),
Instruction::Jump(argument, _) => match argument {
JumpArgument::Location(arg) => BinaryInstruction {
opcode: 8,
argument: *arg,
},
JumpArgument::Label(arg) => {
if let Some(address) = labels.get(*arg) {
BinaryInstruction {
opcode: 8,
argument: *address,
}
} else {
panic!("Tried to JMP to label: {}, which does not exist", arg);
}
},
JumpArgument::MemoryLocation(address) => {
BinaryInstruction {
opcode: 12,
argument: *address
}
}
},
};
program_memory[c] = binary_instruction.opcode;
data_memory[c] = binary_instruction.argument;
}
Program {
data_memory,
program_memory,
}
}
pub fn insert_label<'a>(hashmap: &mut HashMap<&'a str, u8>, label: &Option<Label<'a>>) {
if let Some(label) = label {
hashmap.insert(label.name, label.location);
}
}

View File

@@ -0,0 +1,7 @@
#[macro_use]
extern crate pest_derive;
pub mod generate;
pub mod asm;
pub mod parse;
pub mod simulate;

View File

@@ -0,0 +1,180 @@
use crate::asm::*;
use pest::iterators::{Pair, Pairs};
#[derive(Parser)]
#[grammar = "asm.pest"]
pub struct AsmParser;
pub fn parse_asm<'a>(pairs: Pairs<'a, Rule>) -> Vec<Instruction<'a>> {
let mut instruction = Vec::new();
let mut instruction_counter = 0;
for stmnt in pairs {
if let Rule::stmt = stmnt.as_rule() {
let mut stmnt = stmnt.into_inner();
let first = stmnt.next();
let second = stmnt.next();
// Second can only be Some if we have a label, thus second must be the
// instruction if that is the case.
if second.is_some() {
instruction.push(parse_instruction(
second.unwrap(),
first,
instruction_counter,
));
} else {
instruction.push(parse_instruction(first.unwrap(), None, instruction_counter));
}
instruction_counter += 1;
}
if instruction_counter > 16 {
panic!("This program contains more than 16 instructions, that is impossible on this processor");
}
}
instruction
}
fn parse_instruction<'a>(
instruction: Pair<'a, Rule>,
label: Option<Pair<'a, Rule>>,
instruction_counter: u8,
) -> Instruction<'a> {
let label = label.map(|l| parse_label(l, instruction_counter));
let mut instruction = instruction.into_inner();
let mnemonic = instruction.next().unwrap();
match mnemonic.as_rule() {
Rule::no_arg_instruction => parse_no_arg_instruction(mnemonic, label),
Rule::arg_instruction => {
parse_arg_instruction(mnemonic, instruction.next().unwrap(), label)
}
Rule::jump_instruction => {
parse_jump_instruction(mnemonic, instruction.next().unwrap(), label)
}
Rule::memory_location_instruction => {
parse_memory_location_instruction(mnemonic, instruction.next().unwrap(), label)
}
Rule::constant_arg_instruction => {
parse_constant_arg_instruction(mnemonic, instruction.next().unwrap(), label)
}
_ => unreachable!(),
}
}
fn parse_no_arg_instruction<'a>(
instruction: Pair<'a, Rule>,
label: Option<Label<'a>>,
) -> Instruction<'a> {
match instruction.as_str() {
"NOP" => Instruction::NoArgumentInstruction(NoArgumentInstruction::NOP, label),
_ => unreachable!(),
}
}
fn parse_arg_instruction<'a>(
instruction: Pair<'a, Rule>,
arg: Pair<'a, Rule>,
label: Option<Label<'a>>,
) -> Instruction<'a> {
let arg = parse_argument(arg);
match instruction.as_str() {
"LDA" => Instruction::ArgumentInstruction(ArgumentInstruction::LDA(arg), label),
"ADD" => Instruction::ArgumentInstruction(ArgumentInstruction::ADD(arg), label),
"SUB" => Instruction::ArgumentInstruction(ArgumentInstruction::SUB(arg), label),
_ => unreachable!(),
}
}
fn parse_jump_instruction<'a>(
instruction: Pair<'a, Rule>,
arg: Pair<'a, Rule>,
label: Option<Label<'a>>,
) -> Instruction<'a> {
let arg = parse_jump_argument(arg);
match instruction.as_str() {
"JMP" => Instruction::Jump(arg, label),
_ => unreachable!(),
}
}
fn parse_memory_location_instruction<'a>(
instruction: Pair<'a, Rule>,
arg: Pair<'a, Rule>,
label: Option<Label<'a>>,
) -> Instruction<'a> {
let arg_string = arg.as_str();
let arg_value = u8::from_str_radix(&arg_string[1..arg_string.len() - 1], 16).unwrap();
match instruction.as_str() {
"STA" => {
Instruction::MemoryLocationInstruction(MemoryLocationInstruction::STA(arg_value), label)
}
_ => unreachable!(),
}
}
fn parse_constant_arg_instruction<'a>(
instruction: Pair<'a, Rule>,
arg: Pair<'a, Rule>,
label: Option<Label<'a>>,
) -> Instruction<'a> {
let arg_value = u8::from_str_radix(&arg.as_str()[1..], 16).unwrap();
match instruction.as_str() {
"BRZ" => Instruction::ConstantArgumentInstruction(
ConstantArgumentInstruction::BRZ(arg_value),
label,
),
"BRC" => Instruction::ConstantArgumentInstruction(
ConstantArgumentInstruction::BRC(arg_value),
label,
),
"BRN" => Instruction::ConstantArgumentInstruction(
ConstantArgumentInstruction::BRN(arg_value),
label,
),
_ => unreachable!(),
}
}
fn parse_argument<'a>(argument: Pair<'a, Rule>) -> Argument {
let argument = argument.into_inner().next().unwrap();
match argument.as_rule() {
Rule::memory_location => {
let arg_string = argument.as_str();
let arg_value = u8::from_str_radix(&arg_string[1..arg_string.len() - 1], 16).unwrap();
Argument::MemoryLocation(arg_value)
}
Rule::digit_literal => {
let arg_value = u8::from_str_radix(&argument.as_str()[1..], 16).unwrap();
Argument::Constant(arg_value)
}
_ => unreachable!(),
}
}
fn parse_label<'a>(label: Pair<'a, Rule>, instruction_counter: u8) -> Label<'a> {
match label.as_rule() {
Rule::label => Label {
name: label.as_str(),
location: instruction_counter,
},
_ => unreachable!(),
}
}
fn parse_jump_argument<'a>(arg: Pair<'a, Rule>) -> JumpArgument<'a> {
let arg = arg.into_inner().next().unwrap();
match arg.as_rule() {
Rule::label => JumpArgument::Label(arg.as_str()),
Rule::jump_location => {
JumpArgument::Location(u8::from_str_radix(arg.as_str(), 16).unwrap())
},
Rule::memory_location => {
let arg_string = arg.as_str();
let arg_value = u8::from_str_radix(&arg_string[1..arg_string.len() - 1], 16).unwrap();
JumpArgument::MemoryLocation(arg_value)
}
_ => unreachable!(),
}
}

View File

@@ -0,0 +1,275 @@
use crate::generate::insert_label;
use crate::asm::*;
use crate::generate::generate_binary;
use std::collections::HashMap;
use std::fmt;
use serde::Serialize;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct StateRegister {
pub carry: bool,
pub zero: bool,
pub negative: bool
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct OpcodeInfo {
pub addr: u8,
pub content: u8
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct State {
pub step: usize,
pub clk: bool,
pub pc: u8,
pub addr_bus: u8,
pub data_bus: u8,
pub ir: u8, // instruction register
pub dr: u8, // data register
pub akku: u8,
pub sr: StateRegister,
pub opcode_info: Option<OpcodeInfo>
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "<tr>")?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.step)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.clk as u8)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.pc)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.addr_bus)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.data_bus)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.ir)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.dr)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">{}</td>", self.akku)?;
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">C: {}, Z: {}, N: {}</td>", self.sr.carry as u8, self.sr.zero as u8, self.sr.negative as u8)?;
if let Some(opcode_info) = self.opcode_info {
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\">addr: {}, val: {}</td>", opcode_info.addr, opcode_info.content)?;
} else {
writeln!(f, "<td style=\"border: 1px solid #000000; padding: 0mm 1.91mm;\"></td>")?;
}
writeln!(f, "</tr>")
}
}
pub fn simulate<'a>(instructions: Vec<Instruction<'a>>, max_steps: usize) -> Vec<State> {
let mut data_memory = generate_binary(instructions.clone()).data_memory;
let mut labels: HashMap<&str, u8> = HashMap::new();
let mut states: Vec<State> = Vec::new();
let mut step: usize = 0;
let mut clk: bool;
let mut pc: u8 = 0;
let mut addr_bus: u8;
let mut data_bus: u8;
let mut ir : u8 = 0;
let mut dr : u8 = 0;
let mut akku : u8 = 0;
let mut sr: StateRegister = StateRegister {
carry: false,
zero: false,
negative: false
};
for instruction in instructions.iter() {
match instruction {
Instruction::NoArgumentInstruction(_, label)
| Instruction::MemoryLocationInstruction(_, label)
| Instruction::ConstantArgumentInstruction(_, label)
| Instruction::ArgumentInstruction(_, label)
| Instruction::Jump(_, label) => {
insert_label(&mut labels, label);
}
}
}
let mut next_pc: Option<u8> = None;
let mut next_akku = 0;
let mut next_carry = false;
let mut next_data_mem_addr: Option<usize> = None;
let mut next_data_mem_val: Option<u8> = None;
loop {
if next_akku != akku {
next_carry = (next_akku & (1<<4)) != 0;
}
if let Some(mut next_pc_value) = next_pc {
next_pc_value = next_pc_value % 16;
next_akku = next_akku % 16;
pc = next_pc_value;
next_pc = None;
}
akku = next_akku;
if let Some(addr) = next_data_mem_addr {
data_memory[addr] = next_data_mem_val.unwrap();
next_data_mem_addr = None;
next_data_mem_val = None;
}
let instruction = if pc as usize > instructions.len() - 1 {
Instruction::NoArgumentInstruction(NoArgumentInstruction::NOP, None)
} else {
instructions[pc as usize]
};
let binary_instruction: BinaryInstruction = match instruction {
Instruction::NoArgumentInstruction(instruction, _) => instruction.into(),
Instruction::MemoryLocationInstruction(instruction, _) => instruction.into(),
Instruction::ConstantArgumentInstruction(instruction, _) => instruction.into(),
Instruction::ArgumentInstruction(instruction, _) => instruction.into(),
Instruction::Jump(argument, _) => match argument {
JumpArgument::Location(arg) => BinaryInstruction {
opcode: 8,
argument: arg,
},
JumpArgument::Label(arg) => {
if let Some(address) = labels.get(arg) {
BinaryInstruction {
opcode: 8,
argument: *address,
}
} else {
panic!("Tried to JMP to label: {}, which does not exist", arg);
}
},
JumpArgument::MemoryLocation(address) => {
BinaryInstruction {
opcode: 12,
argument: address
}
}
},
};
clk = false;
addr_bus = pc;
data_bus = data_memory[pc as usize];
let opcode_info = match instruction {
Instruction::MemoryLocationInstruction(instruction, _) => {
match instruction {
MemoryLocationInstruction::STA(location) => Some(OpcodeInfo{addr: location, content: data_memory[location as usize]}),
}
},
Instruction::ArgumentInstruction(instruction, _) => {
match instruction {
ArgumentInstruction::ADD(argument) | ArgumentInstruction::SUB(argument) | ArgumentInstruction::LDA(argument) => match argument {
Argument::MemoryLocation(location) => Some(OpcodeInfo{addr: location, content: data_memory[location as usize]}),
_ => None
}
}
},
Instruction::Jump(JumpArgument::MemoryLocation(location), _) => {
Some(OpcodeInfo{
addr: location,
content: data_memory[location as usize]
})
}
_ => None
};
states.push(State{
step,
clk,
pc,
addr_bus,
data_bus,
ir,
dr,
akku,
sr,
opcode_info
});
clk = true;
sr.carry = next_carry;
sr.zero = akku == 0 || akku == (1<<4);
sr.negative = (akku & (1<<3)) != 0;
dr = binary_instruction.argument;
ir = binary_instruction.opcode;
addr_bus = dr;
match instruction {
Instruction::NoArgumentInstruction(instruction, _) => match instruction {
NoArgumentInstruction::NOP => {}
},
Instruction::ConstantArgumentInstruction(instruction, _) => match instruction {
ConstantArgumentInstruction::BRC(arg) if sr.carry => next_pc = Some(pc + arg),
ConstantArgumentInstruction::BRN(arg) if sr.negative => next_pc = Some(pc + arg),
ConstantArgumentInstruction::BRZ(arg) if sr.zero => next_pc = Some(pc + arg),
_ => {}
},
Instruction::Jump(arg, _) => match arg {
JumpArgument::Label(label) => {
next_pc = Some(*labels.get(label).unwrap());
addr_bus = *labels.get(label).unwrap();
},
JumpArgument::Location(location) => {
next_pc = Some(location);
addr_bus = location;
},
JumpArgument::MemoryLocation(location) => {
next_pc = Some(data_memory[location as usize]);
addr_bus = data_memory[location as usize];
}
},
Instruction::MemoryLocationInstruction(arg, _) => match arg {
MemoryLocationInstruction::STA(arg) => {
next_data_mem_addr = Some(arg as usize);
next_data_mem_val = Some(akku);
}
},
Instruction::ArgumentInstruction(instruction, _) => match instruction {
ArgumentInstruction::LDA(arg) => match arg {
Argument::MemoryLocation(location) => next_akku = data_memory[location as usize],
Argument::Constant(val) => next_akku = val
},
ArgumentInstruction::ADD(arg) => match arg {
Argument::MemoryLocation(location) => next_akku = akku + data_memory[location as usize],
Argument::Constant(val) => next_akku = akku + val
},
ArgumentInstruction::SUB(arg) => match arg {
Argument::MemoryLocation(location) => next_akku = akku + (data_memory[location as usize] ^ 0b1111) + 1,
Argument::Constant(val) => next_akku = akku + (val ^ 0b1111) + 1
}
}
}
data_bus = data_memory[addr_bus as usize];
if let None = next_pc {
next_pc = Some(pc + 1);
}
states.push(State{
step,
clk,
pc,
addr_bus,
data_bus,
ir,
dr,
akku,
sr,
opcode_info
});
step += 1;
if step == max_steps {
break;
}
}
return states;
}