yaxpeax_nd812/lib.rs
1//! # `yaxpeax-nd812`, a decoder for the ND812 instruction set
2//!
3//! the ND812 instruction set is used in the Nuclear Data ND812 microcomputer, first introduced in
4//! 1970. the ND812 and associated additional hardware (such as the ND4410) were used for
5//! scientific applications and it seems relatively few programs for this equipment has survived to
6//! the present day.
7//!
8//! interesting for yaxpeax reasons, the ND812 is a 12-bit machine. `yaxpeax-nd812` decodes units
9//! of [`ND812Word`], consulting only the low 12 bits of the contained `u16`. `ND812Word` then
10//! requires an `impl Reader<u16, ND812Word>` to decode from; there is a default impl to read
11//! `ND812Word` from a regular `&[u8]`, but a more comprehensive `ND812` simulator would need to
12//! also reproduce the wrap-at-4k-boundary behavior from the real hardware.
13//!
14//! the actual packing of 12-bit words may also be of interest. i couldn't find any `ND812`
15//! programs online as binary, even as binary images to be loaded by simulators - i couldn't really
16//! find any `ND812` simulators available online either. so my best guess for a reasonable binary
17//! format is to do what people do with PDP-8 (also 12-bit) binary blobs, with 12-bit words in
18//! two-byte units of memory.
19//!
20//! lastly, thank goodness for `bitsavers.org`. i found many of the manuals and documents for the
21//! `ND812` in scattered places online, but bitsavers has them all in one place. the reference
22//! there helped me answer a few questions about missing documents, and led me to finding program
23//! `ND41-1085` in the `IM41-1085` manual for x-ray analysis. it turned out that the best test
24//! cases were reference programs from Nuclear Data themselves.
25//!
26//! reference materials:
27//! ```text
28//! shasum -a 256 IM*
29//! 3a4ccbdd898ff071636d14af908e2386d6204b77d39a546f416f40ad7ad890fa
30//! IM41-0001_Software_Instruction_Manual_BASC-12_General_Assembler_Jan71.pdf
31//! 39dcb814862c385986aee6ff31b4f0a942e9ff8dcaac0046cbcba55f052d42e5
32//! IM41-0002-04_Software_Instruction_Manual_ND812_Symbolic_Text_Editor_Nov72.pdf
33//! d5380bed1407566b491d00e654bc7967208fa71ef6daa7ec82e73805f671ff0a
34//! IM41-0059-00_NUTRAN_User_and_Programmers_Guide_Nov72.pdf
35//! f508a4bb6a834352b1a391ac0dd851201dd6a6a5cfa6eec53aa4c6dbf86e088a
36//! IM41-1062-00_Software_Instruction_Manual_ND4410_Low_High_Speed_Paper_Tape_IO_Overlay_Program_Apr72.pdf
37//! a1364c23ffadc4414c7b905cfce7cd4c0914a5b0d29b1726246a9d5d68d0aa7a
38//! IM41-8001-01_Software_Instruction_Manual_ND812_Diagnostics_Feb72.pdf
39//! 62013481aab174473ae1cbaed35d02eb7f22a05acd6c56ae36d166502925cb25
40//! IM41-8045-00_Software_Instruction_Manual_Hardware_Multipy_Divide_Test_Jun72.pdf
41//! 3cf00d268cab96eebda973b53b870fe761e83d2e61a733860094920b17d84b22
42//! IM88-0481-02_Hardware_Instruction_Manual_ND812_Teletype_Auto_Loader_Interface_Sep72.pdf
43//! ```
44//!
45//! ## usage
46//!
47//! the fastest way to decode an nd812 instruction is through
48//! [`InstDecoder::decode_slice()`]:
49//! ```
50//! use yaxpeax_nd812::InstDecoder;
51//!
52//! let inst = InstDecoder::decode_u16(&[0o1122]).unwrap();
53//!
54//! assert_eq!("adr j", inst.to_string());
55//! ```
56//!
57//! opcodes and operands are available on the decoded instruction, as well as its length and
58//! operand count:
59//! ```
60//! use yaxpeax_nd812::{InstDecoder, Operand, Opcode};
61//!
62//! let inst = InstDecoder::decode_u16(&[0o1123]).unwrap();
63//!
64//! assert_eq!("sbr j", inst.to_string());
65//! assert_eq!(inst.operand_count(), 2);
66//! assert_eq!(inst.len(), 1);
67//! assert_eq!(inst.opcode(), Opcode::SubR);
68//! assert_eq!(inst.operand(0), Operand::R);
69//! assert_eq!(inst.operand(1), Operand::J);
70//! ```
71//!
72//! additionally, `yaxpeax-nd812` implements `yaxpeax-arch` traits for generic use, such as
73//! [`yaxpeax_arch::LengthedInstruction`]. [`yaxpeax_arch::Arch`] is implemented by
74//! the unit struct [`ND812`]. `yaxpeax-nd812` does not decode from a `U8Reader`, like many other
75//! decodes, but does decode from an `ND812Reader` that can be trivially constructed from a
76//! `U8Reader`.
77//!
78//! ## `#![no_std]`
79//!
80//! `yaxpeax-nd812` should support `no_std` usage, but this is entirely untested.
81
82#![no_std]
83
84mod display;
85
86use yaxpeax_arch::{AddressDiff, Arch, Decoder, LengthedInstruction, Reader, ReadError, U8Reader};
87
88/// a trivial struct for [`yaxpeax_arch::Arch`] to be implemented on. it's only interesting for the
89/// associated type parameters.
90#[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)]
91pub struct ND812;
92
93/// a 12-bit word, as used in the `nd812`.
94#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialOrd, PartialEq)]
95#[repr(transparent)]
96pub struct ND812Word(u16);
97
98impl ND812Word {
99 /// get the value of this 12-bit word as a u16
100 pub fn value(&self) -> u16 {
101 self.0
102 }
103
104 /// create an `ND812Word` from a value `v`
105 ///
106 /// returns `None` if `v` is out of range for an `ND812Word` (is larger than `0o7777`, or in
107 /// base 16, `0x0fff`)
108 pub fn new(v: u16) -> Option<Self> {
109 if v > 0o7777 {
110 None
111 } else {
112 Some(ND812Word(v))
113 }
114 }
115}
116
117impl core::fmt::Display for ND812Word {
118 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
119 write!(f, "{}", self.0)
120 }
121}
122
123impl Arch for ND812 {
124 type Address = u16;
125 type Word = ND812Word;
126 type Instruction = Instruction;
127 type Decoder = InstDecoder;
128 type DecodeError = DecodeError;
129 type Operand = Operand;
130}
131
132#[derive(Copy, Clone, PartialEq, Eq, Debug)]
133pub enum DecodeError {
134 /// no input available but the instruction would require at least one more word to decode
135 ExhaustedInput,
136 /// the word(s) to decode this instruction do not map to a defined instruction
137 Undefined,
138}
139
140impl From<yaxpeax_arch::ReadError> for DecodeError {
141 fn from(_e: yaxpeax_arch::ReadError) -> Self {
142 DecodeError::ExhaustedInput
143 }
144}
145
146impl core::fmt::Display for DecodeError {
147 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
148 use yaxpeax_arch::DecodeError;
149 f.write_str(self.description())
150 }
151}
152
153impl yaxpeax_arch::DecodeError for DecodeError {
154 fn data_exhausted(&self) -> bool {
155 *self == DecodeError::ExhaustedInput
156 }
157 fn bad_opcode(&self) -> bool {
158 *self == DecodeError::Undefined
159 }
160 fn bad_operand(&self) -> bool {
161 *self == DecodeError::Undefined
162 }
163 fn description(&self) -> &'static str {
164 match self {
165 DecodeError::ExhaustedInput => "exhausted input",
166 DecodeError::Undefined => "undefined encoding",
167 }
168 }
169}
170
171/// a wrapper describing one of four (three optional) memory fields in an `nd812`. some `nd812`
172/// documentation refers to these as "stacks" of memory.
173#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
174pub struct MemoryField {
175 value: u8
176}
177
178impl MemoryField {
179 fn new(which: u8) -> Self {
180 assert!(which < 4);
181
182 MemoryField { value: which }
183 }
184
185 /// get the bits to select this `field` as encoded in an `nd812` instruction. default field is
186 /// `0`, with possible alternate field `1`, `2`, and `3`. the `nd812` does not support more
187 /// than four total field, so this function will never return a value of 4 or above.
188 pub fn num(&self) -> u8 {
189 self.value
190 }
191}
192
193/// an `nd812` instruction.
194///
195/// `nd812` instructions have an [`Opcode`] and up to three [`Operand`]s. they are one or two
196/// `ND812Word` long.
197#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
198pub struct Instruction {
199 /// the operation of this instruction.
200 opcode: Opcode,
201 operands: [Operand; 3],
202 /// the nd812 "field" this instruction references. if this is present, memory referenced will
203 /// be with respect to this 4096-word field of memory. a `field` of memory, to the `nd812`, is
204 /// a linear region of memory - this is also called a "stack" in some documentation. even so,
205 /// there is no "stack pointer" nor "growth" (upward or downward) notions.
206 ///
207 /// if there is a field selected, and the instruction is `jmp` or `jps`, this field will be
208 /// made default for instructions after this.
209 referenced_field: Option<MemoryField>,
210 length: u8,
211}
212
213impl Default for Instruction {
214 fn default() -> Instruction {
215 Instruction {
216 opcode: Opcode::STOP,
217 operands: [Operand::Nothing, Operand::Nothing, Operand::Nothing],
218 referenced_field: None,
219 length: 0,
220 }
221 }
222}
223
224impl Instruction {
225 fn reset_operands(&mut self) {
226 self.operands = [Operand::Nothing, Operand::Nothing, Operand::Nothing];
227 }
228
229 /// the length of this instruction, in terms of [`ND812Word`].
230 pub fn len(&self) -> u8 {
231 self.length
232 }
233
234 /// get the number of operands in this instruction.
235 ///
236 /// calls to `Instruction::operand` for indices between 0 and this value will return an operand
237 /// other than `Operand::Nothing`.
238 pub fn operand_count(&self) -> u8 {
239 if self.operands[0] == Operand::Nothing {
240 0
241 } else if self.operands[1] == Operand::Nothing {
242 1
243 } else {
244 2
245 }
246 }
247
248 /// get the `Operand` at the provided index.
249 ///
250 /// indices above `3` will always yield `Operand::Nothing`.
251 pub fn operand(&self, idx: u8) -> Operand {
252 self.operands.get(idx as usize).map(|x| *x).unwrap_or(Operand::Nothing)
253 }
254
255 /// get the `Opcode` of this instruction.
256 pub fn opcode(&self) -> Opcode {
257 self.opcode
258 }
259}
260
261impl LengthedInstruction for Instruction {
262 type Unit = AddressDiff<<ND812 as Arch>::Address>;
263 fn min_size() -> Self::Unit {
264 AddressDiff::from_const(1)
265 }
266 fn len(&self) -> Self::Unit {
267 AddressDiff::from_const(self.length as u16)
268 }
269}
270
271impl yaxpeax_arch::Instruction for Instruction {
272 fn well_defined(&self) -> bool { true }
273}
274
275/// an operand for an `nd812` instruction.
276#[derive(Copy, Clone, Hash, PartialEq, Eq)]
277pub enum Operand {
278 /// no operand in this position.
279 ///
280 /// reaching this as a user of `yaxpeax_nd812` is almost certainly a bug. `Instruction::operand`
281 /// will return `None` rather than `Operand::Nothing`.
282 Nothing,
283 /// the `J` register
284 J,
285 /// the `K` register
286 K,
287 /// the `JK` register
288 JK,
289 /// the `R` register
290 R,
291 /// the `S` register
292 S,
293 /// the `RS` register
294 RS,
295 /// the `OverflowBit` register
296 OverflowBit,
297 /// the `FlagBit` register
298 FlagBit,
299 /// memory access to `pc` plus the given signed offset, in the range `[-64, 64]` (inclusive).
300 /// additionally, the displacement may be indirect; `pc + offset` may be used as a pointer.
301 Displacement(bool, i8),
302 /// memory access to the absolute u12 address, in the range `[0, 4095]` (inclusive).
303 /// additionally, the displacement may be indirect; `offset` may be used as a pointer.
304 ///
305 /// in practice, instructions with an `Absolute` operand also may have an alternate field
306 /// selected, so correct interpretation of this operand will need to consult the referenced
307 /// field as well.
308 Absolute(bool, u16),
309 /// a literal value encoded in an instruction (modern instruction sets would call this an
310 /// `immediate`)
311 Literal(u8),
312}
313
314/// an `nd812` instruction's operation.
315#[allow(non_camel_case_types)]
316#[derive(Copy, Clone, Hash, PartialEq, Eq)]
317pub enum Opcode {
318 /// `Stop Execution`
319 STOP,
320 /// `One cycle delay`
321 IDLE,
322 /// `Cassette High-Speed Forward EOT (TWIO)`
323 CHSF,
324 /// `Cassette Space Forward to File Mark (TWIO)`
325 CSPF,
326 /// `Cassette Skip on File Mark (TWIO)`
327 CSFM,
328 /// `Cassette Skip if EOT (TWIO)`
329 CSET,
330 /// `Cassette High-Speed Reverse BOT (TWIO)`
331 CHSR,
332 /// `Cassette Skip No-Error (TWIO)`
333 CSNE,
334 /// `Cassette Skip if On-Line Tape Ready (TWIO)`
335 CSTR,
336 /// `Cassette Skip if BOT (TWIO)`
337 CSBT,
338 /// `Cassette Clear All Flags (TWIO)`
339 CCLF,
340 /// `Cassette Skip if Read Ready (TWIO)`
341 CSRR,
342 /// `Cassette Read to J (TWIO)`
343 CRDT,
344 /// `Cassette Write File Mark (TWIO)`
345 CWFM,
346 /// `Cassette Skip if Write Ready (TWIO)`
347 CSWR,
348 /// `Cassette Write Transfer (TWIO)`
349 CWRT,
350 /// `Two Word Skip if Memory Not Equal`
351 ///
352 /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
353 /// `Operand::K`.
354 TWSM,
355 TWDSZ,
356 TWISZ,
357 /// `Two Word Subtract`
358 ///
359 /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
360 /// `Operand::K`.
361 TWSB,
362 /// `Two Word Add`
363 ///
364 /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
365 /// `Operand::K`.
366 TWAD,
367 /// `Two Word Load`
368 ///
369 /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
370 /// `Operand::K`.
371 TWLD,
372 /// `Two Word Store`
373 ///
374 /// `yaxpeax-nd812` records the register to operate against as an operand, `Operand::J` or
375 /// `Operand::K`.
376 TWST,
377 /// `Two Word Unconditional Jump`
378 TWJMP,
379 /// `Two Word Jump Subroutine`
380 TWJPS,
381 /// `Multiply J by K`
382 MPY,
383 /// `Divide J and K by R`
384 DIV,
385 /// `Read Flag, Overflow from J`
386 RFOV,
387 /// `Disable All Interrupt Levels`
388 IOFF,
389 /// `Enable Level H Only`
390 IONH,
391 /// `Enable Interrupt Levels H & A`
392 IONA,
393 /// `Enable Interrupt Levels H & B`
394 IONB,
395 /// `Enable All Interrupt Levels`
396 IONN,
397 /// `Load J from Switches`
398 LJSW,
399 /// `Load J from Status Register`
400 LJST,
401 /// `And J, K, into <op0>`
402 ///
403 /// `yaxpeax-nd812` records the destination as an operand, `Operand::J`, `Operand::K`, or
404 /// `Operand::JK`
405 And,
406 /// `Load <op0> into <op1>`
407 ///
408 /// `yaxpeax-nd812` records the source and destination both as operands. operands will be one
409 /// of the following pairs:
410 /// * `R, J`
411 /// * `J, R`
412 /// * `S, K`
413 /// * `K, S`
414 /// * `K, J`
415 /// * `RS, JK`
416 /// * `JK, RS`
417 Load, // load op[0] from [1]
418 /// `Exchange <op0> and <op1>`
419 ///
420 /// `yaxpeax-nd812` records the source and destination both as operands. operands will be one
421 /// of the following pairs:
422 /// * `J, R`
423 /// * `K, S`
424 /// * `JK, RS`
425 Exchange, // exchange op[0], op[1]
426 /// `J + K to <op0>`
427 ///
428 /// `yaxpeax-nd812` records the destination as an operand, `Operand::J`, `Operand::K`, or
429 /// `Operand::JK`
430 AddJK, // `op[0] + op[1] to op[2]`
431 /// `J - K to <op0>`
432 ///
433 /// `yaxpeax-nd812` records the destination as an operand, `Operand::J` or `Operand::K`
434 SubJK, // `op[0] - op[1] to op[2]`
435 /// `<op0> + <op1> to <op1>`
436 ///
437 /// `yaxpeax-nd812` records the source and destination both as operands:
438 /// * `op0` can be `Operand::R` or `Operand::S`
439 /// * `op1` can be `Operand::J` or `Operand::K`
440 AddR, // `op[0] + op[1] to op[2]`
441 /// `<op0> - <op1> to <op1>`
442 ///
443 /// `yaxpeax-nd812` records the source and destination both as operands:
444 /// * `op0` can be `Operand::R` or `Operand::S`
445 /// * `op1` can be `Operand::J` or `Operand::K`
446 SubR, // `op[0] - op[1] to op[2]`
447 /// `-(J + K) to <op0>`
448 ///
449 /// `yaxpeax-nd812` records the destination as an operand, `Operand::J`, `Operand::K`, or
450 /// `Operand::JK`
451 NegAddJK, // `-(op[0] + op[1]) to op[2]`
452 /// `K - J to <op0>`
453 ///
454 /// `yaxpeax-nd812` records the destination as an operand, `Operand::J` or `Operand::K`
455 NegSubJK, // `(op[1] - op[0]) to op[2]`
456 /// `-(<op0> + <op1>) to <op1>`
457 ///
458 /// `yaxpeax-nd812` records the source and destination both as operands:
459 /// * `op0` can be `Operand::R` or `Operand::S`
460 /// * `op1` can be `Operand::J` or `Operand::K`
461 NegAddR, // `-(op[0] + op[1]) to op[2]`
462 /// `<op1> - <op0> to <op1>`
463 ///
464 /// `yaxpeax-nd812` records the source and destination both as operands:
465 /// * `op0` can be `Operand::R` or `Operand::S`
466 /// * `op1` can be `Operand::J` or `Operand::K`
467 NegSubR, // `(op[1] - op[0]) to op[2]`
468 /// `Shift <op0> left N`
469 ///
470 /// `yaxpeax-nd812` records the register as an operand, either `Operand::J`, `Operand::K`, or
471 /// `Operand::JK`.
472 Shift, // `j <<= N`, what is N?
473 /// `Rotate <op0> left N`
474 ///
475 /// `yaxpeax-nd812` records the register as an operand, either `Operand::J`, `Operand::K`, or
476 /// `Operand::JK`.
477 Rotate, // `j <<= N`, what is N?
478 /// `Skip if Flag Register One`
479 ///
480 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
481 /// `Operand::Overflow`, or J, K, JK,
482 SNZ,
483 /// `Skip if Flag Register Zero`
484 ///
485 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
486 /// `Operand::OverflowBit`, or J, K, JK,
487 SIZ,
488 ///
489 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
490 /// `Operand::OverflowBit`, or J, K, JK,
491 CLR,
492 ///
493 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
494 /// `Operand::OverflowBit`, or J, K, JK
495 CMP,
496 ///
497 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
498 /// `Operand::OverflowBit`, or J, K, JK
499 SET,
500 /// `Skip on Power Low`
501 SKPL,
502 /// `Powerfail System On`
503 PION,
504 /// `Powerfail System Off`
505 PIOF,
506 ///
507 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
508 /// `Operand::OverflowBit`, or J, K, JK
509 SIP,
510 ///
511 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
512 /// `Operand::OverflowBit`, or J, K, JK
513 INC,
514 ///
515 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
516 /// `Operand::OverflowBit`, or J, K, JK
517 SIN,
518 ///
519 /// `yaxpeax-nd812` records the register as an operand, either `Operand::FlagBit` or
520 /// `Operand::OverflowBit`, or J, K, JK
521 NEG,
522 /// `AND with J, Forward`
523 ANDF,
524 /// `AND J Literal`
525 ANDL,
526 /// `ADD J Literal`
527 ADDL,
528 /// `SUBTRACT J Literal`
529 SUBL,
530 /// `Skip if J not Equal Memory`
531 SMJ,
532 /// `Decrement Memory and Skip`
533 DSZ,
534 /// `Increment Memory and Skip`
535 ISZ,
536 /// `Subtract from J`
537 SBJ,
538 /// `Add to J`
539 ADJ,
540 /// `Load J`
541 LDJ,
542 /// `Store J`
543 STJ,
544 /// `Unconditional Jump`
545 JMP,
546 /// `Unconditional Skip`
547 SKIP,
548 /// `Jump Subroutine`
549 JPS,
550 /// `Execute Displaced Instruction`
551 XCT,
552 /// `TTY Keyboard-Reader Fetch`
553 TIF,
554 /// `TTY Keyboard Into J`
555 TIR,
556 /// `TIR and TIF combined`
557 TRF,
558 /// `TTY Skip if Keyboard Ready`
559 TIS,
560 /// `Clear printer/punch flag`
561 TOC,
562 /// `Clear printer/punch flag, load printer/punch buffer from J and print/punch`
563 TOP,
564 /// `TOP and TOC combined`
565 TCP,
566 /// `TTY Skip if Printer-Punch Reader`
567 TOS,
568 /// `HS Reader - Fetch`
569 HIF,
570 /// `HS Reader - CLR Flag, Read Buffer`
571 HIR,
572 /// `HIR and HIF combined`
573 HRF,
574 /// `Skip if HS reader flag = 1`
575 HIS,
576 /// `HS Punch - Punch On`
577 HOP,
578 /// `HS Punch - CLR Flag, Load Buffer`
579 HOL,
580 /// `HS Punch - Load and Punch`
581 HLP,
582 /// `HS Punch - Skip if punch ready`
583 HOS,
584 /// `Cassette - Unit 1 On-Line`
585 CSLCT1,
586 /// `Cassette - Unit 2 On-Line`
587 CSLCT2,
588 /// `Cassette - Unit 3 On-Line`
589 CSLCT3,
590 /// `Load JPS Reg from J, INT Reg from K`
591 LDREG,
592 /// `Load JPS Reg to J, INT Reg to K`
593 LDJK,
594 /// `Restore JPS and INT field bits`
595 RJIB,
596}
597
598/// the ND812 uses a modified character set to pack two characters into 12-bit words; each
599/// character is *6* bits.
600///
601/// TODO: not yet sure how this maps to ascii; `Appendix C` describes `A` as `ASCII CODE 301` -
602/// doesn't match as octal or.. anything else. the whole character set is reproduced here, for
603/// reference. this all DOES line up with ascii if ND812 assumes ascii has the high bit set?
604pub const ND812_CHARSET: &[u8] = &[
605 b'A', b'B', b'C', b'D', b'E', b'F',
606 b'G', b'H', b'I', b'J', b'K', b'L',
607 b'M', b'N', b'O', b'P', b'Q', b'R',
608 b'S', b'T', b'U', b'V', b'W', b'X',
609 b'Y', b'Z', b'0', b'1', b'2', b'3',
610 b'4', b'5', b'6', b'7', b'8', b'9',
611 b'$', b'*', b'+', b'!', b'-', b'.',
612 b'/', b';', b'=', b' ', b'\t', b'\n',
613 0x0c, b'\r', 0o377
614];
615
616/// an `nd812` instruction decoder.
617///
618/// there are no decode options for `nd812`, so this is a trivial struct that exists only for the
619/// [`yaxpeax_arch::Decoder`] trait impl.
620#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
621pub struct InstDecoder { }
622
623pub struct ND812Reader<T> {
624 underlying: T,
625 start: u16,
626 mark: u16,
627 offset: u16,
628}
629
630impl<'a> ND812Reader<&'a [u16]> {
631 pub fn of_u16(data: &'a [u16]) -> Self {
632 ND812Reader {
633 underlying: data,
634 start: 0,
635 mark: 0,
636 offset: 0,
637 }
638 }
639}
640
641impl<'a> Reader<u16, ND812Word> for ND812Reader<&'a [u16]> {
642 fn next(&mut self) -> Result<ND812Word, ReadError> {
643 if let Some(word) = self.underlying.get(self.offset as usize) {
644 if word & 0xf000 != 0 {
645 return Err(ReadError::IOError("invalid nd812 word in u16 data: bits in 12-15 are set"));
646 }
647
648 self.offset += 1;
649 Ok(ND812Word(word & 0o7777))
650 } else {
651 Err(ReadError::ExhaustedInput)
652 }
653 }
654
655 fn next_n(&mut self, buf: &mut [ND812Word]) -> Result<(), ReadError> {
656 if buf.len() > self.underlying.len() - self.offset as usize {
657 return Err(ReadError::ExhaustedInput);
658 }
659
660 // there's at least enough data, though some of it could be invalid...
661 // TODO: this will result in an error potentially consuming data without indicating how
662 // much data was consumed. not good!
663 for i in 0..buf.len() {
664 buf[i] = self.next()?;
665 }
666
667 Ok(())
668 }
669
670 fn mark(&mut self) {
671 self.mark = self.offset;
672 }
673
674 fn offset(&mut self) -> u16 {
675 self.offset - self.mark
676 }
677
678 fn total_offset(&mut self) -> u16 {
679 self.offset - self.start
680 }
681}
682
683impl<'a> ND812Reader<U8Reader<'a>> {
684 pub fn of_u8(data: &'a [u8]) -> Self {
685 ND812Reader {
686 underlying: U8Reader::new(data),
687 start: 0,
688 mark: 0,
689 offset: 0,
690 }
691 }
692}
693
694impl<'a> Reader<u16, ND812Word> for ND812Reader<U8Reader<'a>> {
695 fn next(&mut self) -> Result<ND812Word, ReadError> {
696 let high = Reader::<u16, u8>::next(&mut self.underlying)?;
697 let low = Reader::<u16, u8>::next(&mut self.underlying)?;
698 self.offset += 1;
699 Ok(ND812Word(u16::from_le_bytes([low, high])))
700 }
701
702 fn next_n(&mut self, buf: &mut [ND812Word]) -> Result<(), ReadError> {
703 // TODO: this will result in an error potentially consuming data without indicating how
704 // much data was consumed. not good!
705 for i in 0..buf.len() {
706 buf[i] = self.next()?;
707 }
708
709 Ok(())
710 }
711
712 fn mark(&mut self) {
713 Reader::<u16, u8>::mark(&mut self.underlying);
714 self.mark = self.offset;
715 }
716
717 fn offset(&mut self) -> u16 {
718 self.offset - self.mark
719 }
720
721 fn total_offset(&mut self) -> u16 {
722 self.offset - self.start
723 }
724}
725
726/*
727impl Reader<u16, ND812Word> for ND812Reader<U16Reader> {
728}
729*/
730
731impl InstDecoder {
732 /// decode a slice of bytes into an instruction (or error)
733 ///
734 /// this is just a higher-level interface to the [`InstDecoder`] impl of
735 /// [`yaxpeax_arch::Decoder`].
736 pub fn decode_slice(data: &[u8]) -> Result<Instruction, <ND812 as Arch>::DecodeError> {
737 InstDecoder::default()
738 .decode(&mut ND812Reader::of_u8(data))
739 }
740
741 /// decode a slice of `u16` into an instruction (or error)
742 ///
743 /// this is just a higher-level interface to the [`InstDecoder`] impl of
744 /// [`yaxpeax_arch::Decoder`].
745 pub fn decode_u16(data: &[u16]) -> Result<Instruction, <ND812 as Arch>::DecodeError> {
746 InstDecoder::default()
747 .decode(&mut ND812Reader::of_u16(data))
748 }
749}
750
751impl Default for InstDecoder {
752 fn default() -> Self {
753 InstDecoder { }
754 }
755}
756
757impl Decoder<ND812> for InstDecoder {
758 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> {
759 inst.length = 0;
760 inst.reset_operands();
761 words.mark();
762 let word = words.next()?;
763
764 let operation = word.0 >> 8;
765
766 if word.0 & 0o7700 == 0 {
767 inst.opcode = Opcode::STOP;
768 inst.length = words.offset() as u8;
769
770 return Ok(());
771 }
772
773 match operation {
774 0b0000 |
775 0b0001 => {
776 // two word instruction
777 let address = words.next()?;
778 let opc = (word.0 >> 5) & 0b0001111;
779 let field = word.0 & 0b11;
780 let change_field = (word.0 & 0b0100) != 0;
781 let kj = (word.0 & 0b1000) != 0;
782 let ind = (word.0 & 0b10000) != 0;
783
784 if word.0 < 0o0240 {
785 // unallocated
786 // cassette two-word i/o op
787 } else if word.0 == 0o0740 {
788 // TWIO (two word i/o)
789 let opc = address.0;
790
791 const OPC: &[Option<Opcode>] = &[
792 None, Some(Opcode::CHSF), Some(Opcode::CSPF), None, Some(Opcode::CSFM), None, None, None,
793 Some(Opcode::CSET), None, None, None, None, None, None, None,
794 None, Some(Opcode::CHSR), Some(Opcode::CSNE), None, Some(Opcode::CSTR), None, None, None,
795 Some(Opcode::CSBT), None, None, None, None, None, None, None,
796 None, Some(Opcode::CCLF), Some(Opcode::CSRR), None, Some(Opcode::CRDT), None, None, None,
797 None, Some(Opcode::CWFM), Some(Opcode::CSWR), None, Some(Opcode::CWRT), None, None, None,
798 ];
799
800 if opc < 0o100 {
801 return Err(DecodeError::Undefined);
802 }
803
804 let opc = opc - 0o0100;
805 inst.opcode = *OPC.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?;
806 } else {
807 let opc = opc - 5;
808 // starts at `0240`
809 const OPC: &[(Opcode, bool)] = &[
810 (Opcode::TWSM, true),
811 (Opcode::TWDSZ, false),
812 (Opcode::TWISZ, false),
813 (Opcode::TWSB, true),
814 (Opcode::TWAD, true),
815 (Opcode::TWLD, true),
816 (Opcode::TWST, true),
817 (Opcode::TWJMP, false),
818 (Opcode::TWJPS, false),
819 // nothing for 0700 - would be a two-word xct
820 ];
821
822 let (opcode, has_op) = *OPC.get(opc as usize).ok_or(DecodeError::Undefined)?;
823
824 if change_field {
825 inst.referenced_field = Some(MemoryField::new(field as u8));
826 }
827
828 inst.opcode = opcode;
829
830 if has_op {
831 inst.operands[0] = if kj {
832 Operand::K
833 } else {
834 Operand::J
835 };
836 inst.operands[1] = Operand::Absolute(ind, address.0);
837 } else {
838 inst.operands[0] = Operand::Absolute(ind, address.0);
839 }
840 }
841 },
842 0b0010 => {
843 // group 1 instructions
844 // ```
845 // op1 = 0010
846 //
847 // | op1 | j | k | shift | shift count |
848 // | |acc|acc| rot | |
849 // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11|
850 // ```
851
852 let shift = (word.0 & 0o0017) as u8;
853 let opc = ((word.0 >> 4) & 0o0003) as u8;
854 let kj = ((word.0 >> 6) & 0o0003) as u8;
855
856 let dest = match kj {
857 0b00 => {
858 // opcodes like `0o10xx`
859 // in practice the only instructions here are `0b1000` to `0b1011`
860 let low = (word.0 & 0o0077) as u8;
861 const OPC: &[Opcode] = &[
862 Opcode::MPY,
863 Opcode::DIV,
864 Opcode::RFOV,
865 Opcode::IOFF,
866 Opcode::IONH,
867 Opcode::IONB,
868 Opcode::IONA,
869 Opcode::IONN,
870 Opcode::LJSW,
871 Opcode::LJST,
872 ];
873 inst.opcode = *OPC.get(low as usize).ok_or(DecodeError::Undefined)?;
874 inst.length = words.offset() as u8;
875
876 return Ok(());
877 },
878 0b01 => Operand::J,
879 0b10 => Operand::K,
880 _ => Operand::JK,
881 };
882
883 match opc {
884 0b00 => {
885 // `0b0010xx00xxxx`
886 let opc = shift;
887 if opc == 0o0000 {
888 inst.opcode = Opcode::And;
889 inst.operands[0] = dest;
890 } else if opc == 0o0001 {
891 // Load (R, J) or (S, K)
892 inst.opcode = Opcode::Load;
893 inst.operands[1] = dest;
894 let source = match dest {
895 Operand::J => Operand::R,
896 Operand::K => Operand::S,
897 _ /* JK */ => Operand::RS,
898 };
899 inst.operands[0] = source;
900 } else if opc == 0o0002 {
901 // Load (J, R) or (K, S)
902 inst.opcode = Opcode::Load;
903 inst.operands[0] = dest;
904 let source = match dest {
905 Operand::J => Operand::R,
906 Operand::K => Operand::S,
907 _ /* JK */ => Operand::RS,
908 };
909 inst.operands[1] = source;
910 } else if opc == 0o0003 {
911 // Exchange (J, R) or (K, S)
912 inst.opcode = Opcode::Exchange;
913 inst.operands[0] = dest;
914 let source = match dest {
915 Operand::J => Operand::R,
916 Operand::K => Operand::S,
917 _ /* JK */ => Operand::RS,
918 };
919 inst.operands[1] = source;
920 } else if opc == 0o0004 {
921 // if dest == K, load K from J, else invalid
922 if dest == Operand::K {
923 inst.opcode = Opcode::Load;
924 } else {
925 return Err(DecodeError::Undefined);
926 }
927 } else {
928 return Err(DecodeError::Undefined);
929 }
930 }
931 0b01 => {
932 // `0b0010xx01xxxx`
933 const OPC: &[Option<(Opcode, Option<Operand>)>] = &[
934 Some((Opcode::AddJK, None)),
935 Some((Opcode::SubJK, None)),
936 Some((Opcode::AddR, Some(Operand::R))), // Add (R, J) -> J
937 Some((Opcode::SubR, Some(Operand::R))), // Sub (R, J) -> J
938 Some((Opcode::AddR, Some(Operand::S))), // Add (S, J) -> J
939 Some((Opcode::SubR, Some(Operand::S))), // Sub (S, J) -> J
940 None,
941 None,
942 Some((Opcode::NegAddJK, None)),
943 Some((Opcode::NegSubJK, None)),
944 Some((Opcode::NegAddR, Some(Operand::R))),
945 Some((Opcode::NegSubR, Some(Operand::R))),
946 Some((Opcode::NegAddR, Some(Operand::S))),
947 Some((Opcode::NegSubR, Some(Operand::S))),
948 None,
949 None,
950 ];
951
952 let (opc, extra_operands) = *OPC.get(shift as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?;
953 inst.opcode = opc;
954 if let Some(extra) = extra_operands {
955 inst.operands[0] = extra;
956 inst.operands[1] = dest;
957 } else {
958 inst.operands[0] = dest;
959 }
960 }
961 0b10 => {
962 inst.opcode = Opcode::Shift;
963 inst.operands[0] = dest;
964 inst.operands[1] = Operand::Literal(shift);
965 }
966 // 0b11
967 _ => {
968 inst.opcode = Opcode::Rotate;
969 inst.operands[0] = dest;
970 inst.operands[1] = Operand::Literal(shift);
971 }
972 };
973 }
974 // octal codes 1400, 1500, 1600, 1700
975 0b0011 => {
976 // group 2 instructions
977 #[allow(non_upper_case_globals)]
978 const OPC_Flags: &[Option<(Opcode, Operand)>] = &[
979 Some((Opcode::IDLE, Operand::Nothing)), Some((Opcode::SNZ, Operand::FlagBit)), None, None, None, Some((Opcode::SIZ, Operand::FlagBit)), None, None,
980 Some((Opcode::CLR, Operand::FlagBit)), None, None, None, None, None, None, None,
981 Some((Opcode::CMP, Operand::FlagBit)), None, None, None, None, None, None, None,
982 Some((Opcode::SET, Operand::FlagBit)), None, None, None, None, None, None, None,
983 Some((Opcode::SKPL, Operand::Nothing)), Some((Opcode::SNZ, Operand::OverflowBit)), Some((Opcode::SKIP, Operand::Nothing)), None, None, Some((Opcode::SIZ, Operand::OverflowBit)), None, None,
984 Some((Opcode::CLR, Operand::OverflowBit)), None, None, None, None, None, None, None,
985 Some((Opcode::CMP, Operand::OverflowBit)), None, None, None, None, None, None, None,
986 Some((Opcode::SET, Operand::OverflowBit)), None, None, None, None, None, None, None,
987 ];
988
989 #[allow(non_upper_case_globals)]
990 const OPC_NotFlags: &[Option<Opcode>] = &[
991 None, Some(Opcode::SNZ), Some(Opcode::SIP), None, Some(Opcode::INC), Some(Opcode::SIZ), Some(Opcode::SIN), None,
992 Some(Opcode::CLR), None, None, None, None, None, None, None,
993 Some(Opcode::CMP), None, None, None, Some(Opcode::NEG), None, None, None,
994 Some(Opcode::SET), None, None, None, None, None, None, None,
995 ];
996
997 let jk = (word.0 & 0o0300) >> 6;
998 let opc = word.0 & 0o0077;
999
1000 if opc == 0o0000 {
1001 // idle, pion, piof, or undefined
1002 const OPS: &[Result<Opcode, DecodeError>] = &[
1003 Ok(Opcode::IDLE),
1004 Ok(Opcode::PION),
1005 Ok(Opcode::PIOF),
1006 Err(DecodeError::Undefined)
1007 ];
1008 inst.opcode = OPS[jk as usize]?;
1009 } else {
1010 let (opcode, operand) = match jk {
1011 0b00 => {
1012 *OPC_Flags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?
1013 },
1014 0b01 => {
1015 (*OPC_NotFlags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?, Operand::J)
1016 }
1017 0b10 => {
1018 (*OPC_NotFlags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?, Operand::K)
1019 }
1020 _ => {
1021 (*OPC_NotFlags.get(opc as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?, Operand::JK)
1022 }
1023 };
1024 inst.opcode = opcode;
1025 inst.operands[0] = operand;
1026 }
1027 }
1028 // octal code 2000
1029 0b0100 => {
1030 // literal instructions, `andf`, `andl`, ``addl`, subl`
1031 const OPC: &[Opcode] = &[
1032 Opcode::ANDF,
1033 Opcode::ANDL,
1034 Opcode::ADDL,
1035 Opcode::SUBL,
1036 ];
1037
1038 let opc = (word.0 >> 6) & 0b11;
1039 let literal = (word.0 & 0o0077) as u8;
1040
1041 let opc = OPC[opc as usize];
1042 inst.opcode = opc;
1043 inst.operands = [
1044 Operand::Literal(literal),
1045 Operand::Nothing,
1046 Operand::Nothing,
1047 ];
1048 }
1049 // octal code 7400+
1050 0b1111 => {
1051 // `tif`, `tir`, `trf`, `tis`, ...
1052 let opc = word.0;
1053 if opc < 0o7500 {
1054 const OPC: &[Option<Opcode>] = &[
1055 None, Some(Opcode::TIF), Some(Opcode::TIR), Some(Opcode::TRF), Some(Opcode::TIS), None, None, None,
1056 None, Some(Opcode::TOC), Some(Opcode::TOP), Some(Opcode::TCP), Some(Opcode::TOS), None, None, None,
1057 None, Some(Opcode::HIS), Some(Opcode::HIR), Some(Opcode::HRF), Some(Opcode::HIS), None, None, None,
1058 None, Some(Opcode::HOP), Some(Opcode::HOL), Some(Opcode::HLP), Some(Opcode::HOS), None, None, None,
1059 ];
1060 let idx = opc - 0o7400;
1061 inst.opcode = *OPC.get(idx as usize).and_then(|x| x.as_ref()).ok_or(DecodeError::Undefined)?;
1062 } else if opc < 0o7600 {
1063 return Err(DecodeError::Undefined);
1064 } else if opc < 0o7700 {
1065 inst.opcode = if opc == 0o7601 {
1066 Opcode::CSLCT1
1067 } else if opc == 0o7602 {
1068 Opcode::CSLCT2
1069 } else if opc == 0o7604 {
1070 Opcode::CSLCT3
1071 } else {
1072 return Err(DecodeError::Undefined);
1073 };
1074 } else {
1075 inst.opcode = if opc == 0o7720 {
1076 Opcode::LDREG
1077 } else if opc == 0o7721 {
1078 Opcode::LDJK
1079 } else if opc == 0o7722 {
1080 Opcode::RJIB
1081 } else {
1082 return Err(DecodeError::Undefined);
1083 };
1084 }
1085 }
1086 // remaining instructions are keyed entirely on the upper four bits:
1087 // `smj`, `dsz`, `isz`, `sbj`, `adj`, `ldj`, `stj`, `jmp`, `jps`, `xct`.
1088 // this set of instructions starts at 0o2400.
1089 other => {
1090 const OPC: &[Opcode] = &[
1091 Opcode::SMJ,
1092 Opcode::DSZ,
1093 Opcode::ISZ,
1094 Opcode::SBJ,
1095 Opcode::ADJ,
1096 Opcode::LDJ,
1097 Opcode::STJ,
1098 Opcode::JMP,
1099 Opcode::JPS,
1100 Opcode::XCT,
1101 ];
1102
1103 let offset = (word.0 & 0o0077) as i8;
1104 let negative = (word.0 >> 6) & 1;
1105 let indirect = (word.0 >> 7) & 1;
1106 let offset = if negative == 1 {
1107 -offset
1108 } else {
1109 offset
1110 };
1111
1112 let opc = OPC[(other - (0o2400 >> 8)) as usize];
1113 inst.opcode = opc;
1114 inst.operands = [
1115 Operand::Displacement(indirect == 1, offset),
1116 Operand::Nothing,
1117 Operand::Nothing,
1118 ];
1119 }
1120 }
1121
1122 inst.length = words.offset() as u8;
1123 Ok(())
1124 }
1125}