Added an assembler for the m68k

It can encode some basic instructions but not the more complicated
addressing modes or special instructions.  It can keep track of
labels and patch the displacement addresses after assembling the
output data, but it doesn't check for duplicates or that a label
has been declared before it appears.  It also doesn't take into
account the origin yet.  To run it, there is an m68kas command
that will take a file and print the data:
```
cargo run -p moa --bin m68kas <filename>
```

It's not yet tied into the rest of the system, but could be used in
the debugger to allow insertion of new code into a running system.
This commit is contained in:
transistor 2022-05-12 21:53:34 -07:00
parent 061c13fdc7
commit 586a16509f
7 changed files with 1003 additions and 15 deletions

22
src/bin/m68kas.rs Normal file
View File

@ -0,0 +1,22 @@
use std::fs;
use std::env;
use moa::cpus::m68k::M68kType;
use moa::cpus::m68k::assembler::M68kAssembler;
fn main() {
let mut assembler = M68kAssembler::new(M68kType::MC68000);
let filename = env::args().nth(1).unwrap();
let text = fs::read_to_string(filename).unwrap();
let words = assembler.assemble_words(&text).unwrap();
println!("Output:");
for word in words.iter() {
print!("{:04x} ", word);
}
println!("");
}

586
src/cpus/m68k/assembler.rs Normal file
View File

