summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2022-01-21 03:32:25 -0800
committeriximeow <me@iximeow.net>2022-01-21 03:32:25 -0800
commit86510eeed5e69c57f3677d4970e22d4f717e3284 (patch)
tree3c315793e6867237d43bdf2ea1219922aa58213a
err, guess this exists now too
-rw-r--r--.gitignore2
-rw-r--r--CHANGELOG3
-rw-r--r--Cargo.toml13
-rw-r--r--LICENSE12
-rw-r--r--README.md23
-rw-r--r--src/display.rs453
-rw-r--r--src/lib.rs1123
-rw-r--r--tests/test.rs53
8 files changed, 1682 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2c96eb1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+target/
+Cargo.lock
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..92c531a
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,3 @@
+# 1.0.0
+
+* first release, decoder exists
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0123aa7
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+
+name = "yaxpeax-nd812"
+version = "1.0.0"
+authors = [ "iximeow <me@iximeow.net>" ]
+license = "0BSD"
+repository = "http://git.iximeow.net/yaxpeax-nd812/"
+description = "nd812 decoder for the yaxpeax project"
+keywords = ["nd812", "microcomputer", "disassembler"]
+edition = "2018"
+
+[dependencies]
+yaxpeax-arch = { version = "0.2.4", default-features = false, features = [] }
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7091f53
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,12 @@
+Copyright (c) 2020 iximeow
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..14cd328
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+## yaxpeax-nd812
+
+[![crate](https://img.shields.io/crates/v/yaxpeax-nd812.svg?logo=rust)](https://crates.io/crates/yaxpeax-nd812)
+[![documentation](https://docs.rs/yaxpeax-nd812/badge.svg)](https://docs.rs/yaxpeax-nd812)
+
+an `ND812` decoder implemented as part of the yaxpeax project, including traits provided by [`yaxpeax-arch`](https://git.iximeow.net/yaxpeax-arch/about/).
+
+the `ND812` is a 12-bit microcomputer intended for scientific computing purposes, from Nuclear Data, Inc. several other related systems, such as the `ND4410`, require an `ND812` microcomputer for operation - some program listings to operate those additional pieces of hardware are executed on the attached `ND812`. the `ND812` itself was first sold in 1970.
+
+users of this library will either want to use [quick and dirty APIs](https://docs.rs/yaxpeax-nd812/latest/yaxpeax_nd812/index.html#usage), or more generic decode interfaces from `yaxpeax-arch` - appropriate when mixing `yaxpeax-nd812` with other `yaxpeax` decoders, such as `yaxpeax-x86`.
+
+### features
+
+* it exists
+* `#[no_std]`
+
+### it exists
+
+there aren't many ND812 programs, and fewer ND812 simulators. presumably, someone wanting to simulate an ND812 would need to interpret its instructions. all ND812 programs i've found are text listings and an interpreter could easily be written to interpret the 12-bit-octal-words-as-text directly.. but, well, a binary decoder exists now.
+
+### `#[no_std]`
+
+if, for some reason, you want to disassemble `ND812` instructions without the Rust standard library around, that should work. this is primarily for consistency with other decoders than any need, and is not particularly tested.
diff --git a/src/display.rs b/src/display.rs
new file mode 100644
index 0000000..0dc4c06
--- /dev/null
+++ b/src/display.rs
@@ -0,0 +1,453 @@
+use core::fmt;
+
+use crate::{Opcode, Operand, Instruction};
+
+impl fmt::Display for Instruction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.opcode {
+ Opcode::And => {
+ return write!(f, "ad{} {}", self.operands[0], self.operands[1]);
+ },
+ Opcode::Load => {
+ return write!(f, "l{}f{}", self.operands[0], self.operands[1]);
+ },
+ Opcode::Exchange => {
+ return write!(f, "ex{}{}", self.operands[0], self.operands[1]);
+ },
+ Opcode::AddJK => {
+ return write!(f, "ajk {}", self.operands[0]);
+ },
+ Opcode::SubJK => {
+ return write!(f, "sjk {}", self.operands[0]);
+ },
+ Opcode::AddR => {
+ return write!(f, "ad{} {}", self.operands[0], self.operands[1]);
+ },
+ Opcode::SubR => {
+ return write!(f, "sb{} {}", self.operands[0], self.operands[1]);
+ },
+ Opcode::NegAddJK => {
+ return write!(f, "najk {}", self.operands[0]);
+ },
+ Opcode::NegSubJK => {
+ return write!(f, "nsjk {}", self.operands[0]);
+ },
+ Opcode::NegAddR => {
+ return write!(f, "nad{} {}", self.operands[0], self.operands[1]);
+ },
+ Opcode::NegSubR => {
+ return write!(f, "nsb{} {}", self.operands[0], self.operands[1]);
+ },
+ Opcode::Shift => {
+ return write!(f, "sftz {}, {}", self.operands[0], self.operands[1]);
+ },
+ Opcode::Rotate => {
+ return write!(f, "rotd {}, {}", self.operands[0], self.operands[1]);
+ },
+ _ => {}
+ }
+
+ write!(f, "{}", self.opcode)?;
+ let mut first_separate_op = 0;
+ if [Opcode::TWSM, Opcode::TWSB, Opcode::TWAD, Opcode::TWLD, Opcode::TWST].contains(&self.opcode) {
+ write!(f, "{}", self.operands[0])?;
+ first_separate_op += 1;
+ }
+
+ for i in 0..(self.operand_count() as usize){
+ if let Operand::Absolute(true, _) | Operand::Displacement(true, _) = self.operands[i] {
+ write!(f, "@")?;
+ break;
+ }
+ }
+
+ if let Some(field) = self.referenced_field.as_ref() {
+ write!(f, " field={}", field.num())?;
+ }
+ for i in first_separate_op..self.operand_count() {
+ f.write_str(" ")?;
+ format_operand(f, &self.operands[i as usize])?;
+ if i + 1 < self.operand_count() {
+ f.write_str(",")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl fmt::Debug for Operand {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ <Operand as fmt::Display>::fmt(self, f)
+ }
+}
+
+impl fmt::Display for Operand {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use crate::Operand::*;
+ match self {
+ Nothing => f.write_str("BUG"),
+ J => f.write_str("j"),
+ K => f.write_str("k"),
+ JK => f.write_str("jk"),
+ R => f.write_str("r"),
+ S => f.write_str("s"),
+ RS => f.write_str("rs"),
+ OverflowBit => f.write_str("o"),
+ FlagBit => f.write_str("f"),
+ Displacement(indirect, offset) => {
+ if *indirect {
+ f.write_str("(indirect) ")?;
+ }
+ if *offset < 0 {
+ write!(f, "$-{:#02x}", -offset)
+ } else {
+ write!(f, "$+{:#02x}", offset)
+ }
+ },
+ Absolute(indirect, addr) => {
+ if *indirect {
+ f.write_str("(indirect) ")?;
+ }
+ write!(f, "0o{:04o}", addr)
+ }
+ Literal(value) => {
+ write!(f, "${:03x}", value)
+ },
+ }
+ }
+}
+
+// mostly the same as the `Display` impl, but does not print `indirect`, since that's supposed to
+// be handled by the mnemonic part of the instruction.
+fn format_operand(f: &mut fmt::Formatter, op: &Operand) -> fmt::Result {
+ use crate::Operand::*;
+ match op {
+ Nothing => f.write_str(""),
+ J => f.write_str("j"),
+ K => f.write_str("k"),
+ JK => f.write_str("jk"),
+ R => f.write_str("r"),
+ S => f.write_str("s"),
+ RS => f.write_str("rs"),
+ OverflowBit => f.write_str("o"),
+ FlagBit => f.write_str("f"),
+ Displacement(_, offset) => {
+ if *offset < 0 {
+ write!(f, "$-{:#02x}", -offset)
+ } else {
+ write!(f, "$+{:#02x}", offset)
+ }
+ },
+ Absolute(_, addr) => {
+ write!(f, "0o{:04o}", addr)
+ }
+ Literal(value) => {
+ write!(f, "${:03x}", value)
+ },
+ }
+
+}
+
+impl fmt::Debug for Opcode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ <Opcode as fmt::Display>::fmt(self, f)
+ }
+}
+
+impl fmt::Display for Opcode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Opcode::STOP => {
+ f.write_str("stop")
+ },
+ Opcode::IDLE => {
+ f.write_str("idle")
+ },
+ Opcode::CHSF => {
+ f.write_str("chsf")
+ },
+ Opcode::CSPF => {
+ f.write_str("cspf")
+ },
+ Opcode::CSFM => {
+ f.write_str("csfm")
+ },
+ Opcode::CSET => {
+ f.write_str("cset")
+ },
+ Opcode::CHSR => {
+ f.write_str("chsr")
+ },
+ Opcode::CSNE => {
+ f.write_str("csne")
+ },
+ Opcode::CSTR => {
+ f.write_str("cstr")
+ },
+ Opcode::CSBT => {
+ f.write_str("csbt")
+ },
+ Opcode::CCLF => {
+ f.write_str("cclf")
+ },
+ Opcode::CSRR => {
+ f.write_str("csrr")
+ },
+ Opcode::CRDT => {
+ f.write_str("crdt")
+ },
+ Opcode::CWFM => {
+ f.write_str("cwfm")
+ },
+ Opcode::CSWR => {
+ f.write_str("cswr")
+ },
+ Opcode::CWRT => {
+ f.write_str("cwrt")
+ },
+ Opcode::TWSM => {
+ f.write_str("twsm")
+ },
+ Opcode::TWDSZ => {
+ f.write_str("twdsz")
+ },
+ Opcode::TWISZ => {
+ f.write_str("twisz")
+ },
+ Opcode::TWSB => {
+ f.write_str("twsb")
+ },
+ Opcode::TWAD => {
+ f.write_str("twad")
+ },
+ Opcode::TWLD => {
+ f.write_str("twld")
+ },
+ Opcode::TWST => {
+ f.write_str("twst")
+ },
+ Opcode::TWJMP => {
+ f.write_str("twjmp")
+ },
+ Opcode::TWJPS => {
+ f.write_str("twjps")
+ },
+ Opcode::MPY => {
+ f.write_str("mpy")
+ },
+ Opcode::DIV => {
+ f.write_str("div")
+ },
+ Opcode::RFOV => {
+ f.write_str("rfov")
+ },
+ Opcode::IOFF => {
+ f.write_str("ioff")
+ },
+ Opcode::IONH => {
+ f.write_str("ionh")
+ },
+ Opcode::IONA => {
+ f.write_str("iona")
+ },
+ Opcode::IONB => {
+ f.write_str("ionb")
+ },
+ Opcode::IONN => {
+ f.write_str("ionn")
+ },
+ Opcode::LJSW => {
+ f.write_str("ljsw")
+ },
+ Opcode::LJST => {
+ f.write_str("ljst")
+ },
+ Opcode::And => {
+ f.write_str("and")
+ },
+ Opcode::Load => {
+ f.write_str("load")
+ }, // load op[0] from [1]
+ Opcode::Exchange => {
+ f.write_str("exchange")
+ }, // exchange op[0], op[1]
+ Opcode::AddJK => {
+ f.write_str("addjk")
+ }, // `op[0] + op[1] to op[2]`
+ Opcode::SubJK => {
+ f.write_str("subjk")
+ }, // `op[0] - op[1] to op[2]`
+ Opcode::AddR => {
+ f.write_str("addr")
+ }, // `op[0] + op[1] to op[2]`
+ Opcode::SubR => {
+ f.write_str("subr")
+ }, // `op[0] - op[1] to op[2]`
+ Opcode::NegAddJK => {
+ f.write_str("negaddjk")
+ }, // `-(op[0] + op[1]) to op[2]`
+ Opcode::NegSubJK => {
+ f.write_str("negsubjk")
+ }, // `(op[1] - op[0]) to op[2]`
+ Opcode::NegAddR => {
+ f.write_str("negaddr")
+ }, // `-(op[0] + op[1]) to op[2]`
+ Opcode::NegSubR => {
+ f.write_str("negsubr")
+ }, // `(op[1] - op[0]) to op[2]`
+ Opcode::Shift => {
+ f.write_str("shift")
+ }, // `j <<= N`, what is N?
+ Opcode::Rotate => {
+ f.write_str("rotate")
+ }, // `j <<= N`, what is N?
+ Opcode::SNZ => {
+ f.write_str("snz")
+ },
+ Opcode::SIZ => {
+ f.write_str("siz")
+ },
+ Opcode::CLR => {
+ f.write_str("clr")
+ },
+ Opcode::CMP => {
+ f.write_str("cmp")
+ },
+ Opcode::SET => {
+ f.write_str("set")
+ },
+ Opcode::SKPL => {
+ f.write_str("skpl")
+ },
+ Opcode::PION => {
+ f.write_str("pion")
+ },
+ Opcode::PIOF => {
+ f.write_str("piof")
+ },
+ Opcode::SIP => {
+ f.write_str("sip")
+ },
+ Opcode::INC => {
+ f.write_str("inc")
+ },
+ Opcode::SIN => {
+ f.write_str("sin")
+ },
+ Opcode::NEG => {
+ f.write_str("neg")
+ },
+ Opcode::ANDF => {
+ f.write_str("andf")
+ },
+ Opcode::ANDL => {
+ f.write_str("andl")
+ },
+ Opcode::ADDL => {
+ f.write_str("addl")
+ },
+ Opcode::SUBL => {
+ f.write_str("subl")
+ },
+ Opcode::SMJ => {
+ f.write_str("smj")
+ },
+ Opcode::DSZ => {
+ f.write_str("dsz")
+ },
+ Opcode::ISZ => {
+ f.write_str("isz")
+ },
+ Opcode::SBJ => {
+ f.write_str("sbj")
+ },
+ Opcode::ADJ => {
+ f.write_str("adj")
+ },
+ Opcode::LDJ => {
+ f.write_str("ldj")
+ },
+ Opcode::STJ => {
+ f.write_str("stj")
+ },
+ Opcode::JMP => {
+ f.write_str("jmp")
+ },
+ Opcode::SKIP => {
+ f.write_str("skip")
+ },
+ Opcode::JPS => {
+ f.write_str("jps")
+ },
+ Opcode::XCT => {
+ f.write_str("xct")
+ },
+ Opcode::TIF => {
+ f.write_str("tif")
+ },
+ Opcode::TIR => {
+ f.write_str("tir")
+ },
+ Opcode::TRF => {
+ f.write_str("trf")
+ },
+ Opcode::TIS => {
+ f.write_str("tis")
+ },
+ Opcode::TOC => {
+ f.write_str("toc")
+ },
+ Opcode::TOP => {
+ f.write_str("top")
+ },
+ Opcode::TCP => {
+ f.write_str("tcp")
+ },
+ Opcode::TOS => {
+ f.write_str("tos")
+ },
+ Opcode::HIF => {
+ f.write_str("hif")
+ },
+ Opcode::HIR => {
+ f.write_str("hir")
+ },
+ Opcode::HRF => {
+ f.write_str("hrf")
+ },
+ Opcode::HIS => {
+ f.write_str("his")
+ },
+ Opcode::HOP => {
+ f.write_str("hop")
+ },
+ Opcode::HOL => {
+ f.write_str("hol")
+ },
+ Opcode::HLP => {
+ f.write_str("hlp")
+ },
+ Opcode::HOS => {
+ f.write_str("hos")
+ },
+ Opcode::CSLCT1 => {
+ f.write_str("cslct1")
+ },
+ Opcode::CSLCT2 => {
+ f.write_str("cslct2")
+ },
+ Opcode::CSLCT3 => {
+ f.write_str("cslct3")
+ },
+ Opcode::LDREG => {
+ f.write_str("ldreg")
+ },
+ Opcode::LDJK => {
+ f.write_str("ldjk")
+ },
+ Opcode::RJIB => {
+ f.write_str("rjib")
+ },
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..39cab9a
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,1123 @@
+//! # `yaxpeax-nd812`, a decoder for the ND812 instruction set
+//!
+//! the ND812 instruction set is used in the Nuclear Data ND812 microcomputer, first introduced in
+//! 1970. the ND812 and associated additional hardware (such as the ND4410) were used for
+//! scientific applications and it seems relatively few programs for this equipment has survived to
+//! the present day.
+//!
+//! interesting for yaxpeax reasons, the ND812 is a 12-bit machine. `yaxpeax-nd812` decodes units
+//! of [`ND812Word`], consulting only the low 12 bits of the contained `u16`. `ND812Word` then
+//! requires an `impl Reader<u16, ND812Word>` to decode from; there is a default impl to read
+//! `ND812Word` from a regular `&[u8]`, but a more comprehensive `ND812` simulator would need to
+//! also reproduce the wrap-at-4k-boundary behavior from the real hardware.
+//!
+//! the actual packing of 12-bit words may also be of interest. i couldn't find any `ND812`
+//! programs online as binary, even as binary images to be loaded by simulators - i couldn't really
+//! find any `ND812` simulators available online either. so my best guess for a reasonable binary
+//! format is to do what people do with PDP-8 (also 12-bit) binary blobs, with 12-bit words in
+//! two-byte units of memory.
+//!
+//! lastly, thank goodness for `bitsavers.org`. i found many of the manuals and documents for the
+//! `ND812` in scattered places online, but bitsavers has them all in one place. the reference
+//! there helped me answer a few questions about missing documents, and led me to finding program
+//! `ND41-1085` in the `IM41-1085` manual for x-ray analysis. it turned out that the best test
+//! cases were reference programs from Nuclear Data themselves.
+//!
+//! reference materials:
+//! ```text
+//! shasum -a 256 IM*
+//! 3a4ccbdd898ff071636d14af908e2386d6204b77d39a546f416f40ad7ad890fa
+//! IM41-0001_Software_Instruction_Manual_BASC-12_General_Assembler_Jan71.pdf
+//! 39dcb814862c385986aee6ff31b4f0a942e9ff8dcaac0046cbcba55f052d42e5
+//! IM41-0002-04_Software_Instruction_Manual_ND812_Symbolic_Text_Editor_Nov72.pdf
+//! d5380bed1407566b491d00e654bc7967208fa71ef6daa7ec82e73805f671ff0a
+//! IM41-0059-00_NUTRAN_User_and_Programmers_Guide_Nov72.pdf
+//! f508a4bb6a834352b1a391ac0dd851201dd6a6a5cfa6eec53aa4c6dbf86e088a
+//! IM41-1062-00_Software_Instruction_Manual_ND4410_Low_High_Speed_Paper_Tape_IO_Overlay_Program_Apr72.pdf
+//! a1364c23ffadc4414c7b905cfce7cd4c0914a5b0d29b1726246a9d5d68d0aa7a
+//! IM41-8001-01_Software_Instruction_Manual_ND812_Diagnostics_Feb72.pdf
+//! 62013481aab174473ae1cbaed35d02eb7f22a05acd6c56ae36d166502925cb25
+//! IM41-8045-00_Software_Instruction_Manual_Hardware_Multipy_Divide_Test_Jun72.pdf
+//! 3cf00d268cab96eebda973b53b870fe761e83d2e61a733860094920b17d84b22
+//! IM88-0481-02_Hardware_Instruction_Manual_ND812_Teletype_Auto_Loader_Interface_Sep72.pdf
+//! ```
+//!
+//! ## usage
+//!
+//! the fastest way to decode an nd812 instruction is through
+//! [`InstDecoder::decode_slice()`]:
+//! ```
+//! use yaxpeax_nd812::InstDecoder;
+//!
+//! let inst = InstDecoder::decode_u16(&[0o1122]).unwrap();
+//!
+//! assert_eq!("adr j", inst.to_string());
+//! ```
+//!
+//! opcodes and operands are available on the decoded instruction, as well as its length and
+//! operand count:
+//! ```
+//! use yaxpeax_nd812::{InstDecoder, Operand, Opcode};
+//!
+//! let inst = InstDecoder::decode_u16(&[0o1123]).unwrap();
+//!
+//! assert_eq!("sbr j", inst.to_string());
+//! assert_eq!(inst.operand_count(), 2);
+//! assert_eq!(inst.len(), 1);
+//! assert_eq!(inst.opcode(), Opcode::SubR);
+//! assert_eq!(inst.operand(0), Operand::R);
+//! assert_eq!(inst.operand(1), Operand::J);
+//! ```
+//!
+//! additionally, `yaxpeax-nd812` implements `yaxpeax-arch` traits for generic use, such as
+//! [`yaxpeax_arch::LengthedInstruction`]. [`yaxpeax_arch::Arch`] is implemented by
+//! the unit struct [`ND812`].
+//!
+//! ## `#![no_std]`
+//!
+//! `yaxpeax-nd812` should support `no_std` usage, but this is entirely untested.
+
+#![no_std]
+
+mod display;
+
+use yaxpeax_arch::{AddressDiff, Arch, Decoder, LengthedInstruction, Reader, ReadError, U8Reader};
+
+/// a trivial struct for [`yaxpeax_arch::Arch`] to be implemented on. it's only interesting for the
+/// associated type parameters.
+#[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)]
+pub struct ND812;
+
+/// a 12-bit word, as used in the `nd812`.
+#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialOrd, PartialEq)]
+#[repr(transparent)]
+pub struct ND812Word(u16);
+
+impl ND812Word {
+ /// get the value of this 12-bit word as a u16
+ pub fn value(&self) -> u16 {
+ self.0
+ }
+
+ /// create an `ND812Word` from a value `v`
+ ///
+ /// returns `None` if `v` is out of range for an `ND812Word` (is larger than `0o7777`, or in
+ /// base 16, `0x0fff`)
+ pub fn new(v: u16) -> Option<Self> {
+ if v > 0o7777 {
+ None
+ } else {
+ Some(ND812Word(v))
+ }
+ }
+}
+
+impl core::fmt::Display for ND812Word {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl Arch for ND812 {
+ type Address = u16;
+ type Word = ND812Word;
+ type Instruction = Instruction;
+ type Decoder = InstDecoder;
+ type DecodeError = DecodeError;
+ type Operand = Operand;
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum DecodeError {
+ /// no input available but the instruction would require at least one more word to decode
+ ExhaustedInput,
+ /// the word(s) to decode this instruction do not map to a defined instruction
+ Undefined,
+}
+
+impl From<yaxpeax_arch::ReadError> for DecodeError {
+ fn from(_e: yaxpeax_arch::ReadError) -> Self {
+ DecodeError::ExhaustedInput
+ }
+}
+
+impl core::fmt::Display for DecodeError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ use yaxpeax_arch::DecodeError;
+ f.write_str(self.description())
+ }
+}
+
+impl yaxpeax_arch::DecodeError for DecodeError {
+ fn data_exhausted(&self) -> bool {
+ *self == DecodeError::ExhaustedInput
+ }
+ fn bad_opcode(&self) -> bool {
+ *self == DecodeError::Undefined
+ }
+ fn bad_operand(&self) -> bool {
+ *self == DecodeError::Undefined
+ }
+ fn description(&self) -> &'static str {
+ match self {
+ DecodeError::ExhaustedInput => "exhausted input",
+ DecodeError::Undefined => "undefined encoding",
+ }
+ }
+}
+
+/// a wrapper describing one of four (three optional) memory fields in an `nd812`. some `nd812`
+/// documentation refers to these as "stacks" of memory.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
+pub struct MemoryField {
+ value: u8
+}
+
+impl MemoryField {
+ fn new(which: u8) -> Self {
+ assert!(which < 4);
+
+ MemoryField { value: which }
+ }
+
+ /// get the bits to select this `field` as encoded in an `nd812` instruction. default field is
+ /// `0`, with possible alternate field `1`, `2`, and `3`. the `nd812` does not support more
+ /// than four total field, so this function will never return a value of 4 or above.
+ pub fn num(&self) -> u8 {
+ self.value
+ }
+}
+
+/// an `nd812` instruction.
+///
+/// `nd812` instructions have an [`Opcode`] and up to three [`Operand`]s. they are one or two
+/// `ND812Word` long.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Instruction {
+ /// the operation of this instruction.
+ opcode: Opcode,
+ operands: [Operand; 3],
+ /// the nd812 "field" this instruction references. if this is present, memory referenced will
+ /// be with respect to this 4096-word field of memory. a `field` of memory, to the `nd812`, is
+ /// a linear region of memory - this is also called a "stack" in some documentation. even so,
+ /// there is no "stack pointer" nor "growth" (upward or downward) notions.
+ ///
+ /// if there is a field selected, and the instruction is `jmp` or `jps`, this field will be
+ /// made default for instructions after this.
+ referenced_field: Option<MemoryField>,
+ length: u8,
+}
+
+impl Default for Instruction {
+ fn default() -> Instruction {
+ Instruction {
+ opcode: Opcode::STOP,
+ operands: [Operand::Nothing, Operand::Nothing, Operand::Nothing],
+ referenced_field: None,
+ length: 0,
+ }
+ }
+}
+
+impl Instruction {
+ fn reset_operands(&mut self) {
+ self.operands = [Operand::Nothing, Operand::Nothing, Operand::Nothing];
+ }
+
+ /// the length of this instruction, in terms of [`ND812Word`].
+ pub fn len(&self) -> u8 {
+ self.length
+ }
+
+ /// get the number of operands in this instruction.
+ ///
+ /// calls to `Instruction::operand` for indices between 0 and this value will return an operand
+ /// other than `Operand::Nothing`.
+ pub fn operand_count(&self) -> u8 {
+ if self.operands[0] == Operand::Nothing {
+ 0
+ } else if self.operands[1] == Operand::Nothing {
+ 1
+ } else {
+ 2
+ }
+ }
+
+ /// get the `Operand` at the provided index.
+ ///
+ /// indices above `3` will always yield `Operand::Nothing`.
+ pub fn operand(&self, idx: u8) -> Operand {
+ self.operands.get(idx as usize).map(|x| *x).unwrap_or(Operand::Nothing)
+ }
+
+ /// get the `Opcode` of this instruction.
+ pub fn opcode(&self) -> Opcode {
+ self.opcode
+ }
+}
+
+impl LengthedInstruction for Instruction {
+ type Unit = AddressDiff<<ND812 as Arch>::Address>;
+ fn min_size() -> Self::Unit {
+ AddressDiff::from_const(1)
+ }
+ fn len(&self) -> Self::Unit {
+ AddressDiff::from_const(self.length as u16)
+ }
+}
+
+impl yaxpeax_arch::Instruction for Instruction {
+ fn well_defined(&self) -> bool { true }
+}
+
+/// an operand for an `nd812` instruction.
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Operand {
+ /// no operand in this position.
+ ///
+ /// reaching this as a user of `yaxpeax_nd812` is almost certainly a bug. `Instruction::operand`
+ /// will return `None` rather than `Operand::Nothing`.
+ Nothing,
+ /// the `J` register
+ J,
+ /// the `K` register
+ K,
+ /// the `JK` register
+ JK,
+ /// the `R` register
+ R,
+ /// the `S` register
+ S,
+ /// the `RS` register
+ RS,
+ /// the `OverflowBit` register
+ OverflowBit,
+ /// the `FlagBit` register
+ FlagBit,
+ /// memory access to `pc` plus the given signed offset, in the range `[-64, 64]` (inclusive).
+ /// additionally, the displacement may be indirect; `pc + offset` may be used as a pointer.
+ Displacement(bool, i8),
+ /// memory access to the absolute u12 address, in the range `[0, 4095]` (inclusive).
+ /// additionally, the displacement may be indirect; `offset` may be used as a pointer.
+ ///
+ /// in practice, instructions with an `Absolute` operand also may have an alternate field
+ /// selected, so correct interpretation of this operand will need to consult the referenced
+ /// field as well.
+ Absolute(bool, u16),
+ /// a literal value encoded in an instruction (modern instruction sets would call this an
+ /// `immediate`)
+ Literal(u8),
+}
+
+/// an `nd812` instruction's operation.
+#[allow(non_camel_case_types)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Opcode {
+ /// `Stop Execution`
+ STOP,
+ /// `One cycle delay`
+ IDLE,
+ /// `Cassette High-Speed Forward EOT (TWIO)`
+ CHSF,
+ /// `Cassette Space Forward to File Mark (TWIO)`
+ CSPF,
+ /// `Cassette Skip on File Mark (TWIO)`
+ CSFM,
+ /// `Cassette Skip if EOT (TWIO)`
+ CSET,
+ /// `Cassette High-Speed Reverse BOT (TWIO)`
+ CHSR,
+ /// `Cassette Skip No-Error (TWIO)`
+ CSNE,
+ /// `Cassette Skip if On-Line Tape Ready (TWIO)`
+ CSTR,
+ /// `Cassette Skip if BOT (TWIO)`
+ CSBT,
+ /// `Cassette Clear All Flags (TWIO)`
+ CCLF,
+ /// `Cassette Skip if Read Ready (TWIO)`
+ CSRR,
+ /// `Cassette Read to J (TWIO)`
+ CRDT,
+ /// `Cassette Write File Mark (TWIO)`
+ CWFM,
+ /// `Cassette Skip if Write Ready (TWIO)`
+ CSWR,
+ /// `Cassette Write Transfer (TWIO)`
+ CWRT,
+ /// `Two Word Skip if Memory Not Equal`
+ ///
+ /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
+ /// `Operand::K`.
+ TWSM,
+ TWDSZ,
+ TWISZ,
+ /// `Two Word Subtract`
+ ///
+ /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
+ /// `Operand::K`.
+ TWSB,
+ /// `Two Word Add`
+ ///
+ /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
+ /// `Operand::K`.
+ TWAD,
+ /// `Two Word Load`
+ ///
+ /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
+ /// `Operand::K`.
+ TWLD,
+ /// `Two Word Store`
+ ///
+ /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
+ /// `Operand::K`.
+ TWST,
+ /// `Two Word Unconditional Jump`
+ TWJMP,
+ /// `Two Word Jump Subroutine`
+ TWJPS,
+ /// `Multiply J by K`
+ MPY,
+ /// `Divide J and K by R`
+ DIV,
+ /// `Read Flag, Overflow from J`
+ RFOV,
+ /// `Disable All Interrupt Levels`
+ IOFF,
+ /// `Enable Level H Only`
+ IONH,
+ /// `Enable Interrupt Levels H & A`
+ IONA,
+ /// `Enable Interrupt Levels H & B`
+ IONB,
+ /// `Enable All Interrupt Levels`
+ IONN,
+ /// `Load J from Switches`
+ LJSW,
+ /// `Load J from Status Register`
+ LJST,
+ /// `And J, K, into <op0>`
+ ///
+ /// `yaxpeax-nd812` records the destination as an operand, `Operand::J`, `Operand::K`, or
+ /// `Operand::JK`
+ And,
+ /// `Load <op0> into <op1>`
+ ///
+ /// `yaxpeax-nd812` records the source and destination both as operands. operands will be one
+ /// of the following pairs:
+ /// * `R, J`
+ /// * `J, R`
+ /// * `S, K`
+ /// * `K, S`
+ /// * `K, J`
+ /// * `RS, JK`
+ /// * `JK, RS`
+ Load, // load op[0] from [1]
+ /// `Exchange <op0> and <op1>`
+ ///
+ /// `yaxpeax-nd812` records the source and destination both as operands. operands will be one
+ /// of the following pairs:
+ /// * `J, R`
+ /// * `K, S`
+ /// * `JK, RS`
+ Exchange, // exchange op[0], op[1]
+ /// `J + K to <op0>`
+ ///
+ /// `yaxpeax-nd812` records the destination as an operand, `Operand::J`, `Operand::K`, or
+ /// `Operand::JK`
+ AddJK, // `op[0] + op[1] to op[2]`
+ /// `J - K to <op0>`
+ ///
+ /// `yaxpeax-nd812` records the destination as an operand, `Operand::J` or `Operand::K`
+ SubJK, // `op[0] - op[1] to op[2]`
+ /// `<op0> + <op1> to <op1>`
+ ///
+ /// `yaxpeax-nd812` records the source and destination both as operands:
+ /// * `op0` can be `Operand::R` or `Operand::S`
+ /// * `op1` can be `Operand::J` or `Operand::K`
+ AddR, // `op[0] + op[1] to op[2]`
+ /// `<op0> - <op1> to <op1>`
+ ///
+ /// `yaxpeax-nd812` records the source and destination both as operands:
+ /// * `op0` can be `Operand::R` or `Operand::S`
+ /// * `op1` can be `Operand::J` or `Operand::K`
+ SubR, // `op[0] - op[1] to op[2]`
+ /// `-(J + K) to <op0>`
+ ///
+ /// `yaxpeax-nd812` records the destination as an operand, `Operand::J`, `Operand::K`, or
+ /// `Operand::JK`
+ NegAddJK, // `-(op[0] + op[1]) to op[2]`
+ /// `K - J to <op0>`
+ ///
+ /// `yaxpeax-nd812` records the destination as an operand, `Operand::J` or `Operand::K`
+ NegSubJK, // `(op[1] - op[0]) to op[2]`
+ /// `-(<op0> + <op1>) to <op1>`
+ ///
+ /// `yaxpeax-nd812` records the source and destination both as operands:
+ /// * `op0` can be `Operand::R` or `Operand::S`
+ /// * `op1` can be `Operand::J` or `Operand::K`
+ NegAddR, // `-(op[0] + op[1]) to op[2]`
+ /// `<op1> - <op0> to <op1>`
+ ///
+ /// `yaxpeax-nd812` records the source and destination both as operands:
+ /// * `op0` can be `Operand::R` or `Operand::S`
+ /// * `op1` can be `Operand::J` or `Operand::K`
+ NegSubR, // `(op[1] - op[0]) to op[2]`
+ /// `Shift <op0> left N`
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::J`, `Operand::K`, or
+ /// `Operand::JK`.
+ Shift, // `j <<= N`, what is N?
+ /// `Rotate <op0> left N`
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::J`, `Operand::K`, or
+ /// `Operand::JK`.
+ Rotate, // `j <<= N`, what is N?
+ /// `Skip if Flag Register One`
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::Overflow`, or J, K, JK,
+ SNZ,
+ /// `Skip if Flag Register Zero`
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK,
+ SIZ,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK,
+ CLR,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK
+ CMP,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK
+ SET,
+ /// `Skip on Power Low`
+ SKPL,
+ /// `Powerfail System On`
+ PION,
+ /// `Powerfail System Off`
+ PIOF,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK
+ SIP,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK
+ INC,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK
+ SIN,
+ ///
+ /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
+ /// `Operand::OverflowBit`, or J, K, JK
+ NEG,
+ /// `AND with J, Forward`
+ ANDF,
+ /// `AND J Literal`
+ ANDL,
+ /// `ADD J Literal`
+ ADDL,
+ /// `SUBTRACT J Literal`
+ SUBL,
+ /// `Skip if J not Equal Memory`
+ SMJ,
+ /// `Decrement Memory and Skip`
+ DSZ,
+ /// `Increment Memory and Skip`
+ ISZ,
+ /// `Subtract from J`
+ SBJ,
+ /// `Add to J`
+ ADJ,
+ /// `Load J`
+ LDJ,
+ /// `Store J`
+ STJ,
+ /// `Unconditional Jump`
+ JMP,
+ /// `Unconditional Skip`
+ SKIP,
+ /// `Jump Subroutine`
+ JPS,
+ /// `Execute Displaced Instruction`
+ XCT,
+ /// `TTY Keyboard-Reader Fetch`
+ TIF,
+ /// `TTY Keyboard Into J`
+ TIR,
+ /// `TIR and TIF combined`
+ TRF,
+ /// `TTY Skip if Keyboard Ready`
+ TIS,
+ /// `Clear printer/punch flag`
+ TOC,
+ /// `Clear printer/punch flag, load printer/punch buffer from J and print/punch`
+ TOP,
+ /// `TOP and TOC combined`
+ TCP,
+ /// `TTY Skip if Printer-Punch Reader`
+ TOS,
+ /// `HS Reader - Fetch`
+ HIF,
+ /// `HS Reader - CLR Flag, Read Buffer`
+ HIR,
+ /// `HIR and HIF combined`
+ HRF,
+ /// `Skip if HS reader flag = 1`
+ HIS,
+ /// `HS Punch - Punch On`
+ HOP,
+ /// `HS Punch - CLR Flag, Load Buffer`
+ HOL,
+ /// `HS Punch - Load and Punch`
+ HLP,
+ /// `HS Punch - Skip if punch ready`
+ HOS,
+ /// `Cassette - Unit 1 On-Line`
+ CSLCT1,
+ /// `Cassette - Unit 2 On-Line`
+ CSLCT2,
+ /// `Cassette - Unit 3 On-Line`
+ CSLCT3,
+ /// `Load JPS Reg from J, INT Reg from K`
+ LDREG,
+ /// `Load JPS Reg to J, INT Reg to K`
+ LDJK,
+ /// `Restore JPS and INT field bits`
+ RJIB,
+}
+
+/// the ND812 uses a modified character set to pack two characters into 12-bit words; each
+/// character is *6* bits.
+///
+/// TODO: not yet sure how this maps to ascii; `Appendix C` describes `A` as `ASCII CODE 301` -
+/// doesn't match as octal or.. anything else. the whole character set is reproduced here, for
+/// reference. this all DOES line up with ascii if ND812 assumes ascii has the high bit set?
+pub const ND812_CHARSET: &[u8] = &[
+ b'A', b'B', b'C', b'D', b'E', b'F',
+ b'G', b'H', b'I', b'J', b'K', b'L',
+ b'M', b'N', b'O', b'P', b'Q', b'R',
+ b'S', b'T', b'U', b'V', b'W', b'X',
+ b'Y', b'Z', b'0', b'1', b'2', b'3',
+ b'4', b'5', b'6', b'7', b'8', b'9',
+ b'$', b'*', b'+', b'!', b'-', b'.',
+ b'/', b';', b'=', b' ', b'\t', b'\n',
+ 0x0c, b'\r', 0o377
+];
+
+/// an `nd812` instruction decoder.
+///
+/// there are no decode options for `nd812`, so this is a trivial struct that exists only for the
+/// [`yaxpeax_arch::Decoder`] trait impl.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct InstDecoder { }
+
+pub struct ND812Reader<T> {
+ underlying: T,
+ start: u16,
+ mark: u16,
+ offset: u16,
+}
+
+impl<'a> ND812Reader<&'a [u16]> {
+ pub fn of_u16(data: &'a [u16]) -> Self {
+ ND812Reader {
+ underlying: data,
+ start: 0,
+ mark: 0,
+ offset: 0,
+ }
+ }
+}
+
+impl<'a> Reader<u16, ND812Word> for ND812Reader<&'a [u16]> {
+ fn next(&mut self) -> Result<ND812Word, ReadError> {
+ if let Some(word) = self.underlying.get(self.offset as usize) {
+ if word & 0xf000 != 0 {
+ return Err(ReadError::IOError("invalid nd812 word in u16 data: bits in 12-15 are set"));
+ }
+
+ self.offset += 1;
+ Ok(ND812Word(word & 0o7777))
+ } else {
+ Err(ReadError::ExhaustedInput)
+ }
+ }
+
+ fn next_n(&mut self, buf: &mut [ND812Word]) -> Result<(), ReadError> {
+ if buf.len() > self.underlying.len() - self.offset as usize {
+ return Err(ReadError::ExhaustedInput);
+ }
+
+ // there's at least enough data, though some of it could be invalid...
+ // TODO: this will result in an error potentially consuming data without indicating how
+ // much data was consumed. not good!
+ for i in 0..buf.len() {
+ buf[i] = self.next()?;
+ }
+
+ Ok(())
+ }
+
+ fn mark(&mut self) {
+ self.mark = self.offset;
+ }
+
+ fn offset(&mut self) -> u16 {
+ self.offset - self.mark
+ }
+
+ fn total_offset(&mut self) -> u16 {
+ self.offset - self.start
+ }
+}
+
+impl<'a> ND812Reader<U8Reader<'a>> {
+ pub fn of_u8(data: &'a [u8]) -> Self {
+ ND812Reader {
+ underlying: U8Reader::new(data),
+ start: 0,
+ mark: 0,
+ offset: 0,
+ }
+ }
+}
+
+impl<'a> Reader<u16, ND812Word> for ND812Reader<U8Reader<'a>> {
+ fn next(&mut self) -> Result<ND812Word, ReadError> {
+ let high = Reader::<u16, u8>::next(&mut self.underlying)?;
+ let low = Reader::<u16, u8>::next(&mut self.underlying)?;
+ self.offset += 1;
+ Ok(ND812Word(u16::from_le_bytes([low, high])))
+ }
+
+ fn next_n(&mut self, buf: &mut [ND812Word]) -> Result<(), ReadError> {
+ // TODO: this will result in an error potentially consuming data without indicating how
+ // much data was consumed. not good!
+ for i in 0..buf.len() {
+ buf[i] = self.next()?;
+ }
+
+ Ok(())
+ }
+
+ fn mark(&mut self) {
+ Reader::<u16, u8>::mark(&mut self.underlying);
+ self.mark = self.offset;
+ }
+
+ fn offset(&mut self) -> u16 {
+ self.offset - self.mark
+ }
+
+ fn total_offset(&mut self) -> u16 {
+ self.offset - self.start
+ }
+}
+
+/*
+impl Reader<u16, ND812Word> for ND812Reader<U16Reader> {
+}
+*/
+
+impl InstDecoder {
+ /// decode a slice of bytes into an instruction (or error)
+ ///
+ /// this is just a higher-level interface to the [`InstDecoder`] impl of
+ /// [`yaxpeax_arch::Decoder`].
+ pub fn decode_slice(data: &[u8]) -> Result<Instruction, <ND812 as Arch>::DecodeError> {
+ InstDecoder::default()
+ .decode(&mut ND812Reader::of_u8(data))
+ }
+
+ /// decode a slice of `u16` into an instruction (or error)
+ ///
+ /// this is just a higher-level interface to the [`InstDecoder`] impl of
+ /// [`yaxpeax_arch::Decoder`].
+ pub fn decode_u16(data: &[u16]) -> Result<Instruction, <ND812 as Arch>::DecodeError> {
+ InstDecoder::default()
+ .decode(&mut ND812Reader::of_u16(data))
+ }
+}
+
+impl Default for InstDecoder {
+ fn default() -> Self {
+ InstDecoder { }
+ }
+}
+
+impl Decoder<ND812> for InstDecoder {
+ fn decode_into<T: Reader<<ND812 as Arch>::Address, <ND812 as Arch>::Word>>(&self, inst: &mut Instruction, words: &mut T) -> Result<(), <ND812 as Arch>::DecodeError> {
+ inst.length = 0;
+ inst.reset_operands();
+ words.mark();
+ let word = words.next()?;
+
+ let operation = word.0 >> 8;
+
+ if word.0 & 0o7700 == 0 {
+ inst.opcode = Opcode::STOP;
+ inst.length = words.offset() as u8;
+
+ return Ok(());
+ }
+
+ match operation {
+ 0b0000 |
+ 0b0001 => {
+ // two word instruction
+ let address = words.next()?;
+ let opc = (word.0 >> 5) & 0b0001111;
+ let field = word.0 & 0b11;
+ let change_field = (word.0 & 0b0100) != 0;
+ let kj = (word.0 & 0b1000) != 0;
+ let ind = (word.0 & 0b10000) != 0;
+
+ if word.0 < 0o0240 {
+ // unallocated
+ // cassette two-word i/o op
+ } else if word.0 == 0o0740 {
+ // TWIO (two word i/o)
+ let opc = address.0;
+
+ const OPC: &[Option<Opcode>] = &[
+ None, Some(Opcode::CHSF), Some(Opcode::CSPF), None, Some(Opcode::CSFM), None, None, None,
+ Some(Opcode::CSET), None, None, None, None, None, None, None,
+ None, Some(Opcode::CHSR), Some(Opcode::CSNE), None, Some(Opcode::CSTR), None, None, None,
+ Some(Opcode::CSBT), None, None, None, None, None, None, None,
+ None, Some(Opcode::CCLF), Some(Opcode::CSRR), None, Some(Opcode::CRDT), None, None, None,
+ None, Some(Opcode::CWFM), Some(Opcode::CSWR), None, Some(Opcode::CWRT), None, None, None,
+ ];
+
+ if opc < 0o100 {
+ return Err(DecodeError::Undefined);
+ }
+
+ let opc = opc - 0o0100;
+ inst.opcode = *OPC.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?;
+ } else {
+ let opc = opc - 5;
+ // starts at `0240`
+ const OPC: &[(Opcode, bool)] = &[
+ (Opcode::TWSM, true),
+ (Opcode::TWDSZ, false),
+ (Opcode::TWISZ, false),
+ (Opcode::TWSB, true),
+ (Opcode::TWAD, true),
+ (Opcode::TWLD, true),
+ (Opcode::TWST, true),
+ (Opcode::TWJMP, false),
+ (Opcode::TWJPS, false),
+ // nothing for 0700 - would be a two-word xct
+ ];
+
+ let (opcode, has_op) = *OPC.get(opc as usize).ok_or(DecodeError::Undefined)?;
+
+ if change_field {
+ inst.referenced_field = Some(MemoryField::new(field as u8));
+ }
+
+ inst.opcode = opcode;
+
+ if has_op {
+ inst.operands[0] = if kj {
+ Operand::K
+ } else {
+ Operand::J
+ };
+ inst.operands[1] = Operand::Absolute(ind, address.0);
+ } else {
+ inst.operands[0] = Operand::Absolute(ind, address.0);
+ }
+ }
+ },
+ 0b0010 => {
+ // group 1 instructions
+ // ```
+ // op1 = 0010
+ //
+ // | op1 | j | k | shift | shift count |
+ // | |acc|acc| rot | |
+ // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11|
+ // ```
+
+ let shift = (word.0 & 0o0017) as u8;
+ let opc = ((word.0 >> 4) & 0o0003) as u8;
+ let kj = ((word.0 >> 6) & 0o0003) as u8;
+
+ let dest = match kj {
+ 0b00 => {
+ // opcodes like `0o10xx`
+ // in practice the only instructions here are `0b1000` to `0b1011`
+ let low = (word.0 & 0o0077) as u8;
+ const OPC: &[Opcode] = &[
+ Opcode::MPY,
+ Opcode::DIV,
+ Opcode::RFOV,
+ Opcode::IOFF,
+ Opcode::IONH,
+ Opcode::IONB,
+ Opcode::IONA,
+ Opcode::IONN,
+ Opcode::LJSW,
+ Opcode::LJST,
+ ];
+ inst.opcode = *OPC.get(low as usize).ok_or(DecodeError::Undefined)?;
+ inst.length = words.offset() as u8;
+
+ return Ok(());
+ },
+ 0b01 => Operand::J,
+ 0b10 => Operand::K,
+ _ => Operand::JK,
+ };
+
+ match opc {
+ 0b00 => {
+ // `0b0010xx00xxxx`
+ let opc = shift;
+ if opc == 0o0000 {
+ inst.opcode = Opcode::And;
+ inst.operands[0] = dest;
+ } else if opc == 0o0001 {
+ // Load (R, J) or (S, K)
+ inst.opcode = Opcode::Load;
+ inst.operands[1] = dest;
+ let source = match dest {
+ Operand::J => Operand::R,
+ Operand::K => Operand::S,
+ _ /* JK */ => Operand::RS,
+ };
+ inst.operands[0] = source;
+ } else if opc == 0o0002 {
+ // Load (J, R) or (K, S)
+ inst.opcode = Opcode::Load;
+ inst.operands[0] = dest;
+ let source = match dest {
+ Operand::J => Operand::R,
+ Operand::K => Operand::S,
+ _ /* JK */ => Operand::RS,
+ };
+ inst.operands[1] = source;
+ } else if opc == 0o0003 {
+ // Exchange (J, R) or (K, S)
+ inst.opcode = Opcode::Exchange;
+ inst.operands[0] = dest;
+ let source = match dest {
+ Operand::J => Operand::R,
+ Operand::K => Operand::S,
+ _ /* JK */ => Operand::RS,
+ };
+ inst.operands[1] = source;
+ } else if opc == 0o0004 {
+ // if dest == K, load K from J, else invalid
+ if dest == Operand::K {
+ inst.opcode = Opcode::Load;
+ } else {
+ return Err(DecodeError::Undefined);
+ }
+ } else {
+ return Err(DecodeError::Undefined);
+ }
+ }
+ 0b01 => {
+ // `0b0010xx01xxxx`
+ const OPC: &[Option<(Opcode, Option<Operand>)>] = &[
+ Some((Opcode::AddJK, None)),
+ Some((Opcode::SubJK, None)),
+ Some((Opcode::AddR, Some(Operand::R))), // Add (R, J) -> J
+ Some((Opcode::SubR, Some(Operand::R))), // Sub (R, J) -> J
+ Some((Opcode::AddR, Some(Operand::S))), // Add (S, J) -> J
+ Some((Opcode::SubR, Some(Operand::S))), // Sub (S, J) -> J
+ None,
+ None,
+ Some((Opcode::NegAddJK, None)),
+ Some((Opcode::NegSubJK, None)),
+ Some((Opcode::NegAddR, Some(Operand::R))),
+ Some((Opcode::NegSubR, Some(Operand::R))),
+ Some((Opcode::NegAddR, Some(Operand::S))),
+ Some((Opcode::NegSubR, Some(Operand::S))),
+ None,
+ None,
+ ];
+
+ let (opc, extra_operands) = *OPC.get(shift as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?;
+ inst.opcode = opc;
+ if let Some(extra) = extra_operands {
+ inst.operands[0] = extra;
+ inst.operands[1] = dest;
+ } else {
+ inst.operands[0] = dest;
+ }
+ }
+ 0b10 => {
+ inst.opcode = Opcode::Shift;
+ inst.operands[0] = dest;
+ inst.operands[1] = Operand::Literal(shift);
+ }
+ // 0b11
+ _ => {
+ inst.opcode = Opcode::Rotate;
+ inst.operands[0] = dest;
+ inst.operands[1] = Operand::Literal(shift);
+ }
+ };
+ }
+ // octal codes 1400, 1500, 1600, 1700
+ 0b0011 => {
+ // group 2 instructions
+ #[allow(non_upper_case_globals)]
+ const OPC_Flags: &[Option<(Opcode, Operand)>] = &[
+ Some((Opcode::IDLE, Operand::Nothing)), Some((Opcode::SNZ, Operand::FlagBit)), None, None, None, Some((Opcode::SIZ, Operand::FlagBit)), None, None,
+ Some((Opcode::CLR, Operand::FlagBit)), None, None, None, None, None, None, None,
+ Some((Opcode::CMP, Operand::FlagBit)), None, None, None, None, None, None, None,
+ Some((Opcode::SET, Operand::FlagBit)), None, None, None, None, None, None, None,
+ Some((Opcode::SKPL, Operand::Nothing)), Some((Opcode::SNZ, Operand::OverflowBit)), Some((Opcode::SKIP, Operand::Nothing)), None, None, Some((Opcode::SIZ, Operand::OverflowBit)), None, None,
+ Some((Opcode::CLR, Operand::OverflowBit)), None, None, None, None, None, None, None,
+ Some((Opcode::CMP, Operand::OverflowBit)), None, None, None, None, None, None, None,
+ Some((Opcode::SET, Operand::OverflowBit)), None, None, None, None, None, None, None,
+ ];
+
+ #[allow(non_upper_case_globals)]
+ const OPC_NotFlags: &[Option<Opcode>] = &[
+ None, Some(Opcode::SNZ), Some(Opcode::SIP), None, Some(Opcode::INC), Some(Opcode::SIZ), Some(Opcode::SIN), None,
+ Some(Opcode::CLR), None, None, None, None, None, None, None,
+ Some(Opcode::CMP), None, None, None, Some(Opcode::NEG), None, None, None,
+ Some(Opcode::SET), None, None, None, None, None, None, None,
+ ];
+
+ let jk = (word.0 & 0o0300) >> 6;
+ let opc = word.0 & 0o0077;
+
+ if opc == 0o0000 {
+ // idle, pion, piof, or undefined
+ const OPS: &[Result<Opcode, DecodeError>] = &[
+ Ok(Opcode::IDLE),
+ Ok(Opcode::PION),
+ Ok(Opcode::PIOF),
+ Err(DecodeError::Undefined)
+ ];
+ inst.opcode = OPS[jk as usize]?;
+ } else {
+ let (opcode, operand) = match jk {
+ 0b00 => {
+ *OPC_Flags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?
+ },
+ 0b01 => {
+ (*OPC_NotFlags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?, Operand::J)
+ }
+ 0b10 => {
+ (*OPC_NotFlags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?, Operand::K)
+ }
+ _ => {
+ (*OPC_NotFlags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?, Operand::JK)
+ }
+ };
+ inst.opcode = opcode;
+ inst.operands[0] = operand;
+ }
+ }
+ // octal code 2000
+ 0b0100 => {
+ // literal instructions, `andf`, `andl`, ``addl`, subl`
+ const OPC: &[Opcode] = &[
+ Opcode::ANDF,
+ Opcode::ANDL,
+ Opcode::ADDL,
+ Opcode::SUBL,
+ ];
+
+ let opc = (word.0 >> 6) & 0b11;
+ let literal = (word.0 & 0o0077) as u8;
+
+ let opc = OPC[opc as usize];
+ inst.opcode = opc;
+ inst.operands = [
+ Operand::Literal(literal),
+ Operand::Nothing,
+ Operand::Nothing,
+ ];
+ }
+ // octal code 7400+
+ 0b1111 => {
+ // `tif`, `tir`, `trf`, `tis`, ...
+ let opc = word.0;
+ if opc < 0o7500 {
+ const OPC: &[Option<Opcode>] = &[
+ None, Some(Opcode::TIF), Some(Opcode::TIR), Some(Opcode::TRF), Some(Opcode::TIS), None, None, None,
+ None, Some(Opcode::TOC), Some(Opcode::TOP), Some(Opcode::TCP), Some(Opcode::TOS), None, None, None,
+ None, Some(Opcode::HIS), Some(Opcode::HIR), Some(Opcode::HRF), Some(Opcode::HIS), None, None, None,
+ None, Some(Opcode::HOP), Some(Opcode::HOL), Some(Opcode::HLP), Some(Opcode::HOS), None, None, None,
+ ];
+ let idx = opc - 0o7400;
+ inst.opcode = *OPC.get(idx as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?;
+ } else if opc < 0o7600 {
+ return Err(DecodeError::Undefined);
+ } else if opc < 0o7700 {
+ inst.opcode = if opc == 0o7601 {
+ Opcode::CSLCT1
+ } else if opc == 0o7602 {
+ Opcode::CSLCT2
+ } else if opc == 0o7604 {
+ Opcode::CSLCT3
+ } else {
+ return Err(DecodeError::Undefined);
+ };
+ } else {
+ inst.opcode = if opc == 0o7720 {
+ Opcode::LDREG
+ } else if opc == 0o7721 {
+ Opcode::LDJK
+ } else if opc == 0o7722 {
+ Opcode::RJIB
+ } else {
+ return Err(DecodeError::Undefined);
+ };
+ }
+ }
+ // remaining instructions are keyed entirely on the upper four bits:
+ // `smj`, `dsz`, `isz`, `sbj`, `adj`, `ldj`, `stj`, `jmp`, `jps`, `xct`.
+ // this set of instructions starts at 0o2400.
+ other => {
+ const OPC: &[Opcode] = &[
+ Opcode::SMJ,
+ Opcode::DSZ,
+ Opcode::ISZ,
+ Opcode::SBJ,
+ Opcode::ADJ,
+ Opcode::LDJ,
+ Opcode::STJ,
+ Opcode::JMP,
+ Opcode::JPS,
+ Opcode::XCT,
+ ];
+
+ let offset = (word.0 & 0o0077) as i8;
+ let negative = (word.0 >> 6) & 1;
+ let indirect = (word.0 >> 7) & 1;
+ let offset = if negative == 1 {
+ -offset
+ } else {
+ offset
+ };
+
+ let opc = OPC[(other - (0o2400 >> 8)) as usize];
+ inst.opcode = opc;
+ inst.operands = [
+ Operand::Displacement(indirect == 1, offset),
+ Operand::Nothing,
+ Operand::Nothing,
+ ];
+ }
+ }
+
+ inst.length = words.offset() as u8;
+ Ok(())
+ }
+}
diff --git a/tests/test.rs b/tests/test.rs
new file mode 100644
index 0000000..d2750ee
--- /dev/null
+++ b/tests/test.rs
@@ -0,0 +1,53 @@
+use yaxpeax_arch::Decoder;
+
+fn test_display(data: &[u16], expected: &'static str) {
+ let mut reader = yaxpeax_nd812::ND812Reader::of_u16(data);
+ match yaxpeax_nd812::InstDecoder::default().decode(&mut reader) {
+ Ok(instr) => {
+ let displayed = instr.to_string();
+ assert_eq!(&displayed, expected);
+ assert_eq!(data.len() as u8, instr.len());
+ }
+ Err(e) => {
+ let mut msg = "failed to decode".to_owned();
+ if data.len() > 0 {
+ msg.push_str(" [");
+ msg.push_str(&format!("{:04o}", data[0]));
+ for i in 1..data.len() {
+ msg.push_str(", ");
+ msg.push_str(&format!("{:04o}", data[i]));
+ }
+ msg.push_str("]");
+ }
+ msg.push_str(": ");
+ msg.push_str(&e.to_string());
+ panic!("{}", msg);
+ }
+ }
+}
+
+#[test]
+fn test_disassembly() {
+ test_display(&[0o5464], "stj $+0x34"); // symbol name from IM41-1085
+ test_display(&[0o6137], "jmp $-0x1f"); // symbol name from IM41-1085
+ test_display(&[0o4417], "adj $+0xf"); // symbol name from IM41-1085
+ test_display(&[0o1501], "snz j");
+ test_display(&[0o1450], "clr o");
+ test_display(&[0o1510], "clr j");
+ test_display(&[0o0640, 0o2441], "twjps 0o2441");
+ test_display(&[0o0500, 0o2320], "twldj 0o2320");
+ test_display(&[0o4446], "adj $+0x26");
+ test_display(&[0o5625], "stj@ $+0x15"); // symbol name from IM41-1085
+ test_display(&[0o5626], "stj@ $+0x16"); // symbol name from IM41-1085
+ test_display(&[0o5004], "ldj $+0x4"); // symbol name from IM41-1085
+ test_display(&[0o7170], "xct $-0x38"); // page 8-21 of `IM41-1085`, address `5300`. x14 is a label 0x38 words back..
+ test_display(&[0o6554], "jps $-0x2c");
+ test_display(&[0o3410], "isz $+0x8"); // symbol name from IM41-1085
+
+ test_display(&[0o1122], "adr j");
+ test_display(&[0o1002], "rfov");
+ test_display(&[0o1007], "ionn");
+ test_display(&[0o1602], "sip k");
+ test_display(&[0o7401], "tif");
+ test_display(&[0o7722], "rjib");
+}