@ -0,0 +1,586 @@
use std::collections::HashMap;
use crate::error::Error;
use crate::parser::{AssemblyLine, AssemblyOperand, AssemblyParser};
use crate::parser::{expect_args, expect_label, expect_immediate};
use super::state::M68kType;
use super::instructions::Size;
#[repr(usize)]
#[derive(Copy, Clone)]
pub enum Disallow {
None = 0x0000,
NoDReg = 0x0001,
NoAReg = 0x0002,
NoIndirect = 0x0004,
NoIndirectPre = 0x0008,
NoIndirectPost = 0x0010,
NoIndirectOffset = 0x0020,
NoIndirectIndexReg = 0x0040,
NoIndirectImmediate = 0x0080,
NoImmediate = 0x0100,
NoPCRelative = 0x0200,
NoPCRelativeIndex = 0x0400,
NoRegs = 0x0003,
NoRegsOrImmediate = 0x0103,
NoRegsImmediateOrPC = 0x0703,
NoARegImmediateOrPC = 0x0702,
NoRegsPrePostOrImmediate = 0x011B,
NoImmediateOrPC = 0x0700,
}
impl Disallow {
pub fn check(&self, lineno: usize, disallow: Disallow) -> Result<(), Error> {
if (*self as usize) & (disallow as usize) == 0 {
Ok(())
} else {
Err(Error::new(&format!("error at line {}: invalid addressing mode for the instruction", lineno)))
}
}
}
pub enum RelocationType {
Displacement,
Word(String),
Long(String),
}
pub struct Relocation {
pub rtype: RelocationType,
pub label: String,
pub index: usize,
pub from_origin: usize,
}
impl Relocation {
pub fn new(rtype: RelocationType, label: String, index: usize, from_origin: usize) -> Self {
Self {
rtype,
label,
index,
from_origin,
}
}
}
pub struct M68kAssembler {
pub cputype: M68kType,
pub labels: HashMap<String, usize>,
pub output: Vec<u16>,
pub relocations: Vec<Relocation>,
pub current_origin: usize,
}
impl M68kAssembler {
pub fn new(cputype: M68kType) -> Self {
Self {
cputype,
labels: HashMap::new(),
output: vec![],
relocations: vec![],
current_origin: 0,
}
}
pub fn assemble(&mut self, text: &str) -> Result<Vec<u8>, Error> {
self.assemble_in_place(text)?;
Ok(self.output.iter().fold(vec![], |mut acc, item| {
acc.push((*item >> 8) as u8);
acc.push(*item as u8);
acc
}))
}
pub fn assemble_words(&mut self, text: &str) -> Result<Vec<u16>, Error> {
self.assemble_in_place(text)?;
Ok(self.output.clone())
}
pub fn assemble_in_place(&mut self, text: &str) -> Result<(), Error> {
let lines = self.parse(text)?;
for (lineno, line) in lines {
self.convert(lineno, &line)?;
}
self.apply_relocations()?;
Ok(())
}
fn parse(&mut self, text: &str) -> Result<Vec<(usize, AssemblyLine)>, Error> {
let mut output = vec![];
let iter = text.split_terminator("\n");
for (lineno, line_text) in iter.enumerate() {
let mut parser = AssemblyParser::new(lineno, line_text);
let parsed_line = parser.parse_line()?;
if let Some(line) = parsed_line {
output.push((lineno, line));
}
}
Ok(output)
}
fn apply_relocations(&mut self) -> Result<(), Error> {
for reloc in self.relocations.iter() {
match reloc.rtype {
RelocationType::Displacement => {
// TODO this doesn't yet take into accound the origin
let location = *self.labels.get(&reloc.label).ok_or_else(|| Error::new(&format!("error during relocation, label undefined {:?}", reloc.label)))?;
self.output[reloc.index] |= ((self.output[reloc.index] as i8 * 2 + 2) - (location as i8 * 2)) as u16 & 0x00ff;
},
_ => panic!("relocation type unimplemented"),
}
}
Ok(())
}
fn convert(&mut self, lineno: usize, line: &AssemblyLine) -> Result<(), Error> {
match line {
AssemblyLine::Directive(name, list) => {
println!("skipping directive {} ({:?})", name, list);
},
AssemblyLine::Label(label) => {
println!("label {}", label);
self.labels.insert(label.clone(), self.output.len() - 1);
},
AssemblyLine::Instruction(name, list) => {
self.convert_instruction(lineno, &name, &list)?;
},
}
Ok(())
}
fn convert_instruction(&mut self, lineno: usize, mneumonic: &str, args: &[AssemblyOperand]) -> Result<(), Error> {
match mneumonic {
"bra" => {
let label = expect_label(lineno, args)?;
self.output.push(0x6000);
self.relocations.push(Relocation::new(RelocationType::Displacement, label, self.output.len() - 1, self.current_origin));
},
"bsr" => {
let label = expect_label(lineno, args)?;
self.output.push(0x6100);
self.relocations.push(Relocation::new(RelocationType::Displacement, label, self.output.len() - 1, self.current_origin));
},
"illegal" => {
self.output.push(0x4AFC);
},
"lea" => {
expect_args(lineno, args, 2)?;
let reg = expect_address_register(lineno, &args[0])?;
let (effective_address, additional_words) = convert_target(lineno, &args[1], Size::Long, Disallow::NoRegsPrePostOrImmediate)?;
self.output.push(0x41C0 | (reg << 9) | effective_address);
self.output.extend(additional_words);
},
"nop" => {
self.output.push(0x4E71);
},
"rts" => {
self.output.push(0x4E75);
},
"rte" => {
self.output.push(0x4E73);
},
"rtr" => {
self.output.push(0x4E77);
},
"stop" => {
let immediate = expect_immediate(lineno, &args[0])?;
self.output.push(0x4E72);
self.output.extend(convert_immediate(lineno, immediate, Size::Word)?);
},
"trapv" => {
self.output.push(0x4E76);
},
_ => {
self.convert_sized_instruction(lineno, mneumonic, args)?;
},
}
Ok(())
}
fn convert_sized_instruction(&mut self, lineno: usize, mneumonic: &str, args: &[AssemblyOperand]) -> Result<(), Error> {
let operation_size = get_size_from_mneumonic(mneumonic).ok_or_else(|| Error::new(&format!("error at line {}: expected a size specifier (b/w/l)", lineno)));
match &mneumonic[..mneumonic.len() - 1] {
"addi" | "addai" => {
self.convert_common_immediate_instruction(lineno, 0x0600, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"add" | "adda" => {
self.convert_common_dreg_instruction(lineno, 0xD000, args, operation_size?, Disallow::None)?;
},
"andi" => {
if !self.check_convert_flags_instruction(lineno, 0x23C, 0x27C, args)? {
self.convert_common_immediate_instruction(lineno, 0x0200, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
}
},
"and" => {
self.convert_common_dreg_instruction(lineno, 0xC000, args, operation_size?, Disallow::None)?;
},
"asr" | "asl" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE000, args, operation_size?)?;
},
"clr" => {
self.convert_common_single_operand_instruction(lineno, 0x4200, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"cmpi" => {
self.convert_common_immediate_instruction(lineno, 0x0C00, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"cmp" => {
self.convert_common_dreg_instruction(lineno, 0xB000, args, operation_size?, Disallow::None)?;
},
"eori" => {
if !self.check_convert_flags_instruction(lineno, 0x0A3C, 0x0A7C, args)? {
self.convert_common_immediate_instruction(lineno, 0x0A00, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
}
},
"eor" => {
self.convert_common_dreg_instruction(lineno, 0xB000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"lsr" | "lsl" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE008, args, operation_size?)?;
},
"move" | "movea" => {
let operation_size = operation_size?;
expect_args(lineno, args, 2)?;
let (effective_address_left, additional_words_left) = convert_target(lineno, &args[0], operation_size, Disallow::None)?;
let (effective_address_right, additional_words_right) = convert_target(lineno, &args[1], operation_size, Disallow::None)?;
let effective_address_left = (effective_address_left >> 3) | (effective_address_left << 3);
self.output.push(0x0000 | encode_size_for_move(operation_size) | effective_address_left | effective_address_right);
self.output.extend(additional_words_left);
self.output.extend(additional_words_right);
},
"neg" => {
self.convert_common_single_operand_instruction(lineno, 0x4400, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"negx" => {
self.convert_common_single_operand_instruction(lineno, 0x4000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"not" => {
self.convert_common_single_operand_instruction(lineno, 0x4600, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"ori" => {
if !self.check_convert_flags_instruction(lineno, 0x003C, 0x007C, args)? {
self.convert_common_immediate_instruction(lineno, 0x0000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
}
},
"or" => {
self.convert_common_dreg_instruction(lineno, 0x8000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"ror" | "rol" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE018, args, operation_size?)?;
},
"roxr" | "roxl" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE010, args, operation_size?)?;
},
"subi" | "subai" => {
self.convert_common_immediate_instruction(lineno, 0x0400, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"sub" | "suba" => {
self.convert_common_dreg_instruction(lineno, 0x9000, args, operation_size?, Disallow::None)?;
},
// TODO complete remaining instructions
_ => return Err(Error::new(&format!("unrecognized instruction at line {}: {:?}", lineno, mneumonic))),
}
Ok(())
}
fn convert_common_immediate_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
expect_args(lineno, args, 2)?;
let immediate = expect_immediate(lineno, &args[0])?;
let (effective_address, additional_words) = convert_target(lineno, &args[1], operation_size, disallow)?;
self.output.push(opcode | encode_size(operation_size) | effective_address);
self.output.extend(convert_immediate(lineno, immediate, operation_size)?);
self.output.extend(additional_words);
Ok(())
}
fn convert_common_dreg_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
expect_args(lineno, args, 2)?;
let (direction, reg, operand) = convert_reg_and_other(lineno, args, Disallow::NoAReg)?;
let (effective_address, additional_words) = convert_target(lineno, operand, operation_size, disallow)?;
self.output.push(opcode | encode_size(operation_size) | direction | (reg << 9) | effective_address);
self.output.extend(additional_words);
Ok(())
}
fn convert_common_single_operand_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
expect_args(lineno, args, 1)?;
let (effective_address, additional_words) = convert_target(lineno, &args[0], operation_size, disallow)?;
self.output.push(opcode | encode_size(operation_size) | effective_address);
self.output.extend(additional_words);
Ok(())
}
fn check_convert_flags_instruction(&mut self, lineno: usize, opcode_ccr: u16, opcode_sr: u16, args: &[AssemblyOperand]) -> Result<bool, Error> {
if let AssemblyOperand::Register(name) = &args[1] {
let opcode = match name.as_str() {
"ccr" => Some(opcode_ccr),
"sr" => Some(opcode_sr),
_ => None,
};
if let Some(opcode) = opcode {
expect_args(lineno, args, 2)?;
let immediate = expect_immediate(lineno, &args[0])?;
self.output.push(opcode);
self.output.extend(convert_immediate(lineno, immediate, Size::Word)?);
return Ok(true);
}
}
Ok(false)
}
fn convert_common_shift_instruction(&mut self, lineno: usize, mneumonic: &str, opcode: u16, args: &[AssemblyOperand], operation_size: Size) -> Result<(), Error> {
let dirstr = &mneumonic[mneumonic.len() - 2..mneumonic.len() - 1];
let direction = if dirstr == "r" {
0
} else if dirstr == "l" {
1 << 8
} else {
return Err(Error::new(&format!("error at line {}: expected direction of (l)eft or (r)ight, but found {:?}", lineno, dirstr)));
};
match &args {
&[AssemblyOperand::Immediate(_), AssemblyOperand::Register(_)] => {
let mut immediate = expect_immediate(lineno, &args[0])?;
if immediate < 1 || immediate > 8 {
return Err(Error::new(&format!("error at line {}: immediate value must be between 1 and 8, found {:?}", lineno, args)));
} else if immediate == 8 {
immediate = 0;
}
let reg = expect_data_register(lineno, &args[1])?;
self.output.push(opcode | ((immediate as u16) << 9) | direction | encode_size(operation_size) | (0b0 << 5) | reg);
},
&[AssemblyOperand::Register(_), AssemblyOperand::Register(_)] => {
let bit_reg = expect_data_register(lineno, &args[0])?;
let reg = expect_data_register(lineno, &args[1])?;
self.output.push(opcode | ((bit_reg as u16) << 9) | direction | encode_size(operation_size) | (0b1 << 5) | reg);
},
//&[_] => {
// let (effective_address, additional_words) = convert_target(lineno, &args[0], Size::Word, Disallow::NoRegsImmediateOrPC)?;
// self.output.push(opcode | effective_address);
// self.output.extend(additional_words);
//},
_ => return Err(Error::new(&format!("error at line {}: unexpected addressing mode, found {:?}", lineno, args))),
}
Ok(())
}
}
fn convert_target(lineno: usize, operand: &AssemblyOperand, size: Size, disallow: Disallow) -> Result<(u16, Vec<u16>), Error> {
match operand {
AssemblyOperand::Register(name) => {
convert_register(lineno, name, disallow)
},
AssemblyOperand::Immediate(value) => {
disallow.check(lineno, Disallow::NoImmediate)?;
Ok((0b111100, convert_immediate(lineno, *value, size)?))
},
AssemblyOperand::Indirect(args) => {
convert_indirect(lineno, args, disallow)
},
AssemblyOperand::IndirectPost(args, operator) => {
disallow.check(lineno, Disallow::NoIndirectPost)?;
if args.len() == 1 && operator == "+" {
if let AssemblyOperand::Register(name) = &args[0] {
if name.starts_with("a") {
let reg = expect_reg_num(lineno, name)?;
return Ok(((0b011 << 3) | reg, vec![]));
}
}
}
Err(Error::new(&format!("error at line {}: post-increment operator can only be used with a single address register", lineno)))
},
AssemblyOperand::IndirectPre(operator, args) => {
disallow.check(lineno, Disallow::NoIndirectPre)?;
if args.len() == 1 && operator == "-" {
if let AssemblyOperand::Register(name) = &args[0] {
if name.starts_with("a") {
let reg = expect_reg_num(lineno, name)?;
return Ok(((0b100 << 3) | reg, vec![]));
}
}
}
Err(Error::new(&format!("error at line {}: pre-decrement operator can only be used with a single address register", lineno)))
},
// TODO complete remaining types
_ => Err(Error::new(&format!("not implemented: {:?}", operand))),
}
}
fn convert_register(lineno: usize, name: &str, disallow: Disallow) -> Result<(u16, Vec<u16>), Error> {
match name {
name if name.starts_with("d") => {
disallow.check(lineno, Disallow::NoDReg)?;
let reg = expect_reg_num(lineno, name)?;
Ok(((0b000 << 3) | reg, vec![]))
},
name if name.starts_with("a") => {
disallow.check(lineno, Disallow::NoAReg)?;
let reg = expect_reg_num(lineno, name)?;
Ok(((0b001 << 3) | reg, vec![]))
},
"sp" => {
disallow.check(lineno, Disallow::NoAReg)?;
Ok(((0b001 << 3) | 7, vec![]))
},
_ => Err(Error::new(&format!("error at line {}: invalid register {:?}", lineno, name))),
}
}
fn convert_indirect(lineno: usize, args: &[AssemblyOperand], disallow: Disallow) -> Result<(u16, Vec<u16>), Error> {
match &args {
&[AssemblyOperand::Register(name)] => {
disallow.check(lineno, Disallow::NoIndirect)?;
let reg = expect_address_reg_num(lineno, name)?;
Ok(((0b010 << 3) | reg, vec![]))
},
&[AssemblyOperand::Immediate(address)] => {
disallow.check(lineno, Disallow::NoIndirectImmediate)?;
if *address < u16::MAX as usize {
Ok((0b111000, convert_immediate(lineno, *address, Size::Word)?))
} else {
Ok((0b111001, convert_immediate(lineno, *address, Size::Long)?))
}
},
&[AssemblyOperand::Immediate(offset), AssemblyOperand::Register(name)] => {
if name == "pc" {
disallow.check(lineno, Disallow::NoPCRelative)?;
Ok((0b111010, convert_immediate(lineno, *offset, Size::Word)?))
} else {
disallow.check(lineno, Disallow::NoIndirectOffset)?;
let reg = expect_address_reg_num(lineno, name)?;
Ok(((0b101 << 3) | reg, convert_immediate(lineno, *offset, Size::Word)?))
}
},
// TODO add the index register mode
_ => {
Err(Error::new(&format!("error at line {}: expected valid indirect addressing mode, but found {:?}", lineno, args)))
}
}
}
fn convert_reg_and_other<'a>(lineno: usize, args: &'a [AssemblyOperand], disallow: Disallow) -> Result<(u16, u16, &'a AssemblyOperand), Error> {
match &args {
&[AssemblyOperand::Register(reg), effective_address] => {
Ok(((0b1 << 8), expect_reg_num(lineno, &reg)?, effective_address))
},
&[effective_address, AssemblyOperand::Register(reg)] => {
Ok(((0b0 << 8), expect_reg_num(lineno, &reg)?, effective_address))
},
_ => {
Err(Error::new(&format!("error at line {}: expected register and effective address, but found {:?}", lineno, args)))
}
}
}
fn convert_immediate(lineno: usize, value: usize, size: Size) -> Result<Vec<u16>, Error> {
match size {
Size::Byte => {
if value < u8::MAX as usize {
Ok(vec![value as u16])
} else {
Err(Error::new(&format!("error at line {}: immediate number is out of range; must be less than {}, but number is {:?}", lineno, u8::MAX, value)))
}
},
Size::Word => {
if value < u16::MAX as usize {
Ok(vec![value as u16])
} else {
Err(Error::new(&format!("error at line {}: immediate number is out of range; must be less than {}, but number is {:?}", lineno, u16::MAX, value)))
}
},
Size::Long => Ok(vec![(value >> 16) as u16, value as u16]),
}
}
fn expect_data_register(lineno: usize, operand: &AssemblyOperand) -> Result<u16, Error> {
if let AssemblyOperand::Register(name) = operand {
if name.starts_with("d") {
return expect_reg_num(lineno, name);
}
}
Err(Error::new(&format!("error at line {}: expected a data register, but found {:?}", lineno, operand)))
}
fn expect_address_register(lineno: usize, operand: &AssemblyOperand) -> Result<u16, Error> {
if let AssemblyOperand::Register(name) = operand {
if name.starts_with("d") {
return expect_reg_num(lineno, name);
}
}
Err(Error::new(&format!("error at line {}: expected an address register, but found {:?}", lineno, operand)))
}
fn expect_address_reg_num(lineno: usize, name: &str) -> Result<u16, Error> {
if name.starts_with("a") {
return expect_reg_num(lineno, name);
}
Err(Error::new(&format!("error at line {}: expected an address register, but found {:?}", lineno, name)))
}
fn expect_reg_num(lineno: usize, name: &str) -> Result<u16, Error> {
if let Ok(number) = str::parse::<u16>(&name[1..]) {
if number <= 7 {
return Ok(number);
}
}
Err(Error::new(&format!("error at line {}: no such register {:?}", lineno, name)))
}
fn get_size_from_mneumonic(s: &str) -> Option<Size> {
let size_ch = s.chars().last()?;
match size_ch {
'b' => Some(Size::Byte),
'w' => Some(Size::Word),
'l' => Some(Size::Long),
_ => None
}
}
fn encode_size(size: Size) -> u16 {
match size {
Size::Byte => 0b00 << 6,
Size::Word => 0b01 << 6,
Size::Long => 0b10 << 6,
}
}
fn encode_size_for_move(size: Size) -> u16 {
match size {
Size::Byte => 0b01 << 12,
Size::Word => 0b11 << 12,
Size::Long => 0b10 << 12,
}
}
fn encode_size_bit(size: Size) -> Result<u16, Error> {
match size {
Size::Word => Ok(0b01 << 6),
Size::Long => Ok(0b10 << 6),
_ => Err(Error::new(&format!("invalid size for this operation: {:?}", size)))
}
}

View File

@ -307,8 +307,8 @@ impl fmt::Display for BaseRegister {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BaseRegister::None => Ok(()),
BaseRegister::PC => write!(f, "%pc + "),
BaseRegister::AReg(reg) => write!(f, "%a{} + ", reg),
BaseRegister::PC => write!(f, "%pc"),
BaseRegister::AReg(reg) => write!(f, "%a{}", reg),
}
}
}
@ -316,11 +316,10 @@ impl fmt::Display for BaseRegister {
fn fmt_index_disp(index: &Option<IndexRegister>) -> String {
match index {
Some(index) => {
let mut result = format!("%{}", index.xreg);
let mut result = format!(", %{}", index.xreg);
if index.scale != 0 {
result += &format!("<< {}", index.scale);
}
result += " + ";
result
},
None => "".to_string(),
@ -330,7 +329,7 @@ fn fmt_index_disp(index: &Option<IndexRegister>) -> String {
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Target::Immediate(value) => write!(f, "#{:08x}", value),
Target::Immediate(value) => write!(f, "#{:#08x}", value),
Target::DirectDReg(reg) => write!(f, "%d{}", reg),
Target::DirectAReg(reg) => write!(f, "%a{}", reg),
Target::IndirectAReg(reg) => write!(f, "(%a{})", reg),
@ -338,7 +337,7 @@ impl fmt::Display for Target {
Target::IndirectARegDec(reg) => write!(f, "-(%a{})", reg),
Target::IndirectRegOffset(base_reg, index_reg, offset) => {
let index_str = fmt_index_disp(index_reg);
write!(f, "({}{}#{:04x})", base_reg, index_str, offset)
write!(f, "(#{:04x}, {}{})", offset, base_reg, index_str)
},
Target::IndirectMemoryPreindexed(base_reg, index_reg, base_disp, outer_disp) => {
let index_str = fmt_index_disp(index_reg);
@ -398,12 +397,14 @@ impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Instruction::ABCD(src, dest) => write!(f, "abcd\t{}, {}", src, dest),
Instruction::ADD(src @ Target::Immediate(_), dest, size) => write!(f, "addi{}\t{}, {}", size, src, dest),
Instruction::ADD(src, dest, size) => write!(f, "add{}\t{}, {}", size, src, dest),
Instruction::ADDA(target, reg, size) => write!(f, "adda{}\t{}, %a{}", size, target, reg),
Instruction::ADDX(src, dest, size) => write!(f, "addx{}\t{}, {}", size, src, dest),
Instruction::AND(src @ Target::Immediate(_), dest, size) => write!(f, "andi{}\t{}, {}", size, src, dest),
Instruction::AND(src, dest, size) => write!(f, "and{}\t{}, {}", size, src, dest),
Instruction::ANDtoCCR(value) => write!(f, "andb\t{:02x}, %ccr", value),
Instruction::ANDtoSR(value) => write!(f, "andw\t{:04x}, %sr", value),
Instruction::ANDtoCCR(value) => write!(f, "andib\t#{:#02x}, %ccr", value),
Instruction::ANDtoSR(value) => write!(f, "andiw\t#{:#04x}, %sr", value),
Instruction::ASd(src, dest, size, dir) => write!(f, "as{}{}\t{}, {}", dir, size, src, dest),
Instruction::Bcc(cond, offset) => write!(f, "b{}\t{}", cond, offset),
@ -425,6 +426,7 @@ impl fmt::Display for Instruction {
Instruction::CHK(target, reg, size) => write!(f, "chk{}\t{}, %d{}", size, target, reg),
Instruction::CLR(target, size) => write!(f, "clr{}\t{}", size, target),
Instruction::CMP(src @ Target::Immediate(_), dest, size) => write!(f, "cmpi{}\t{}, {}", size, src, dest),
Instruction::CMP(src, dest, size) => write!(f, "cmp{}\t{}, {}", size, src, dest),
Instruction::CMPA(target, reg, size) => write!(f, "cmpa{}\t{}, %a{}", size, target, reg),
@ -435,9 +437,10 @@ impl fmt::Display for Instruction {
write!(f, "div{}l\t{}, {}%d{}", sign, src, opt_reg, destl)
},
Instruction::EOR(src @ Target::Immediate(_), dest, size) => write!(f, "eori{}\t{}, {}", size, src, dest),
Instruction::EOR(src, dest, size) => write!(f, "eor{}\t{}, {}", size, src, dest),
Instruction::EORtoCCR(value) => write!(f, "eorb\t{:02x}, %ccr", value),
Instruction::EORtoSR(value) => write!(f, "eorw\t{:04x}, %sr", value),
Instruction::EORtoCCR(value) => write!(f, "eorib\t#{:#02x}, %ccr", value),
Instruction::EORtoSR(value) => write!(f, "eoriw\t#{:#04x}, %sr", value),
Instruction::EXG(src, dest) => write!(f, "exg\t{}, {}", src, dest),
Instruction::EXT(reg, from_size, to_size) => write!(f, "ext{}{}\t%d{}", if *from_size == Size::Byte && *to_size == Size::Long { "b" } else { "" }, to_size, reg),
@ -468,7 +471,7 @@ impl fmt::Display for Instruction {
Direction::ToTarget => write!(f, "movep{}\t%d{}, ({}, %a{})", size, dreg, areg, offset),
Direction::FromTarget => write!(f, "movep{}\t({}, %a{}), %d{}", size, areg, offset, dreg),
},
Instruction::MOVEQ(value, reg) => write!(f, "moveq\t#{:02x}, %d{}", value, reg),
Instruction::MOVEQ(value, reg) => write!(f, "moveq\t#{:#02x}, %d{}", value, reg),
Instruction::MOVEUSP(target, dir) => match dir {
Direction::ToTarget => write!(f, "movel\t%usp, {}", target),
Direction::FromTarget => write!(f, "movel\t{}, %usp", target),
@ -486,9 +489,10 @@ impl fmt::Display for Instruction {
Instruction::NOP => write!(f, "nop"),
Instruction::NOT(target, size) => write!(f, "not{}\t{}", size, target),
Instruction::OR(src @ Target::Immediate(_), dest, size) => write!(f, "ori{}\t{}, {}", size, src, dest),
Instruction::OR(src, dest, size) => write!(f, "or{}\t{}, {}", size, src, dest),
Instruction::ORtoCCR(value) => write!(f, "orb\t{:02x}, %ccr", value),
Instruction::ORtoSR(value) => write!(f, "orw\t{:04x}, %sr", value),
Instruction::ORtoCCR(value) => write!(f, "orib\t#{:#02x}, %ccr", value),
Instruction::ORtoSR(value) => write!(f, "oriw\t#{:#04x}, %sr", value),
Instruction::PEA(target) => write!(f, "pea\t{}", target),
@ -502,7 +506,8 @@ impl fmt::Display for Instruction {
Instruction::SBCD(src, dest) => write!(f, "sbcd\t{}, {}", src, dest),
Instruction::Scc(cond, target) => write!(f, "s{}\t{}", cond, target),
Instruction::STOP(value) => write!(f, "stop\t#{:04x}", value),
Instruction::STOP(value) => write!(f, "stop\t#{:#04x}", value),
Instruction::SUB(src @ Target::Immediate(_), dest, size) => write!(f, "subi{}\t{}, {}", size, src, dest),
Instruction::SUB(src, dest, size) => write!(f, "sub{}\t{}, {}", size, src, dest),
Instruction::SUBA(target, reg, size) => write!(f, "suba{}\t{}, %a{}", size, target, reg),
Instruction::SUBX(src, dest, size) => write!(f, "subx{}\t{}, {}", size, src, dest),

View File

@ -1,4 +1,5 @@
pub mod assembler;
pub mod state;
pub mod decode;
pub mod execute;

View File

@ -10,6 +10,7 @@ mod decode_tests {
use crate::cpus::m68k::state::Exceptions;
use crate::cpus::m68k::instructions::{Instruction, Target, Size, Sign, XRegister, BaseRegister, IndexRegister, Direction, ShiftDirection};
use crate::cpus::m68k::timing::M68kInstructionTiming;
use crate::cpus::m68k::assembler::M68kAssembler;
const INIT_STACK: Address = 0x00002000;
const INIT_ADDR: Address = 0x00000010;
@ -23,7 +24,8 @@ mod decode_tests {
const DECODE_TESTS: &'static [TestCase] = &[
// MC68000
TestCase { cpu: M68kType::MC68000, data: &[0x4e71], ins: Some(Instruction::NOP) },
TestCase { cpu: M68kType::MC68000, data: &[0x0008, 0x00FF], ins: Some(Instruction::OR(Target::Immediate(0xFF), Target::DirectAReg(0), Size::Byte)) },
// TODO I think this one is illegal (which is causing problems for the assembler)
//TestCase { cpu: M68kType::MC68000, data: &[0x0008, 0x00FF], ins: Some(Instruction::OR(Target::Immediate(0xFF), Target::DirectAReg(0), Size::Byte)) },
TestCase { cpu: M68kType::MC68000, data: &[0x003C, 0x00FF], ins: Some(Instruction::ORtoCCR(0xFF)) },
TestCase { cpu: M68kType::MC68000, data: &[0x007C, 0x1234], ins: Some(Instruction::ORtoSR(0x1234)) },
TestCase { cpu: M68kType::MC68000, data: &[0x0263, 0x1234], ins: Some(Instruction::AND(Target::Immediate(0x1234), Target::IndirectARegDec(3), Size::Word)) },
@ -124,6 +126,77 @@ mod decode_tests {
}
}
#[test]
pub fn run_assembler_tests() {
let mut tests = 0;
let mut errors = 0;
for case in DECODE_TESTS {
if case.ins.is_some() {
tests += 1;
let assembly_text = format!("{}", case.ins.as_ref().unwrap());
print!("Testing assembling of {:?} ", assembly_text);
let mut assembler = M68kAssembler::new(M68kType::MC68000);
match assembler.assemble_words(&assembly_text) {
Ok(data) => {
if data == case.data {
print!("pass");
} else {
errors += 1;
print!("FAILED");
print!("\nleft: {:?}, right: {:?}", data, case.data);
}
println!("");
},
Err(err) => {
println!("FAILED\n{:?}", err);
errors += 1;
},
}
}
}
if errors > 0 {
panic!("{} errors out of {} tests", errors, tests);
}
}
#[test]
pub fn run_assembler_opcode_tests() {
let mut tests = 0;
let mut errors = 0;
//use super::super::testcases::{TimingCase, TIMING_TESTS};
for case in TIMING_TESTS {
tests += 1;
let assembly_text = format!("{}", case.ins);
print!("Testing assembling of {:?} ", assembly_text);
let mut assembler = M68kAssembler::new(M68kType::MC68000);
match assembler.assemble_words(&assembly_text) {
Ok(data) => {
if data[0] == case.data[0] {
print!("pass");
} else {
errors += 1;
print!("FAILED");
print!("\nleft: {:#06x}, right: {:#06x}", data[0], case.data[0]);
}
println!("");
},
Err(err) => {
println!("FAILED\n{:?}", err);
errors += 1;
},
}
}
if errors > 0 {
panic!("{} errors out of {} tests", errors, tests);
}
}
//use super::super::testcases::{TimingCase, TIMING_TESTS};
struct TimingCase {
@ -527,6 +600,41 @@ mod execute_tests {
}
}
#[test]
pub fn run_assembler_tests() {
use crate::cpus::m68k::assembler::M68kAssembler;
let mut tests = 0;
let mut errors = 0;
for case in TEST_CASES {
tests += 1;
let assembly_text = format!("{}", case.ins);
print!("Testing assembling of {:?} ", assembly_text);
let mut assembler = M68kAssembler::new(M68kType::MC68000);
match assembler.assemble_words(&assembly_text) {
Ok(data) => {
if data == case.data {
print!("pass");
} else {
errors += 1;
print!("FAILED");
print!("\nleft: {:?}, right: {:?}", data, case.data);
}
println!("");
},
Err(err) => {
println!("FAILED\n{:?}", err);
errors += 1;
},
}
}
if errors > 0 {
panic!("{} errors out of {} tests", errors, tests);
}
}
const TEST_CASES: &'static [TestCase] = &[
TestCase {

View File

@ -8,6 +8,7 @@ pub mod devices;
pub mod debugger;
pub mod interrupts;
pub mod system;
pub mod parser;
pub mod host;
pub mod cpus;

265
src/parser.rs Normal file
View File

@ -0,0 +1,265 @@
use std::str::Chars;
use std::iter::Peekable;
use crate::error::Error;
#[derive(Debug)]
pub enum AssemblyLine {
Directive(String, Vec<String>),
Label(String),
Instruction(String, Vec<AssemblyOperand>),
}
#[derive(Debug)]
pub enum AssemblyOperand {
Register(String),
Indirect(Vec<AssemblyOperand>),
IndirectPre(String, Vec<AssemblyOperand>),
IndirectPost(Vec<AssemblyOperand>, String),
Immediate(usize),
Label(String),
}
pub struct AssemblyParser<'input> {
lexer: AssemblyLexer<'input>,
}
impl<'input> AssemblyParser<'input> {
pub fn new(lineno: usize, input: &'input str) -> Self {
Self {
lexer: AssemblyLexer::new(lineno, input),
}
}
pub fn parse_line(&mut self) -> Result<Option<AssemblyLine>, Error> {
let token = match self.lexer.get_next() {
Some(token) => token,
None => return Ok(None),
};
let result = match token.as_str() {
"." => {
let name = self.lexer.expect_next()?;
let list = self.parse_list_of_words()?;
AssemblyLine::Directive(name, list)
},
word if word.chars().nth(0).map(|ch| is_word(ch)).unwrap_or(false) => {
let next = self.lexer.peek();
if next.is_some() && next.as_ref().unwrap() == ":" {
self.lexer.expect_next()?;
AssemblyLine::Label(token)
} else {
let list = self.parse_list_of_operands()?;
AssemblyLine::Instruction(token, list)
}
},
_ => {
return Err(Error::new(&format!("parse error at line {}: expected word, found {:?}", self.lexer.lineno(), token)));
},
};
self.lexer.expect_end()?;
Ok(Some(result))
}
fn parse_list_of_words(&mut self) -> Result<Vec<String>, Error> {
let mut list = vec![];
loop {
list.push(self.lexer.expect_next()?);
let next = self.lexer.peek();
if next.is_none() || next.unwrap() != "," {
return Ok(list);
}
}
}
fn parse_list_of_operands(&mut self) -> Result<Vec<AssemblyOperand>, Error> {
let mut list = vec![];
// If we're already at the end of the line, then it's an empty list, so return
let next = self.lexer.peek();
if next.is_none() {
return Ok(list);
}
loop {
list.push(self.parse_operand()?);
let next = self.lexer.peek();
if next.is_none() || next.unwrap() != "," {
return Ok(list);
}
self.lexer.expect_next()?;
}
}
fn parse_operand(&mut self) -> Result<AssemblyOperand, Error> {
let token = self.lexer.expect_next()?;
match token.as_str() {
"%" => {
// TODO check for movem type ranges
Ok(AssemblyOperand::Register(self.lexer.expect_next()?))
},
"(" => {
let list = self.parse_list_of_operands()?;
self.lexer.expect_token(")")?;
if let Some(next) = self.lexer.peek() {
if next == "+" || next == "-" {
self.lexer.expect_next()?;
return Ok(AssemblyOperand::IndirectPost(list, next));
}
}
Ok(AssemblyOperand::Indirect(list))
},
"+" | "-" => {
self.lexer.expect_token("(")?;
let list = self.parse_list_of_operands()?;
self.lexer.expect_token(")")?;
Ok(AssemblyOperand::IndirectPre(token, list))
},
"#" => {
let string = self.lexer.expect_next()?;
let number = parse_any_number(self.lexer.lineno(), &string)?;
Ok(AssemblyOperand::Immediate(number))
},
_ => {
if is_digit(token.chars().nth(0).unwrap()) {
let number = parse_any_number(self.lexer.lineno(), &token)?;
Ok(AssemblyOperand::Immediate(number))
} else {
Ok(AssemblyOperand::Label(token))
}
}
}
}
}
fn parse_any_number(lineno: usize, string: &str) -> Result<usize, Error> {
let (radix, numeric) = if string.starts_with("0x") {
(16, &string[2..])
} else if string.starts_with("0b") {
(2, &string[2..])
} else if string.starts_with("0o") {
(8, &string[2..])
} else {
(10, string)
};
usize::from_str_radix(numeric, radix)
.map_err(|_| Error::new(&format!("parse error at line {}: expected number after #, but found {:?}", lineno, string)))
}
pub struct AssemblyLexer<'input> {
lineno: usize,
chars: Peekable<Chars<'input>>,
peeked: Option<String>,
}
impl<'input> AssemblyLexer<'input> {
pub fn new(lineno: usize, input: &'input str) -> Self {
Self {
lineno,
chars: input.chars().peekable(),
peeked: None,
}
}
pub fn lineno(&self) -> usize {
self.lineno
}
pub fn get_next(&mut self) -> Option<String> {
if self.peeked.is_some() {
let result = std::mem::replace(&mut self.peeked, None);
return result;
}
self.eat_whitespace();
let ch = self.chars.next()?;
let mut string = ch.to_string();
if is_word(ch) {
while let Some(ch) = self.chars.next_if(|ch| is_word(*ch) || *ch == '.') {
// Ignore periods in words
if ch != '.' {
string.push(ch);
}
}
}
Some(string)
}
pub fn peek(&mut self) -> Option<String> {
self.peeked = self.get_next();
self.peeked.clone()
}
pub fn expect_next(&mut self) -> Result<String, Error> {
self.get_next().ok_or_else(|| Error::new(&format!("unexpected end of input at line {}", self.lineno)))
}
pub fn expect_token(&mut self, expected: &str) -> Result<(), Error> {
let token = self.expect_next()?;
if token == expected {
Ok(())
} else {
Err(Error::new(&format!("parse error at line {}: expected {:?}, found {:?}", self.lineno, expected, token)))
}
}
pub fn expect_end(&mut self) -> Result<(), Error> {
if let Some(token) = self.get_next() {
Err(Error::new(&format!("expected end of line at {}: found {:?}", self.lineno, token)))
} else {
Ok(())
}
}
fn eat_whitespace(&mut self) {
while self.chars.next_if(|ch| is_whitespace(*ch)).is_some() { }
}
}
fn is_whitespace(ch: char) -> bool {
ch == ' ' || ch == '\n' || ch == '\t'
}
fn is_word(ch: char) -> bool {
('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ('0'..='9').contains(&ch) || (ch == '_')
}
fn is_digit(ch: char) -> bool {
('0'..='9').contains(&ch)
}
pub fn expect_args(lineno: usize, args: &[AssemblyOperand], expected: usize) -> Result<(), Error> {
if args.len() == expected {
Ok(())
} else {
Err(Error::new(&format!("error at line {}: expected {} args, but found {}", lineno, expected, args.len())))
}
}
pub fn expect_label(lineno: usize, args: &[AssemblyOperand]) -> Result<String, Error> {
expect_args(lineno, args, 1)?;
if let AssemblyOperand::Label(name) = &args[0] {
Ok(name.clone())
} else {
Err(Error::new(&format!("error at line {}: expected a label, but found {:?}", lineno, args)))
}
}
pub fn expect_immediate(lineno: usize, operand: &AssemblyOperand) -> Result<usize, Error> {
if let AssemblyOperand::Immediate(value) = operand {
Ok(*value)
} else {
Err(Error::new(&format!("error at line {}: expected an immediate value, but found {:?}", lineno, operand)))
}
}