Initial commit of files related to NED architecture.
authorAaron Taylor <ataylor@subgeniuskitty.com>
Sun, 23 Dec 2018 12:26:28 +0000 (04:26 -0800)
committerAaron Taylor <ataylor@subgeniuskitty.com>
Sun, 23 Dec 2018 12:26:28 +0000 (04:26 -0800)
Includes nedsim/nedasm/neddis and a calculator written in NED assembly.

41 files changed:
LICENSE.txt [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
common/main.mk [new file with mode: 0644]
docs/architecture_manual.md [new file with mode: 0644]
docs/compat_matrix.md [new file with mode: 0644]
docs/design_goals.md [new file with mode: 0644]
docs/instruction_reference.md [new file with mode: 0644]
misc/clocktest.c [new file with mode: 0644]
nedasm/Makefile [new file with mode: 0644]
nedasm/README.md [new file with mode: 0644]
nedasm/nedasm.c [new file with mode: 0644]
nedasm/nedasm_codegen.c [new file with mode: 0644]
nedasm/nedasm_codegen.h [new file with mode: 0644]
nedasm/nedasm_misc.h [new file with mode: 0644]
nedasm/nedasm_parser.c [new file with mode: 0644]
nedasm/nedasm_parser.h [new file with mode: 0644]
nedasm/nedasm_parser_extensions.c [new file with mode: 0644]
nedasm/nedasm_parser_extensions.h [new file with mode: 0644]
nedasm/nedasm_structures.c [new file with mode: 0644]
nedasm/nedasm_structures.h [new file with mode: 0644]
neddis/Makefile [new file with mode: 0644]
neddis/README.md [new file with mode: 0644]
neddis/neddis.c [new file with mode: 0644]
nedsim/Makefile [new file with mode: 0644]
nedsim/README.md [new file with mode: 0644]
nedsim/nedsim.c [new file with mode: 0644]
software/4func_calculator/Makefile [new file with mode: 0644]
software/4func_calculator/README.md [new file with mode: 0644]
software/4func_calculator/calc.asm [new file with mode: 0644]
software/README.md [new file with mode: 0644]
software/assembly_fragments/README.md [new file with mode: 0644]
software/assembly_fragments/ansi_escape.asm [new file with mode: 0644]
software/assembly_fragments/echo.asm [new file with mode: 0644]
software/assembly_fragments/labels.asm [new file with mode: 0644]
software/assembly_fragments/multiply.asm [new file with mode: 0644]
software/assembly_fragments/negation.asm [new file with mode: 0644]
software/assembly_fragments/subroutines/sr_itoa.asm [new file with mode: 0644]
software/assembly_fragments/subroutines/sr_subtract.asm [new file with mode: 0644]
software/assembly_fragments/subroutines/sr_terminalIO.asm [new file with mode: 0644]
software/assembly_fragments/subroutines/subroutines.asm [new file with mode: 0644]

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..d6b0400
--- /dev/null
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..d45d9da
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+# © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+MODULES = nedsim nedasm neddis
+
+all:
+       @for dir in $(MODULES); do \
+               (cd $$dir; ${MAKE} all); \
+       done
+
+clean:
+       @for dir in $(MODULES); do \
+               (cd $$dir; ${MAKE} clean); \
+       done
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..d311c7d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+NED - Numeric Evaluation Device
+===============================
+
+Description
+-----------
+
+NED is an experimental CPU architecture.
+
+The folders `nedsim/`, `nedasm/` and `neddis/` form a simulator, assembler and
+disassembler for the NED architecture. More details can be found in the
+`README.md` located in the top level directory of each sub-project.
+
+Among other things, the `docs/` folder contains an architecture manual,
+instruction reference, and compatibility matrix for the various parts of NED.
+
+The `software/` folder contains assembly programs written for the NED
+architecture.
+
+Status
+------
+
+NED is incomplete and subject to frequent breaking changes.
+
+Installation
+------------
+
+A convenience makefile is included in this top level directory with targets
+`make all` and `make clean` which will compile/remove nedsim/nedasm/neddis.
+Binaries can be used in place or manually copied where desired.
diff --git a/common/main.mk b/common/main.mk
new file mode 100644 (file)
index 0000000..e8c7578
--- /dev/null
@@ -0,0 +1,14 @@
+# © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+####################################################################################################
+# Executables
+
+CC              = cc
+NEDASM          = nedasm
+NEDSIM          = nedsim
+
+####################################################################################################
+# Configuration
+
+CC_FLAGS        = -Wall -std=c99
diff --git a/docs/architecture_manual.md b/docs/architecture_manual.md
new file mode 100644 (file)
index 0000000..262c179
--- /dev/null
@@ -0,0 +1,125 @@
+NED - Architecture Manual
+=========================
+
+Overview
+--------
+
+* Stack based
+* Twos-complement
+* Big-endian
+* Simplicity above all else. See what we can achieve with minimal hardware
+  that's as fast as we can make it. Essentially, let the compiler target the
+  'microcode' level.
+* Explicitly no kernel/user mode.
+* Explicitly no MMU, just a flat, shared memory space. Think threads, not
+  processes.
+
+Version: 1
+
+Instruction Word Formats
+------------------------
+
+All instruction words in NED are 32-bits long and fall into one of three
+possible formats.
+
+    +---------+-----+-----+--------+--------+--------+--------+--------+
+    |  Format |  31 |  30 | 29..24 | 23..18 | 17..12 |  11..6 |  5..0  |
+    +---------+-----+-----+--------+--------+--------+--------+--------+
+    |    A    |  1  |                  31-bit Immediate                |
+    +---------+-----+-----+--------------------------------------------+
+    |    B    |  0  |  1  |               Unassigned                   |
+    +---------+-----+-----+--------+--------+--------+--------+--------+
+    |    C    |  0  |  0  | Syl. 1 | Syl. 2 | Syl. 3 | Syl. 4 | Syl. 5 |
+    +---------+-----+-----+--------+--------+--------+--------+--------+
+
+Format A contains a 31-bit field which is placed on the stack after shifting
+left one position and padding with a zero. For example, the instruction
+
+    11000000 01000000 01000000 01000000
+
+will place the value
+
+    10000000 10000000 10000000 10000000 
+
+on the TOS.
+
+Format B is reserved for future instructions.
+
+Format C packs five syllables per instruction word, executed in order from 1 to 5.
+Syllables are defined as follows.
+
+|  Bits  |  Name  |  Description                                               |
+| ------ | ------ | ---------------------------------------------------------- |
+| 1xxxxx | IM_x   | Push a 5-bit immediate to TOS.                             |
+| 011xxx | LDSP+x | Copy the value stored at TOS+x and push to TOS.            |
+| 010xxx | STSP+x | Pop from TOS and overwrite value at TOS+x.                 |
+| 001000 | AND    | Logical AND of TOS and NOS, pushed to TOS.                 |
+| 001001 | OR     | Logical OR of TOS and NOS, pushed to TOS.                  |
+| 001010 | NOT    | Logical NOT of TOS, pushed to TOS.                         |
+| 001011 | XOR    | Logical XOR of TOS and NOS, pushed to TOS.                 |
+| 001100 | ADD    | Signed, twos-complement addition. TOS <- TOS + NOS         |
+| 001101 | SWAP   | Swap TOS and NOS.                                          |
+| 001110 | JMP    | Pop TOS and set PC to popped value.                        |
+| 001111 | MVSTCK | Pop the TOS and context switch to that state ID.           |
+| 000100 | SHIFT  | Pop TOS & NOS. Shift NOS by TOS bits to the left/right.    |
+| 000101 | CMPSWP | Compare-and-swap with ptr, old_val & new_val on stack.     |
+| 000110 | TEST   | Pop TOS and set PSW according to value.                    |
+| 000111 | BRZ    | Pop TOS. If PSW_Z==1, then set PC to popped value.         |
+| 000010 | LOAD   | Pop address from TOS, dereference and store to TOS.        |
+| 000011 | STORE  | Pop address from TOS, pop data from NOS, deref and store.  |
+| 000001 | NOP    | Do nothing.                                                |
+| 000000 | HALT   | Halt the CPU.                                              |
+
+The Instruction Reference contains a more complete description of these operations.
+
+Processor State
+---------------
+
+The Processor Status Word (PSW) contains *N*egative and *Z*ero flags in the
+following bit positions.  See the Instruction Reference for details on which
+operations set these flags.
+
+    +-------+------------+-----+-----+
+    |  Bits |    31..2   |  1  |  0  |
+    +-------+------------+-----+-----+
+    | Flags | Unassigned |  N  |  Z  |
+    +-------+------------+-----+-----+
+
+In addition to a traditional Program Counter (PC), the processor also includes
+a Syllable Counter (SC) which ranges from 0 to 4. Both the PC and SC point to
+the next instruction to be executed. Thus, when executing the middle syllable
+of a word located at address 0x200, the PC is 0x204 and the SC is 3.
+
+Since the CPU is stack based, it includes a Stack Pointer (SP).
+
+Memory Map
+----------
+
+The address space is laid out with memory mapped I/O below 512 MB and RAM above.
+
+    4 GB
+    |-------- Data
+    512 MB
+    |-------- I/O (general purpose)
+    128 MB
+    |-------- I/O (reserved for processors in 16 MB chucks x 8 CPUs)
+    0 Mb
+
+Temporarily, there are four read-only registers accessible in the lowest four
+words of memory. Writes to these registers are ignored.
+
+| Address |       Name        |  Description                    |
+| ------- | ----------------- | ------------------------------- |
+|     0x0 | Zero Register     | Constant 0x0                    |
+|     0x4 | Negative Register | Constant 0x80000000             |
+|     0x8 | PC Register       | PC of currently active thread.  |
+|     0xC | PSW Register      | PSW of currently active thread. |
+
+Also temporarily, a UART-like peripheral is present with the following registers.
+
+|  Address  |     Name        |  Description                                    |
+| --------- | --------------- | ----------------------------------------------- |
+| 0x8000000 | Transmit Buffer | Accepts one byte and transmits to stdout.       |
+| 0x8000004 | Transmit Status | Bit 0 is set when UART is ready to send a byte. |
+| 0x8000008 | Receive Buffer  | Contains one byte from stdin.                   |
+| 0x800000C | Receive Status  | Bit 0 is set when a byte is ready to be read.   |
diff --git a/docs/compat_matrix.md b/docs/compat_matrix.md
new file mode 100644 (file)
index 0000000..7e4bd2b
--- /dev/null
@@ -0,0 +1,12 @@
+Overview
+========
+
+This file tracks compatibility between different versions of NED related
+projects.
+
+Compatibility Matrix
+====================
+
+| nedsim | nedasm | neddis | Arch. Man. | Inst. Ref. |
+| ------ | ------ | ------ | ---------- | ---------- |
+|      1 |      1 |      1 |          1 |          1 |
diff --git a/docs/design_goals.md b/docs/design_goals.md
new file mode 100644 (file)
index 0000000..8cddd3c
--- /dev/null
@@ -0,0 +1,25 @@
+Goals
+=====
+
+* Build real hardware to maintain interest in software development.
+* Sanity check before attempting a workstation-capable machine.
+* Develop better hardware intuition.
+* Include sufficient capability to handle a real application after completion
+  of experimentation. A calculator or instrument controller would be
+  satisfactory.
+* Establish a performance mark against which to predict/compare future designs.
+
+Principles
+==========
+
+* Simplicity.
+* Let the implementation guide the definition.
+* Keep options open. Postpone decisions where possible.
+* Design for debugging and rework. The CPU should be easily instrumented.
+* Defining what IS NOT is as important as defining what IS.
+* Don't add functionality unless a real application requires it.
+* Don't get bogged down in I/O. Use modern hardware where helpful.
+* Worse is better. If 90% functionality can be implemented for 10% effort, do
+  it and move on.
+* The only thing worse than generalizing from a single example is generalizing
+  from no example at all.
diff --git a/docs/instruction_reference.md b/docs/instruction_reference.md
new file mode 100644 (file)
index 0000000..d8bc473
--- /dev/null
@@ -0,0 +1,267 @@
+NED - Instruction Reference
+===========================
+
+Overview
+--------
+
+Version: 1
+
+Format A - Word
+---------------
+
+    +----------+----+-------+
+    |          | 31 | 30..0 |
+    +----------+----+-------+
+    |   WORD_x |  1 |  x..x |
+    +----------+----+-------+
+
+Operation: TOS = x
+Indicators: None
+Description: Pushes bits 30-0 on to TOS after left shift by one position and
+             padding with zero bit.
+
+Format C - Syllables
+--------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |     IM_x | 1 | x | x | x | x | x |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = x
+Indicators: None
+Description: Pushes bits 4-0 on to TOS while padding bits 31-5 with zeroes.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |   LDSP+x | 0 | 1 | 1 | x | x | x |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = TOS[x]
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pushes a copy of the x'th entry on the stack to the TOS. Indexing
+             begins at 0. Thus, LDSP+0 duplicates the top stack entry and
+             LDSP+1 pushes a copy of the NOS to the TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |   STSP+x | 0 | 1 | 0 | x | x | x |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS[x] = TOS
+Indicators: None
+Description: Pops TOS and stores it x positions deep, overwriting the
+             pre-existing stack entry. Indexing begins at 0 and is counted
+             after popping the TOS. Thus, STSP+0 deletes the NOS, equivalent
+             to SWAP, DROP if such instructions existed.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      AND | 0 | 0 | 1 | 0 | 0 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = TOS ^ NOS
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS and NOS, performs bitwise Boolean AND and pushes result
+             to TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |       OR | 0 | 0 | 1 | 0 | 0 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = TOS V NOS 
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS and NOS, performs bitwise Boolean OR and pushes result
+             to TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      NOT | 0 | 0 | 1 | 0 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = ~TOS
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS, performs bitwise Boolean NOT and pushes result to TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      XOR | 0 | 0 | 1 | 0 | 1 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = TOS >< NOS
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS and NOS, performs bitwise Boolean XOR and pushes result
+             to TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      ADD | 0 | 0 | 1 | 1 | 0 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = TOS + NOS
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS and NOS, performs signed, twos-complement addition and
+             pushes result to TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |     SWAP | 0 | 0 | 1 | 1 | 0 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = NOS; NOS = TOS
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Swaps the TOS and NOS entries on the stack. Sets PSW flags based
+             on new TOS entry.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      JMP | 0 | 0 | 1 | 1 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: PC = TOS
+Indicators: None
+Description: Pops TOS and loads value in to PC, overwriting existing PC. Resets
+             SC to zero.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |   MVSTCK | 0 | 0 | 1 | 1 | 1 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: SID = TOS
+Indicators: None
+Description: Pops TOS and loads value in to SID, overwriting existing SID.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |    SHIFT | 0 | 0 | 0 | 1 | 0 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = NOS (<< or >>) TOS
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS and NOS, shifts NOS according to TOS and pushes back to
+             TOS. Direction of shift is left if bit 31 of TOS is set, otherwise
+             shift is right. Magnitude of shift is controlled by bits 30-0 of
+             TOS. Note that this is sign-magnitude, not twos-complement.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |   CMPSWP | 0 | 0 | 0 | 1 | 0 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation:
+Indicators:
+Description: Not yet implemented.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |     TEST | 0 | 0 | 0 | 1 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: None
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS and sets PSW flags accordingly.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      BRZ | 0 | 0 | 0 | 1 | 1 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: PC = TOS if PSW_Z = 0
+Indicators: None
+Description: Pops TOS. If PSW_Z = 0, loads value in to PC, pushing old PC on to
+             TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |     LOAD | 0 | 0 | 0 | 0 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: TOS = (TOS)
+Indicators: Sets PSW_N and PSW_Z flags.
+Description: Pops TOS, treating it as a pointer in to RAM and pushing the
+             corresponding value from RAM on to TOS.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |    STORE | 0 | 0 | 0 | 0 | 1 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: (TOS) = NOS
+Indicators: None
+Description: Pops TOS and NOS, treating TOS as a pointer in to RAM and storing
+             NOS at that address.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |      NOP | 0 | 0 | 0 | 0 | 0 | 1 |
+    +----------+---+---+---+---+---+---+
+
+Operation: None
+Indicators: None
+Description: Does nothing for one CPU cycle.
+
+--------------------------------------------------------------------------------
+
+    +----------+---+---+---+---+---+---+
+    |          | 5 | 4 | 3 | 2 | 1 | 0 |
+    +----------+---+---+---+---+---+---+
+    |     HALT | 0 | 0 | 0 | 0 | 0 | 0 |
+    +----------+---+---+---+---+---+---+
+
+Operation: None
+Indicators: None
+Description: Halts the CPU.
diff --git a/misc/clocktest.c b/misc/clocktest.c
new file mode 100644 (file)
index 0000000..c3d3817
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <time.h>
+
+int
+main(int argc, char ** argv)
+{
+    struct timespec ts = {0,0};
+    int retval;
+
+    retval = clock_getres(CLOCK_MONOTONIC, &ts);
+    if (!retval) {
+        printf("Clock resolution: %ld s -- %ld ns\n", ts.tv_sec, ts.tv_nsec);
+    }
+
+    retval = clock_gettime(CLOCK_MONOTONIC, &ts);
+    if (!retval) {
+        printf("Current clock: %ld s -- %ld ns\n", ts.tv_sec, ts.tv_nsec);
+    }
+}
diff --git a/nedasm/Makefile b/nedasm/Makefile
new file mode 100644 (file)
index 0000000..93fd797
--- /dev/null
@@ -0,0 +1,14 @@
+# © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+include ../common/main.mk
+
+SRC != ls *.c
+
+all: nedasm
+
+nedasm:
+       $(CC) -o $@ $(SRC)
+
+clean:
+       @rm -f nedasm
diff --git a/nedasm/README.md b/nedasm/README.md
new file mode 100644 (file)
index 0000000..f67f010
--- /dev/null
@@ -0,0 +1,25 @@
+Overview
+========
+
+An assembler for the NED architecture.
+
+Status
+======
+
+Functional without restrictions.
+
+Installation
+============
+
+Use `make` and `make clean` to build/remove nedasm files. System installation
+requires manually copying the binary in to place.
+
+Operation
+=========
+
+Use `nedasm -h` to see current command-line options.
+
+Note that the assembler will refuse to overwrite an existing file.
+
+Until a syntax reference is written, see the `ned/software` folder for working
+syntax examples.
diff --git a/nedasm/nedasm.c b/nedasm/nedasm.c
new file mode 100644 (file)
index 0000000..4b4d3bc
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "nedasm_structures.h"
+#include "nedasm_misc.h"
+#include "nedasm_parser.h"
+#include "nedasm_codegen.h"
+
+#define VERSION 1
+
+void
+print_usage(char ** argv)
+{
+    printf( "NED Assembler v%d (www.subgeniuskitty.com)\n"
+            "Usage: %s [option] ...\n"
+            "  -h         Help (prints this message)\n"
+            "  -i <file>  Specify file name of assembly code input.\n"
+            "  -o <file>  Specify file name for assembled output.\n"
+            , VERSION, argv[0]
+    );
+}
+
+void
+enforce_word_boundary(struct instruction * instructions)
+{
+    uint8_t syllable_counter = 0;
+    struct instruction * tmp_instructions = instructions;
+    tmp_instructions = seek_instruction_list_start(tmp_instructions);
+    while (tmp_instructions != NULL) {
+        if (tmp_instructions->syllable == WORD) {
+            if (syllable_counter) insert_NOP_structs_before(tmp_instructions, (5-syllable_counter));
+            syllable_counter = 0;
+        } else if (tmp_instructions->syllable == LABEL) {
+            struct instruction * temp = tmp_instructions;
+            while (temp->syllable == LABEL) temp = temp->prev;
+            if (syllable_counter) insert_NOP_structs_after(temp, (5-syllable_counter));
+            syllable_counter = 0;
+        } else if (tmp_instructions->syllable != WORD) {
+            syllable_counter = (syllable_counter + 1) % 5;
+        }
+        tmp_instructions = tmp_instructions->next;
+    }
+    if (syllable_counter != 0) {
+        instructions = seek_instruction_list_end(instructions);
+        insert_NOP_structs_after(instructions, (5-syllable_counter));
+    }
+}
+
+void
+assign_addresses(struct instruction * instructions)
+{
+    instructions = seek_instruction_list_start(instructions);
+    uint32_t offset = 0;
+    uint8_t increment = 4; /* Bytes per word */
+    while (instructions != NULL) {
+        if (instructions->syllable == LABEL) {
+            instructions = instructions->next;
+        } else {
+            if (instructions->syllable == WORD) {
+                instructions->address = MEM_BEGIN + offset;
+                instructions = instructions->next;
+            } else {
+                for (int i=0; i<5; i++) {
+                    instructions->address = MEM_BEGIN + offset;
+                    instructions = instructions->next;
+                }
+            }
+            offset += increment;
+        }
+    }
+}
+
+void
+resolve_labels(struct instruction * instructions)
+{
+    instructions = seek_instruction_list_start(instructions);
+    while (instructions != NULL) {
+        if (instructions->syllable == WORD && instructions->target != NULL) {
+            struct instruction * temp = seek_instruction_list_start(instructions);
+            while (temp != NULL) {
+                if (temp->syllable == LABEL) {
+                    if (strncmp(instructions->target, temp->label, MAX_LABEL_LEN) == 0) {
+                        while ((temp->syllable == LABEL) && (temp->next != NULL)) {
+                            temp = temp->next;
+                        }
+                        if (temp->syllable != LABEL) {
+                            instructions->data = temp->address;
+                            break;
+                        }
+                    }
+                }
+                temp = temp->next;
+            }
+            if (instructions->data == 0) {
+                fprintf(stderr,"ERROR: Failed to resolve label %s on line %u.\n",
+                    instructions->target, instructions->linenum);
+                exit(EXIT_FAILURE);
+            }
+        }
+        instructions = instructions->next;
+    }
+}
+
+void
+prune_label_structs(struct instruction * instructions)
+{
+    instructions = seek_instruction_list_start(instructions);
+    while (instructions != NULL) {
+        if (instructions->syllable == LABEL) {
+            instructions = remove_instruction_struct(instructions);
+        }
+        instructions = instructions->next;
+    }
+}
+
+void
+catch_double_label_declarations(struct instruction * instructions)
+{
+    instructions = seek_instruction_list_start(instructions);
+    while (instructions != NULL) {
+        if (instructions->syllable == LABEL) {
+            struct instruction * tmp_instructions = instructions->next;
+            while (tmp_instructions != NULL) {
+                if (tmp_instructions->syllable == LABEL) {
+                    if (strncmp(instructions->label,
+                            tmp_instructions->label, MAX_LABEL_LEN) == 0
+                    ) {
+                        fprintf(stderr,
+                            "ERROR: Multiple declarations of label %s on lines %d and %d.\n",
+                            instructions->label, instructions->linenum,
+                            tmp_instructions->linenum
+                        );
+                        exit(EXIT_FAILURE);
+                    }
+                }
+                tmp_instructions = tmp_instructions->next;
+            }
+        }
+        instructions = instructions->next;
+    }
+}
+
+int
+main(int argc, char ** argv)
+{
+    /*
+     * Process command line arguments
+     */
+    int c = -1;
+    FILE * input = NULL;
+    FILE * output = NULL;
+    while ((c = getopt(argc,argv,"i:o:h")) != -1) {
+        switch (c) {
+            case 'i':
+                if ((input = fopen(optarg, "r")) == NULL) {
+                    fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
+                }
+                break;
+            case 'o':
+                /* TODO: Don't create the output file until we know the input was valid. */
+                if ((output = fopen(optarg, "wx")) == NULL) {
+                    fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
+                }
+                break;
+            case 'h':
+                print_usage(argv);
+                exit(EXIT_SUCCESS);
+                break;
+            default:
+                break;
+        }
+    }
+    if (input == NULL || output == NULL) {
+        fprintf(stderr, "ERROR: Must specify input and output files.\n");
+        print_usage(argv);
+        exit(EXIT_FAILURE);
+    }
+
+    /*
+     * Data structure for intermediate representation.
+     */
+    struct instruction * instructions = create_instruction_struct();
+    instructions->syllable = NOP;
+
+    /*
+     * Parse input file and generate intermediate representation.
+     */
+    parse_assembly(&instructions, input);
+    fclose(input);
+//    if (instructions == NULL) {
+//        fprintf(stderr, "ERROR: Failed to parse any input.\n");
+//       exit(EXIT_FAILURE);
+//    }
+
+    /*
+     * Make passes over the intermediate representation to enforce architecture
+     * rules and prepare for code generation.
+     */
+    catch_double_label_declarations(instructions);
+    enforce_word_boundary(instructions);
+    assign_addresses(instructions);
+    resolve_labels(instructions);
+    prune_label_structs(instructions);
+
+    generate_code(instructions, output);
+
+    /*
+     * Cleanup and exit
+     */
+    fclose(output);
+    exit(EXIT_SUCCESS);
+}
diff --git a/nedasm/nedasm_codegen.c b/nedasm/nedasm_codegen.c
new file mode 100644 (file)
index 0000000..9484a1d
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "nedasm_structures.h"
+
+void
+write_word_to_file(uint32_t * word, FILE * file)
+{
+    fwrite(word, 4, 1, file);
+}
+
+void
+pad_word_boundary(uint8_t * syllable_count, uint32_t * word, FILE * output)
+{
+    if (*syllable_count > 0) {
+        while (*syllable_count <= 4) {
+            *word |= 0b000001 << 6 * (4 - (*syllable_count)++);
+        }
+        write_word_to_file(word, output);
+        *syllable_count = 0;
+        *word = 0;
+    }
+}
+
+void
+generate_code_WORD(struct instruction * instructions, FILE * output)
+{
+    /* Set the instruction format to Type A. */
+    uint32_t temp_word = 0b10000000000000000000000000000000;
+
+    /* Since x in WORD_x will be shifted left by one position, check for validity. */
+    if (instructions->data % 2 != 0) {
+        fprintf(stderr, "ERROR: Unable to use odd value with WORD on line %d.\n",
+            instructions->linenum);
+        exit(EXIT_FAILURE);
+    }
+    if (instructions->data > 0b01111111111111111111111111111111) {
+        fprintf(stderr, "ERROR: Value too large for WORD on line %d.\n",
+            instructions->linenum);
+        exit(EXIT_FAILURE);
+    }
+
+    /* Set the data portion of the instruction. */
+    temp_word |= instructions->data >> 1;
+
+    /* Write to disk. */
+    write_word_to_file(&temp_word, output);
+}
+
+void
+generate_code_IM(struct instruction * instructions, FILE * output,
+                 uint8_t * syllable_count, uint32_t * temp_word)
+{
+    uint8_t temp_syllable = 0b00100000;
+    if (instructions->data > 0b11111) {
+        fprintf(stderr, "ERROR: Value too large for IM on line %d.\n",
+            instructions->linenum);
+        exit(EXIT_FAILURE);
+    }
+    temp_syllable |= instructions->data;
+    *temp_word |= temp_syllable << 6 * (4 - *syllable_count);
+}
+
+void
+generate_code_LDSP(struct instruction * instructions, FILE * output,
+                   uint8_t * syllable_count, uint32_t * temp_word)
+{
+    uint8_t temp_syllable = 0b00011000;
+    if (instructions->data > 0b111) {
+        fprintf(stderr, "ERROR: Value too large for LDSP on line %d.\n",
+            instructions->linenum);
+        exit(EXIT_FAILURE);
+    }
+    temp_syllable |= instructions->data;
+    *temp_word |= temp_syllable << 6 * (4 - *syllable_count);
+}
+
+void
+generate_code_STSP(struct instruction * instructions, FILE * output,
+                   uint8_t * syllable_count, uint32_t * temp_word)
+{
+    uint8_t temp_syllable = 0b00010000;
+    if (instructions->data > 0b111) {
+        fprintf(stderr, "ERROR: Value too large for STSP on line %d.\n",
+            instructions->linenum);
+        exit(EXIT_FAILURE);
+    }
+    temp_syllable |= instructions->data;
+    *temp_word |= temp_syllable << 6 * (4 - *syllable_count);
+}
+
+void
+generate_code(struct instruction * instructions, FILE * output)
+{
+    uint8_t syllable_count = 0;
+    uint32_t temp_word = 0;
+
+    instructions = seek_instruction_list_start(instructions);
+    while (instructions != NULL) {
+        /* If starting a new word, zero the word, setting it to Type C by default. */
+        if (syllable_count == 0) temp_word = 0b00000000000000000000000000000000;
+
+        switch (instructions->syllable) {
+            case WORD:
+                /* Must pad partial word w/NOPs & write to disk before starting new WORD. */
+                pad_word_boundary(&syllable_count, &temp_word, output);
+                generate_code_WORD(instructions, output);
+                break;
+            case IM:
+                generate_code_IM(instructions, output, &syllable_count, &temp_word);
+                break;
+            case LDSP:
+                generate_code_LDSP(instructions, output, &syllable_count, &temp_word);
+                break;
+            case STSP:
+                generate_code_STSP(instructions, output, &syllable_count, &temp_word);
+                break;
+            case MVSTCK:    temp_word |= 0b001111 << 6 * (4 - syllable_count);  break;
+            case ADD:       temp_word |= 0b001100 << 6 * (4 - syllable_count);  break;
+            case XOR:       temp_word |= 0b001011 << 6 * (4 - syllable_count);  break;
+            case NOT:       temp_word |= 0b001010 << 6 * (4 - syllable_count);  break;
+            case OR:        temp_word |= 0b001001 << 6 * (4 - syllable_count);  break;
+            case AND:       temp_word |= 0b001000 << 6 * (4 - syllable_count);  break;
+            case BRZ:       temp_word |= 0b000111 << 6 * (4 - syllable_count);  break;
+            case TEST:      temp_word |= 0b000110 << 6 * (4 - syllable_count);  break;
+            case CMPSWP:    temp_word |= 0b000101 << 6 * (4 - syllable_count);  break;
+            case SHIFT:     temp_word |= 0b000100 << 6 * (4 - syllable_count);  break;
+            case STORE:     temp_word |= 0b000011 << 6 * (4 - syllable_count);  break;
+            case LOAD:      temp_word |= 0b000010 << 6 * (4 - syllable_count);  break;
+            case NOP:       temp_word |= 0b000001 << 6 * (4 - syllable_count);  break;
+            case HALT:      temp_word |= 0b000000 << 6 * (4 - syllable_count);  break;
+            case SWAP:      temp_word |= 0b001101 << 6 * (4 - syllable_count);  break;
+            case JMP:       temp_word |= 0b001110 << 6 * (4 - syllable_count);  break;
+            default:
+                fprintf(stderr, "ERROR: Unassigned syllable on line %u.\n", instructions->linenum);
+                break;
+        }
+
+        if (syllable_count == 4) write_word_to_file(&temp_word, output);
+
+        if (instructions->syllable != WORD) syllable_count = (syllable_count + 1) % 5;
+
+        instructions = instructions->next;
+    }
+
+    /* If necessary, pad incomplete word with NOPs. */
+    pad_word_boundary(&syllable_count, &temp_word, output);
+}
diff --git a/nedasm/nedasm_codegen.h b/nedasm/nedasm_codegen.h
new file mode 100644 (file)
index 0000000..64af0be
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#ifndef NEDASM_CODEGEN_H
+#define NEDASM_CODEGEN_H
+
+#include "nedasm_structures.h"
+
+void generate_code(struct instruction * instructions, FILE * output);
+
+#endif
diff --git a/nedasm/nedasm_misc.h b/nedasm/nedasm_misc.h
new file mode 100644 (file)
index 0000000..1f43b12
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#ifndef NEDASM_MISC_H
+#define NEDASM_MISC_H
+
+/* Max length, including null terminator, of assembly mnemonics. */
+/* For example, the alphabetic part of ADD, LDSP+4, IM_3, etc. */
+#define MAX_MNEMONIC_LEN 8
+
+/* Max length, including null terminator, of assembly data. */
+/* For example, the numeric part of LDSP+4, IM_3, etc. */
+#define MAX_DATA_LEN 16
+
+/* Max length, including null terminator, of labels. */
+/* For example, "multiply", "init", etc. */
+#define MAX_LABEL_LEN 256
+
+// TODO: Turn this into a command line option
+/* Address at which code will be loaded. */
+#define MEM_BEGIN 0x20000000
+
+#endif
diff --git a/nedasm/nedasm_parser.c b/nedasm/nedasm_parser.c
new file mode 100644 (file)
index 0000000..4dc2a41
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "nedasm_structures.h"
+#include "nedasm_misc.h"
+#include "nedasm_parser_extensions.h"
+
+void
+seek_newline(uint32_t * linenum, FILE * input)
+{
+    int c;
+    while ((c = fgetc(input)) != EOF) {
+        if (c == '\n') {
+            (*linenum)++;
+            return;
+        }
+    }
+    ungetc(EOF, input);
+}
+
+struct instruction *
+parse_mnemonic(uint32_t * linenum, FILE * input, struct instruction * instructions)
+{
+    int c;
+    char mnemonic[MAX_MNEMONIC_LEN];
+    size_t mnemonic_index = 0;
+    char data[MAX_DATA_LEN];
+    size_t data_index = 0;
+    char label[MAX_LABEL_LEN];
+    size_t label_index = 0;
+
+    while ((c = fgetc(input)) != EOF) {
+        if (c == '_' || c == '+' || c == '>') {
+            continue;
+        } else if (isupper(c)) {
+            if (mnemonic_index >= MAX_MNEMONIC_LEN-2) { /* -2 to reserve space for '\0'. */
+                fprintf(stderr, "ERROR: Violating MAX_MNEMONIC_LEN on line %d.\n", *linenum);
+                exit(EXIT_FAILURE);
+            }
+            mnemonic[mnemonic_index++] = c;
+        } else if (isdigit(c)) {
+            if (data_index >= MAX_DATA_LEN-2) { /* -2 to reserve space for '\0'. */
+                fprintf(stderr, "ERROR: Violating MAX_DATA_LEN on line %d.\n", *linenum);
+                exit(EXIT_FAILURE);
+            }
+            data[data_index++] = c;
+        } else if (islower(c)) {
+            if (label_index >= MAX_LABEL_LEN-2) { /* -2 to reserve space for '\0'. */
+                fprintf(stderr, "ERROR: Violating MAX_LABEL_LEN on line %d.\n", *linenum);
+                exit(EXIT_FAILURE);
+            }
+            label[label_index++] = c;
+        } else {
+            ungetc(c, input);
+            break;
+        }
+    }
+    mnemonic[mnemonic_index] = '\0';
+    data[data_index] = '\0';
+    label[label_index] = '\0';
+    if (c == EOF) ungetc(EOF, input);
+
+    struct instruction * new_instruction = create_instruction_struct();
+
+    new_instruction->linenum = *linenum;
+
+    if (strncmp(mnemonic, "WORD", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = WORD;
+        new_instruction->data = strtoimax(data, NULL, 10);
+    } else if (strncmp(mnemonic, "IM", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = IM;
+        new_instruction->data = strtoimax(data, NULL, 10);
+    } else if (strncmp(mnemonic, "LDSP", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = LDSP;
+        new_instruction->data = strtoimax(data, NULL, 10);
+    } else if (strncmp(mnemonic, "STSP", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = STSP;
+        new_instruction->data = strtoimax(data, NULL, 10);
+    } else if (strncmp(mnemonic, "MVSTCK", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = MVSTCK;
+    } else if (strncmp(mnemonic, "ADD", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = ADD;
+    } else if (strncmp(mnemonic, "XOR", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = XOR;
+    } else if (strncmp(mnemonic, "NOT", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = NOT;
+    } else if (strncmp(mnemonic, "OR", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = OR;
+    } else if (strncmp(mnemonic, "AND", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = AND;
+    } else if (strncmp(mnemonic, "BRZ", MAX_MNEMONIC_LEN) == 0) {
+        if (label_index) {
+            new_instruction->syllable = WORD;
+            new_instruction->target = malloc(label_index+1);
+            strncpy(new_instruction->target, label, label_index+1);
+            new_instruction->next = create_instruction_struct();
+            new_instruction->next->prev = new_instruction;
+            new_instruction->next->syllable = BRZ;
+            new_instruction->linenum = *linenum;
+        } else {
+            new_instruction->syllable = BRZ;
+        }
+    } else if (strncmp(mnemonic, "TEST", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = TEST;
+    } else if (strncmp(mnemonic, "CMPSWP", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = CMPSWP;
+    } else if (strncmp(mnemonic, "SHIFT", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = SHIFT;
+    } else if (strncmp(mnemonic, "STORE", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = STORE;
+    } else if (strncmp(mnemonic, "LOAD", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = LOAD;
+    } else if (strncmp(mnemonic, "NOP", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = NOP;
+    } else if (strncmp(mnemonic, "HALT", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = HALT;
+    } else if (strncmp(mnemonic, "JMP", MAX_MNEMONIC_LEN) == 0) {
+        if (label_index) {
+            new_instruction->syllable = WORD;
+            new_instruction->target = malloc(label_index+1);
+            strncpy(new_instruction->target, label, label_index+1);
+            new_instruction->next = create_instruction_struct();
+            new_instruction->next->prev = new_instruction;
+            new_instruction->next->syllable = JMP;
+            new_instruction->linenum = *linenum;
+        } else {
+            new_instruction->syllable = JMP;
+        }
+    } else if (strncmp(mnemonic, "SWAP", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = SWAP;
+    } else {
+        /* Fake/composite mnemonics. Not part of the NED ISA. */
+        new_instruction = parse_pseudo_mnemonic(mnemonic, mnemonic_index, data, data_index,
+                label, label_index, new_instruction);
+    }
+
+    if (new_instruction->syllable == 0) {
+        fprintf(stderr, "ERROR: Unable to match mnemonic on line %d.\n", *linenum);
+        exit(EXIT_FAILURE);
+    }
+
+    if (instructions != NULL) {
+        instructions = seek_instruction_list_end(instructions);
+        new_instruction = seek_instruction_list_start(new_instruction);
+        new_instruction->prev = instructions;
+        instructions->next = new_instruction;
+    } else {
+        instructions = new_instruction;
+    }
+
+    instructions = seek_instruction_list_end(instructions);
+
+    return instructions;
+}
+
+struct instruction *
+parse_dest_label(uint32_t * linenum, FILE * input, struct instruction * instructions)
+{
+    instructions = seek_instruction_list_end(instructions);
+
+    char word[MAX_LABEL_LEN];
+    size_t i = 0;
+    int c;
+
+    while ((c = fgetc(input)) != EOF) {
+        /* TODO: Accept underscores or hyphens inside labels. */
+        if (!islower(c)) break;
+        word[i++] = c;
+        if (i >= MAX_LABEL_LEN) {
+            fprintf(stderr, "ERROR: Exceeded max label length on line %d.\n", *linenum);
+            exit(EXIT_FAILURE);
+        }
+    }
+    ungetc(c, input);
+    word[i] = '\0';
+
+    struct instruction * new_instruction = create_instruction_struct();
+    new_instruction->syllable = LABEL;
+    new_instruction->linenum = *linenum;
+    new_instruction->label = malloc(i+1);
+    strncpy(new_instruction->label, word, i+1);
+
+    if (instructions != NULL) {
+        instructions->next = new_instruction;
+        new_instruction->prev = instructions;
+    } else {
+        instructions = new_instruction;
+    }
+
+    return instructions;
+}
+
+void
+parse_assembly(struct instruction ** instructions, FILE * input)
+{
+    int c;
+    uint32_t linenum = 1;
+    while ((c = fgetc(input)) != EOF) {
+        if (c == '#') {
+            seek_newline(&linenum, input);
+        } else if (c == ' ' || c == '\t') {
+            /* Intentionally empty in order to skip whitespace. */
+        } else if (c == '\n') {
+            linenum++;
+        } else if (isupper(c)) { /* Mnemonics start with uppercase alpha char. */
+            ungetc(c, input);
+            *instructions = parse_mnemonic(&linenum, input, *instructions);
+        } else if (islower(c)) { /* Labels start with lowercase alpha char. */
+            ungetc(c, input);
+            *instructions = parse_dest_label(&linenum, input, *instructions);
+        } else {
+            fprintf(stderr, "ERROR: Invalid syntax on line %d\n", linenum);
+            exit(EXIT_FAILURE);
+        }
+    }
+}
diff --git a/nedasm/nedasm_parser.h b/nedasm/nedasm_parser.h
new file mode 100644 (file)
index 0000000..62c247f
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#ifndef NEDASM_PARSER_H
+#define NEDASM_PARSER_H
+
+#include "nedasm_structures.h"
+
+// TODO: Note that this does not hand back an IR that would be executable. No word alignment or other functions have been performed. It's more like a literal translation of the assembly file with addresses in place of labels.
+
+void parse_assembly(struct instruction ** instructions, FILE * input);
+
+#endif
diff --git a/nedasm/nedasm_parser_extensions.c b/nedasm/nedasm_parser_extensions.c
new file mode 100644 (file)
index 0000000..d02e62b
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "nedasm_structures.h"
+#include "nedasm_misc.h"
+
+struct instruction *
+parse_pseudo_mnemonic(char * mnemonic, size_t mnemonic_index, char * data, size_t data_index,
+                      char * label, size_t label_index, struct instruction * new_instruction)
+{
+    uint32_t linenum = new_instruction->linenum;
+
+    if (strncmp(mnemonic, "JSR", MAX_MNEMONIC_LEN) == 0) {
+        if (label_index) {
+            new_instruction->syllable = WORD;
+            new_instruction->target = malloc(label_index+1);
+            strncpy(new_instruction->target, label, label_index+1);
+            new_instruction->next = create_instruction_struct();
+            new_instruction->next->prev = new_instruction;
+            new_instruction = new_instruction->next;
+        }
+        /* JSR must expand to exactly one word (five syllables).                        */
+        /* Otherwise, trailing syllables in the same word are skipped when RTS returns. */
+        // TODO: This should be on a word boundary, regardless of whether a WORD preceded it.
+        for (int i=0; i<5; i++) {
+            switch (i) {
+                case 0:
+                    new_instruction->syllable = IM;
+                    new_instruction->data = 0x08; /* Address of PC register */
+                    break;
+                case 1:
+                    new_instruction->syllable = LOAD;
+                    break;
+                case 2:
+                    new_instruction->syllable = SWAP;
+                    break;
+                case 3:
+                    new_instruction->syllable = JMP;
+                    break;
+                case 4:
+                    new_instruction->syllable = NOP;
+                    break;
+                default:
+                    break;
+            }
+            new_instruction->linenum = linenum;
+            if (i<4) {
+                new_instruction->next = create_instruction_struct();
+                new_instruction->next->prev = new_instruction;
+                new_instruction = new_instruction->next;
+            }
+        }
+    } else if (strncmp(mnemonic, "RTS", MAX_MNEMONIC_LEN) == 0) {
+        new_instruction->syllable = JMP;
+    }
+
+    return new_instruction;
+}
diff --git a/nedasm/nedasm_parser_extensions.h b/nedasm/nedasm_parser_extensions.h
new file mode 100644 (file)
index 0000000..0f89ecc
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include "nedasm_structures.h"
+
+#ifndef NEDASM_PARSER_EXTENSIONS_H
+#define NEDASM_PARSER_EXTENSIONS_H
+
+struct instruction *
+parse_pseudo_mnemonic(char * mnemonic, size_t mnemonic_index, char * data, size_t data_index,
+                      char * label, size_t label_index, struct instruction * new_instruction);
+
+#endif
diff --git a/nedasm/nedasm_structures.c b/nedasm/nedasm_structures.c
new file mode 100644 (file)
index 0000000..a5ab3ef
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "nedasm_structures.h"
+
+struct instruction *
+seek_instruction_list_start(struct instruction * instructions)
+{
+    if (instructions == NULL) return NULL;
+    while (instructions->prev != NULL) instructions = instructions->prev;
+    return instructions;
+}
+
+struct instruction *
+seek_instruction_list_end(struct instruction * instructions)
+{
+    if (instructions == NULL) return NULL;
+    while (instructions->next != NULL) instructions = instructions->next;
+    return instructions;
+}
+
+void
+insert_instruction_struct_before(struct instruction * list, struct instruction * new_node)
+{
+    new_node->next = list;
+    new_node->prev = list->prev;
+    list->prev = new_node;
+    if (new_node->prev != NULL) (new_node->prev)->next = new_node;
+}
+
+void
+insert_instruction_struct_after(struct instruction * list, struct instruction * new_node)
+{
+    new_node->prev = list;
+    new_node->next = list->next;
+    list->next = new_node;
+    if (new_node->next != NULL) (new_node->next)->prev = new_node;
+}
+
+struct instruction *
+remove_instruction_struct(struct instruction * instructions)
+{
+    //struct instruction * delete_me = instructions;
+    struct instruction * return_me = NULL;
+
+    if (instructions->prev != NULL) {
+        instructions->prev->next = instructions->next;
+        return_me = instructions->prev;
+    }
+    if (instructions->next != NULL) {
+        instructions->next->prev = instructions->prev;
+        if (return_me != NULL) return_me = instructions->next;
+    }
+
+    // TODO: Given the one-pass & die-early nature of this task, skip memory management entirely?
+    //if (delete_me->label != NULL) free(delete_me->label);
+    //if (delete_me->target != NULL) free(delete_me->target);
+    //free(delete_me);
+
+    return return_me;
+}
+
+struct instruction *
+create_instruction_struct(void)
+{
+    struct instruction * new_struct = malloc(sizeof(struct instruction));
+    new_struct->prev = NULL;
+    new_struct->next = NULL;
+    new_struct->linenum = 0;
+    new_struct->syllable = 0;
+    new_struct->data = 0;
+    new_struct->target = NULL;
+    new_struct->label = NULL;
+    new_struct->address = 0;
+    return new_struct;
+}
+
+void
+insert_NOP_structs_after(struct instruction * list, uint8_t count)
+{
+    while (count != 0) {
+        struct instruction * new_struct = create_instruction_struct();
+        new_struct->syllable = NOP;
+        insert_instruction_struct_after(list, new_struct);
+        count--;
+    }
+}
+
+void
+insert_NOP_structs_before(struct instruction * list, uint8_t count)
+{
+    while (count != 0) {
+        struct instruction * new_struct = create_instruction_struct();
+        new_struct->syllable = NOP;
+        insert_instruction_struct_before(list, new_struct);
+        count--;
+    }
+}
diff --git a/nedasm/nedasm_structures.h b/nedasm/nedasm_structures.h
new file mode 100644 (file)
index 0000000..f295052
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#ifndef NEDASM_STRUCTURES_H
+#define NEDASM_STRUCTURES_H
+
+#include <stdint.h>
+
+/* This enumeration is slightly misnamed since, in addition to syllables, it also includes WORD. */
+/* And now includes LABELs, not even a real instruction. TODO: Fix either the name or purpose.   */
+enum syllables {
+    /* Manually assign values so that 0 remains unassigned, use for uninitialized value. */
+    WORD    = 1,
+    MVSTCK  = 2,
+    ADD     = 3,
+    XOR     = 4,
+    NOT     = 5,
+    OR      = 6,
+    AND     = 7,
+    BRZ     = 8,
+    TEST    = 9,
+    CMPSWP  = 10,
+    SHIFT   = 11,
+    STORE   = 12,
+    LOAD    = 13,
+    NOP     = 14,
+    HALT    = 15,
+    IM      = 16,
+    LDSP    = 17,
+    STSP    = 18,
+    JMP     = 19,
+    SWAP    = 20,
+    LABEL   = 21
+};
+
+struct instruction {
+    /* Pointer to previous entry in list. NULL indicates start of list. */
+    struct instruction * prev;
+
+    /* Pointer to next entry in list. NULL indicates end of list. */
+    struct instruction * next;
+
+    /* Line number of the source file in which this instruction was commanded. */
+    uint32_t linenum;
+
+    /* Specifies the type of instruction this struct represents. */
+    enum syllables syllable;
+
+    /* Contains data value for instructions containing embedded data. */
+    /* (WORD_x, IM_x, LDSP+x, STSP+x, etc) */
+    int32_t data;
+
+    /* Stores the target label string for instructions that alter PC. */
+    /* NULL if the address to jump to was already placed on TOS by other instructions. */
+    /* (BRZ, JMP, etc) */
+    char * target;
+
+    /* Stores the label name if this instruction is a label target. */
+    char * label;
+
+    /* Address in RAM of this instruction. Generated during assembly as an offset. */
+    uint32_t address;
+};
+
+struct instruction * create_instruction_struct(void);
+
+struct instruction * seek_instruction_list_start(struct instruction * instructions);
+
+struct instruction * seek_instruction_list_end(struct instruction * instructions);
+
+void insert_NOP_structs_after(struct instruction * list, uint8_t count);
+
+void insert_NOP_structs_before(struct instruction * list, uint8_t count);
+
+struct instruction * remove_instruction_struct(struct instruction * instructions);
+
+#endif
diff --git a/neddis/Makefile b/neddis/Makefile
new file mode 100644 (file)
index 0000000..b24f14e
--- /dev/null
@@ -0,0 +1,14 @@
+# © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+include ../common/main.mk
+
+SRC != ls *.c
+
+all: neddis
+
+neddis:
+       $(CC) -o $@ $(SRC)
+
+clean:
+       @rm -f neddis
diff --git a/neddis/README.md b/neddis/README.md
new file mode 100644 (file)
index 0000000..343fea6
--- /dev/null
@@ -0,0 +1,20 @@
+Overview
+========
+
+A disassembler for the NED architecture.
+
+Status
+======
+
+Functional without restrictions.
+
+Installation
+============
+
+Use `make` and `make clean` to build/remove neddis files. System installation
+requires manually copying the binary in to place.
+
+Operation
+=========
+
+Use `neddis -h` to see current command-line options.
diff --git a/neddis/neddis.c b/neddis/neddis.c
new file mode 100644 (file)
index 0000000..96256d0
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define VERSION 1
+
+enum syllables {
+    MVSTCK  = 0b00001111,
+    JMP     = 0b00001110,
+    SWAP    = 0b00001101,
+    ADD     = 0b00001100,
+    XOR     = 0b00001011,
+    NOT     = 0b00001010,
+    OR      = 0b00001001,
+    AND     = 0b00001000,
+    BRZ     = 0b00000111,
+    TEST    = 0b00000110,
+    CMPSWP  = 0b00000101,
+    SHIFT   = 0b00000100,
+    STORE   = 0b00000011,
+    LOAD    = 0b00000010,
+    NOP     = 0b00000001,
+    HALT    = 0b00000000
+};
+
+#define WIDTH_MNEMONICS 14
+
+/*
+ * Output format:
+ * 10 characters for address in hexadecimal
+ * 4 spaces
+ * 10 characters for hex dump of 32-bit word
+ * 4 spaces
+ * 40 characters for binary representation of 32-bit word
+ * 4 spaces
+ * remainder of line for mnemonics
+ */
+
+void
+print_header(void)
+{
+    printf("  Offset   ||     Hex    ||                  Binary         "
+           "         ||                             Description\n"
+           "============================================================"
+           "============================================================"
+           "===================\n");
+}
+
+void
+print_usage(char ** argv)
+{
+    printf( "NED Disassembler v%d (www.subgeniuskitty.com)\n"
+            "Usage: %s -i <file>\n"
+            "  -h         Help (prints this message)\n"
+            "  -i <file>  Specify a binary image file to disassemble.\n"
+            , VERSION, argv[0]
+    );
+}
+
+void
+print_formatA_binary(uint32_t word)
+{
+    printf("1 ");
+    int chunk_length = 0;
+    for (int bit_index = 30; bit_index >= 0; bit_index--) {
+        /* Insert space every 8 bits. Leading space is intentional. */
+        if ((chunk_length++ % 8) == 0) printf(" ");
+
+        if ((word >> bit_index) & 0b1) {
+            printf("1");
+        } else {
+            printf("0");
+        }
+    }
+    printf("   ");
+}
+
+void
+print_formatA_mnemonics(uint32_t word)
+{
+    int position;
+    printf("WORD =====>%n", &position);
+    while (position++ != WIDTH_MNEMONICS) printf(" "); 
+    printf("0x%x%n", ((word & 0b01111111111111111111111111111111) << 1), &position);
+    while (position++ != WIDTH_MNEMONICS) printf(" ");
+    printf("0%o%n", ((word & 0b01111111111111111111111111111111) << 1), &position);
+    while (position++ != WIDTH_MNEMONICS) printf(" ");
+    printf("%u%n", ((word & 0b01111111111111111111111111111111) << 1), &position);
+    while (position++ != WIDTH_MNEMONICS) printf(" ");
+}
+
+uint8_t
+extract_syllable_from_word(uint32_t word, uint8_t index)
+{
+    uint32_t mask = 0b111111 << 6*(4-index);
+    return (word & mask) >> 6*(4-index);
+}
+
+void
+print_formatC_binary(uint32_t word)
+{
+    printf("00 ");
+    for (int syllable_index = 0; syllable_index < 5; syllable_index++) {
+        uint8_t syllable = extract_syllable_from_word(word, syllable_index);
+        for (int i = 5; i >= 0; i--) {
+            if (((syllable >> i) & 0b1) == 1) {
+                printf("1");
+            } else {
+                printf("0");
+            }
+        }
+        printf(" ");
+    }
+    printf("  ");
+}
+
+void
+print_formatC_mnemonics(uint32_t word)
+{
+    for (int syllable_index = 0; syllable_index < 5; syllable_index++) {
+        int position = 0;
+        uint8_t syllable = extract_syllable_from_word(word, syllable_index);
+        if (syllable & 0b100000) { /* Check the first bit of the syllable. 1 means IM_x. */
+            printf("IM_%d%n", (syllable & 0b11111), &position);
+        } else if (syllable & 0b10000) { /* 1 in 2nd bit means LDSP+x or STSP+x instruction. */
+            if (syllable & 0b1000) { /* LDSP+x */
+                printf("LDSP+%d%n", (syllable & 0b111), &position);
+            } else {                 /* STSP+x */
+                printf("STSP+%d%n", (syllable & 0b111), &position);
+            }
+        } else {
+            switch (syllable) {
+                case AND:       printf("AND%n", &position);     break;
+                case OR:        printf("OR%n", &position);      break;
+                case NOT:       printf("NOT%n", &position);     break;
+                case XOR:       printf("XOR%n", &position);     break;
+                case ADD:       printf("ADD%n", &position);     break;
+                case MVSTCK:    printf("MVSTCK%n", &position);  break;
+                case SHIFT:     printf("SHIFT%n", &position);   break;
+                case CMPSWP:    printf("CMPSWP%n", &position);  break;
+                case TEST:      printf("TEST%n", &position);    break;
+                case BRZ:       printf("BRZ%n", &position);     break;
+                case LOAD:      printf("LOAD%n", &position);    break;
+                case STORE:     printf("STORE%n", &position);   break;
+                case NOP:       printf("NOP%n", &position);     break;
+                case HALT:      printf("HALT%n", &position);    break;
+                case JMP:       printf("JMP%n", &position);     break;
+                case SWAP:      printf("SWAP%n", &position);    break;
+                default:        printf("?ERR?%n", &position);   break;
+            }
+        }
+        while (position++ != WIDTH_MNEMONICS) printf(" ");
+    }
+}
+
+int
+main(int argc, char ** argv)
+{
+    /*
+     * Process command line arguments
+     */
+    int c;
+    FILE * input = NULL;
+    while ((c = getopt(argc,argv,"i:h")) != -1) {
+        switch (c) {
+            case 'i':
+                if ((input = fopen(optarg, "r")) == NULL) {
+                    fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
+                }
+                break;
+            case 'h':
+                print_usage(argv);
+                exit(EXIT_SUCCESS);
+                break;
+            default:
+                break;
+        }
+    }
+    if (input == NULL) {
+        fprintf(stderr, "ERROR: Must specify a binary image file with -i flag.\n");
+        print_usage(argv);
+        exit(EXIT_FAILURE);
+    }
+
+    /*
+     * Main Loop
+     */
+    print_header();
+    uint32_t word;
+    uint32_t offset = 0;
+    /* Since all NED instructions are one word (4 bytes) wide, read in one word increments. */
+    while (fread(&word, 4, 1, input)) {
+        printf("0x%08x", offset);
+        printf("    ");
+        printf("0x%08x", word);
+        printf("    ");
+        if (word & (0b1 << 31)) { /* Format A instruction word */
+            print_formatA_binary(word);
+            printf("    ");
+            print_formatA_mnemonics(word);
+        } else if ((word & (0b11 << 30)) == 0) { /* Format C instruction word */
+            print_formatC_binary(word);
+            printf("    ");
+            print_formatC_mnemonics(word);
+        } else {
+            fprintf(stderr, "ERROR: Malformed instruction word: %o\n", word);
+            exit(EXIT_FAILURE);
+        }
+        printf("\n");
+        offset += 4; /* Increment by one word (4 bytes). */
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/nedsim/Makefile b/nedsim/Makefile
new file mode 100644 (file)
index 0000000..f34d74d
--- /dev/null
@@ -0,0 +1,14 @@
+# © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+include ../common/main.mk
+
+SRC != ls *.c
+
+all: nedsim
+
+nedsim:
+       $(CC) -o $@ $(SRC)
+
+clean:
+       @rm -f nedsim
diff --git a/nedsim/README.md b/nedsim/README.md
new file mode 100644 (file)
index 0000000..846cb34
--- /dev/null
@@ -0,0 +1,35 @@
+Overview
+========
+
+A simulator for the NED architecture.
+
+Status
+======
+
+The CPU simulation is complete except for the CMPSWP instruction. Only a single
+CPU is currently supported.
+
+A UART-like console is included with the following register layout:
+
+    0x8000000 - Transmit Buffer - Accepts bytes and prints to console
+    0x8000004 - Transmit Status - Non-zero when UART is ready to accept a character.
+    0x8000008 - Receive Buffer  - Contains a byte from the console when available.
+    0x800000C - Receive Status  - Non-zero when UART contains a character.
+
+Installation
+============
+
+Use `make` and `make clean` to build/remove nedsim files. System installation
+requires manually copying the binary in to place.
+
+What configuration are available can be found as `#define`s at the top of the
+nedsim source code. This includes things like size of system RAM, number of
+hardware threads, etc.
+
+Operation
+=========
+
+Use `nedsim -h` to see current command-line options.
+
+The simulator will terminate if a HALT instruction is reached and can also be
+terminated during operation with Ctrl-C.
diff --git a/nedsim/nedsim.c b/nedsim/nedsim.c
new file mode 100644 (file)
index 0000000..9bdaa59
--- /dev/null
@@ -0,0 +1,650 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <termios.h>
+#include <signal.h>
+
+#define VERSION 1
+
+/* Bytes per word. */
+#define BPW 4
+
+/* Number of stack words. */
+#define STACK_LENGTH 1048576
+
+/* Number of bytes of RAM. */
+#define RAM_LENGTH 1073741824
+
+/* Number of hardware threads. */
+#define THREAD_COUNT 8
+
+/* Number of syllables per word. */
+#define SPW 5
+
+struct DBGthread {
+    uint32_t max_stack;         /* Highest stack entry reached during execution.       */
+    uint32_t max_stack_address; /* Address of PC when highest stack entry was reached. */
+    uint64_t ram_reads;         /* Number of words of RAM read during execution.       */
+    uint64_t ram_writes;        /* Number of words of RAM written during execution.    */
+    uint64_t cycle_count;       /* Total number of CPU cycles. One cycle corresponds   */
+                                /*     to a single CPU action, either a WORD_x word    */
+                                /*     or a single syllable from a five-syllable word. */
+};
+
+struct NEDpsw {
+    bool zero;
+    bool negative;
+};
+
+struct NEDthread {
+    uint32_t stack[STACK_LENGTH];
+    uint32_t sp;
+    uint32_t pc;
+    uint8_t sc;
+    struct NEDpsw * psw;
+    struct DBGthread * debug;
+};
+
+struct NEDstate {
+    uint8_t ram[RAM_LENGTH];
+    struct NEDthread * thread[THREAD_COUNT];
+    struct NEDthread * active_thread;
+    struct timespec * prev_instruction_clock;
+    uint32_t clock_period_ns;
+};
+
+/* This variable allows printing the processor's state from a signal handler. */
+/* It should not be used for anything else.                                   */
+struct NEDstate * NEDdebug = NULL;
+
+enum syllables {
+    MVSTCK  = 0b00001111,
+    JMP     = 0b00001110,
+    SWAP    = 0b00001101,
+    ADD     = 0b00001100,
+    XOR     = 0b00001011,
+    NOT     = 0b00001010,
+    OR      = 0b00001001,
+    AND     = 0b00001000,
+    BRZ     = 0b00000111,
+    TEST    = 0b00000110,
+    CMPSWP  = 0b00000101,
+    SHIFT   = 0b00000100,
+    STORE   = 0b00000011,
+    LOAD    = 0b00000010,
+    NOP     = 0b00000001,
+    HALT    = 0b00000000
+};
+
+void
+print_usage(char ** argv)
+{
+    printf( "NED Simulator v%d (www.subgeniuskitty.com)\n"
+            "Usage: %s -i <file>\n"
+            "  -h                      Help (prints this message)\n"
+            "  -i <file>               Specify a binary image file to load in RAM.\n"
+            "  -p <int ns, optional>   Period in nanoseconds of simulated system clock.\n"
+            "                          Allowable values are 1 <= clock <= 1,000,000,000.\n"
+            , VERSION, argv[0]
+    );
+}
+
+void
+thread_init(struct NEDthread * thread)
+{
+    thread->pc                          = 0;
+    thread->sc                          = 0;
+    thread->sp                          = 0;
+    thread->psw->zero                   = false;
+    thread->psw->negative               = false;
+    thread->debug->max_stack            = 0;
+    thread->debug->max_stack_address    = 0;
+    thread->debug->ram_reads            = 0;
+    thread->debug->ram_writes           = 0;
+    thread->debug->cycle_count          = 0;
+}
+
+void
+set_terminal_mode(void)
+{
+    struct termios options;
+    tcgetattr(STDIN_FILENO, &options);
+    /* Create a cbreak-like environment through the following options. */
+    options.c_lflag &= ~ECHO; /* Disable echoing of input characters. */
+    options.c_lflag &= ~ICANON; /* Disable cooked/line-oriented mode. */
+    options.c_cc[VMIN] = 1;
+    options.c_cc[VTIME] = 0;
+    tcsetattr(STDIN_FILENO, TCSANOW, &options);
+}
+
+void
+unset_terminal_mode(void)
+{
+    struct termios options;
+    tcgetattr(STDIN_FILENO, &options);
+    /* Undo the changes made in set_terminal_mode(). */
+    options.c_lflag |= ECHO; /* Enable echoing of input characters. */
+    options.c_lflag |= ICANON; /* Enable cooked/line-oriented mode. */
+    options.c_cc[VMIN] = 1; /* Default value from /usr/src/sys/sys/ttydefaults.h */
+    options.c_cc[VTIME] = 0; /* Default value from /usr/src/sys/sys/ttydefaults.h */
+    tcsetattr(STDIN_FILENO, TCSANOW, &options);
+}
+
+int
+is_stdin_nonempty(void)
+{
+    fd_set read_fds;
+    FD_ZERO(&read_fds);
+    FD_SET(STDIN_FILENO, &read_fds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+
+    int retval = select(1, &read_fds, NULL, NULL, &timeout);
+
+    if (retval == -1) {
+        /* TODO: How do I want to handle this error? */
+    }
+
+    return retval;
+}
+
+uint32_t
+generate_binary_psw(struct NEDstate * state)
+{
+    uint32_t psw = 0;
+    if (state->active_thread->psw->zero) psw |= 0b1;
+    if (state->active_thread->psw->negative) psw |= 0b10;
+    return psw;
+}
+
+void
+ram_w_byte(struct NEDstate * state, uint32_t address, uint8_t data)
+{
+    state->ram[address] = data;
+}
+
+uint8_t
+ram_r_byte(struct NEDstate * state, uint32_t address)
+{
+    return state->ram[address];
+}
+
+/* For now, with only a terminal for IO, we pick off IO requests when accessing RAM. */
+/* TODO: Improve this before adding any other IO devices like disks. */
+
+void
+ram_w_word(struct NEDstate * state, uint32_t address, uint32_t data)
+{
+    /* TODO: Since PC and PSW are memory mapped, they should accept writes. */
+    /*       Should writes to the PC automatically reset the syllable counter? */
+    if (address == 0x8000000) { /* SLU: XBUF */
+        printf("%c", data);
+        fflush(stdout);
+    } else if (address == 0x0 || address == 0x4) {
+        /* Intentionally empty */
+    } else if (address >= 0x20000000) {
+        state->active_thread->debug->ram_writes += 1;
+        for (int i=3; i>=0; i--) {
+            uint8_t tmp_byte = ((data >> (8*(3-i))) & 0xff);
+            ram_w_byte(state,address+i,tmp_byte);
+        }
+    }
+}
+
+uint32_t
+ram_r_word(struct NEDstate * state, uint32_t address)
+{
+    if (address == 0x0) {               /* Zero register */
+        return 0b0;
+    } else if (address == 0x4) {        /* 0x80000000 register */
+        return 0x80000000;
+    } else if (address == 0x8) {        /* PC register */
+        return state->active_thread->pc;
+    } else if (address == 0xC) {        /* PSW register */
+        return generate_binary_psw(state);
+    } else if (address == 0x8000004) {  /* SLU: XCSR */
+        /* TODO: Should I artificially restrict printing in the simulator? */
+        /*       It might help catch bugs like the GCC bug that slipped past SIMH. */
+        return 0b1;
+    } else if (address == 0x8000008) {  /* SLU: RBUF */
+        if (is_stdin_nonempty()) {
+            return getchar();
+        } else {
+            return (uint8_t)rand();
+        }
+    } else if (address == 0x800000C) {  /* SLU: RCSR */
+        if (is_stdin_nonempty()) {
+            return 0b1;
+        } else {
+            return 0b0;
+        }
+    } else if (address >= 0x20000000) { /* RAM */
+        state->active_thread->debug->ram_reads += 1;
+        uint32_t word = 0;
+        for (int i=0; i<4; i++) word |= (ram_r_byte(state,address+i)) << (8*(3-i));
+        return word;
+    }
+    return 0b0;
+}
+
+uint32_t
+fetch_instruction_word(struct NEDstate * state)
+{
+    uint32_t word = ram_r_word(state, state->active_thread->pc);
+    state->active_thread->pc += BPW;
+    return word;
+}
+
+void
+print_snapshot(struct NEDstate * state)
+{
+    /*
+     * This function is sometimes called from a signal handler that depends
+     * on a global variable. That variable is set to NULL in the declaration.
+     * For that reason, check for a NULL pointer here.
+     */
+    if (state == NULL) return;
+
+    printf("\n");
+    printf("########## Processor Statistics ##########\n");
+    printf("Stack Max: %u @ 0x%08x\n", state->active_thread->debug->max_stack,
+           state->active_thread->debug->max_stack_address
+          );
+    printf("Cycle Count: %lu\n", state->active_thread->debug->cycle_count);
+    printf("RAM Reads: %lu\n", state->active_thread->debug->ram_reads);
+    printf("RAM Writes: %lu\n", state->active_thread->debug->ram_writes);
+    printf("##########  Processor Snapshot  ##########\n");
+    printf("PC: 0x%X\n", state->active_thread->pc);
+    printf("PSW:\n    %d Zero\n    %d Negative\n", 
+           state->active_thread->psw->zero,
+           state->active_thread->psw->negative
+    );
+    printf("Stack:\n       ...\n");
+    int64_t i = state->active_thread->sp;
+    if ((i + 5) > (STACK_LENGTH - 1)) {
+        i = STACK_LENGTH - 1;
+    } else {
+        i += 5;
+    }
+    for ( ; i >= 0; i--) {
+        if (i == (state->active_thread->sp - 1)) {
+            printf("  TOS> %ld: 0x%X\n", i, state->active_thread->stack[i]);
+        } else {
+            printf("       %ld: 0x%X\n", i, state->active_thread->stack[i]);
+        }
+    }
+}
+
+void
+stack_w(struct NEDthread * thread, uint32_t value, uint8_t offset)
+{
+    thread->stack[thread->sp - (offset + 1)] = value;
+}
+
+uint32_t
+stack_r(struct NEDthread * thread, uint8_t offset)
+{
+    return thread->stack[thread->sp - (offset + 1)];
+}
+
+void
+stack_push(struct NEDthread * thread, uint32_t value)
+{
+    if (thread->sp > thread->debug->max_stack) {
+        thread->debug->max_stack = thread->sp;
+        thread->debug->max_stack_address = thread->pc;
+    }
+    thread->stack[thread->sp++] = value;
+}
+
+uint32_t
+stack_pop(struct NEDthread * thread)
+{
+    return thread->stack[--thread->sp];
+}
+
+void
+set_psw_flags(uint32_t word, struct NEDstate * state)
+{
+    if (word == 0) {
+        state->active_thread->psw->zero = true;
+    } else {
+        state->active_thread->psw->zero = false;
+    }
+    if (word & 0x80000000) {
+        state->active_thread->psw->negative = true;
+    } else {
+        state->active_thread->psw->negative = false;
+    }
+}
+
+void
+ned_instruction_and(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 & operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_or(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 | operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_not(struct NEDstate * state)
+{
+    stack_push(state->active_thread, ~stack_pop(state->active_thread));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_xor(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 ^ operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_add(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 + operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_mvstck(struct NEDstate * state)
+{
+    uint32_t new_id = stack_pop(state->active_thread);
+    if (new_id < THREAD_COUNT) {
+        state->active_thread = state->thread[new_id];
+    } else {
+        printf("ERROR: Attempted MVSTCK to ID higher than THREAD_COUNT.\n");
+        print_snapshot(state);
+        unset_terminal_mode();
+        exit(EXIT_FAILURE);
+    }
+}
+
+void
+ned_instruction_shift(struct NEDstate * state)
+{
+    /* TODO: Bounds check: Either all inputs are valid OR shift_by < 32. */
+    /* I guess this also depends if I'm shifting-and-dropping, or barrel-shifting. */
+    /* How should I pad for a right shift if I shift-and-drop? Sign extend? */
+    uint32_t shift_by = stack_pop(state->active_thread);
+    uint32_t word = stack_pop(state->active_thread);
+    if (shift_by & 0x80000000) {
+        stack_push(state->active_thread, (word << (shift_by & 0x7fffffff)));
+    } else {
+        stack_push(state->active_thread, (word >> shift_by));
+    }
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_test(struct NEDstate * state)
+{
+    uint32_t word = stack_pop(state->active_thread);
+    set_psw_flags(word, state);
+}
+
+void
+ned_instruction_jmp(struct NEDstate * state)
+{
+    state->active_thread->pc = stack_pop(state->active_thread);
+    /* TODO: Find better way to communicate we're skipping ahead to the next word. */
+    state->active_thread->sc = 4;
+}
+
+void
+ned_instruction_swap(struct NEDstate * state)
+{
+    uint32_t temp1 = stack_pop(state->active_thread);
+    uint32_t temp2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, temp1);
+    stack_push(state->active_thread, temp2);
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_brz(struct NEDstate * state)
+{
+    uint32_t new_pc = stack_pop(state->active_thread);
+    if (state->active_thread->psw->zero == true) {
+        state->active_thread->pc = new_pc;
+        /* TODO: Find better way to communicate we're skipping ahead to the next word. */
+        state->active_thread->sc = 4;
+    }
+}
+
+void
+ned_instruction_load(struct NEDstate * state)
+{
+    uint32_t address = stack_pop(state->active_thread);
+    stack_push(state->active_thread, ram_r_word(state, address));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_store(struct NEDstate * state)
+{
+    uint32_t address = stack_pop(state->active_thread);
+    uint32_t data = stack_pop(state->active_thread);
+    ram_w_word(state, address, data);
+}
+
+void
+ned_instruction_halt(struct NEDstate * state)
+{
+    printf("Halting.\n");
+    print_snapshot(state);
+    unset_terminal_mode();
+    exit(EXIT_SUCCESS);
+}
+
+void
+execute_syllable(struct NEDstate * state, enum syllables syllable)
+{
+    if (syllable & 0b100000) { /* Check the first bit of the syllable. 1 means IM_x. */
+        stack_push(state->active_thread, (uint32_t)(syllable & 0b11111));
+    } else if (syllable & 0b10000) { /* 1 in 2nd bit means LDSP+x or STSP+x instruction. */
+        if (syllable & 0b1000) { /* LDSP+x */
+            stack_push(state->active_thread,stack_r(state->active_thread,(syllable & 0b111)));
+            set_psw_flags(stack_r(state->active_thread,0), state);
+        } else { /* STSP+x */
+            stack_w(state->active_thread,stack_pop(state->active_thread),(syllable & 0b111));
+        }
+    } else {
+        switch (syllable) {
+            case AND:       ned_instruction_and(state);     break;
+            case OR:        ned_instruction_or(state);      break;
+            case NOT:       ned_instruction_not(state);     break;
+            case XOR:       ned_instruction_xor(state);     break;
+            case ADD:       ned_instruction_add(state);     break;
+            case MVSTCK:    ned_instruction_mvstck(state);  break;
+            case SHIFT:     ned_instruction_shift(state);   break;
+            case CMPSWP:    /* TODO */                      break;
+            case TEST:      ned_instruction_test(state);    break;
+            case JMP:       ned_instruction_jmp(state);     break;
+            case SWAP:      ned_instruction_swap(state);    break;
+            case BRZ:       ned_instruction_brz(state);     break;
+            case LOAD:      ned_instruction_load(state);    break;
+            case STORE:     ned_instruction_store(state);   break;
+            case NOP:       /* Intentionally blank */       break;
+            case HALT:      ned_instruction_halt(state);    break;
+            default:
+                printf("ERROR: Attempted to execute illegal syllable: 0o%o\n", syllable);
+                break;
+        }
+    }
+}
+
+uint8_t
+extract_syllable_from_word(uint32_t word, uint8_t index)
+{
+    uint32_t mask = 0b111111 << 6*(4-index);
+    return (word & mask) >> 6*(4-index);
+}
+
+void
+ned_sigint_handler(int sig)
+{
+    print_snapshot(NEDdebug);
+    unset_terminal_mode();
+    exit(EXIT_SUCCESS);
+}
+
+void
+wait_for_next_clock_cycle(struct NEDstate * state)
+{
+    state->prev_instruction_clock->tv_nsec += state->clock_period_ns;
+    while (state->prev_instruction_clock->tv_nsec > 1000000000) {
+        state->prev_instruction_clock->tv_nsec -= 1000000000;
+        state->prev_instruction_clock->tv_sec += 1;
+    }
+
+    while(1) {
+        struct timespec current_time;
+        if(clock_gettime(CLOCK_MONOTONIC, &current_time)) {
+            fprintf(stderr, "WARNING: Unable to sync to requested CPU speed.\n");
+            break;
+        } else {
+            bool sec_gt = current_time.tv_sec > state->prev_instruction_clock->tv_sec;
+            bool sec_eq = current_time.tv_sec == state->prev_instruction_clock->tv_sec;
+            bool nsec_gt = current_time.tv_nsec > state->prev_instruction_clock->tv_nsec;
+            if (sec_gt || (sec_eq && nsec_gt)) break;
+        }
+    }
+}
+
+int
+main(int argc, char ** argv)
+{
+    /*
+     * Process command line arguments
+     */
+    int c;
+    FILE * input = NULL;
+    uint32_t clock_period = 1;
+    while ((c = getopt(argc,argv,"i:p:h")) != -1) {
+        switch (c) {
+            case 'i':
+                if ((input = fopen(optarg, "r")) == NULL) {
+                    fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
+                }
+                break;
+            case 'p':
+                {
+                    intmax_t temp_p = strtoimax(optarg, NULL, 0);
+                    if (1 <= temp_p && temp_p <= 1000000000) {
+                        clock_period = temp_p;
+                    } else {
+                        fprintf(stderr, "ERROR: Clock period out of range.\n");
+                    }
+                    break;
+                }
+            case 'h':
+                print_usage(argv);
+                exit(EXIT_SUCCESS);
+                break;
+            default:
+                break;
+        }
+    }
+    if (input == NULL) {
+        fprintf(stderr, "ERROR: Must specify a binary image file with -i flag.\n");
+        print_usage(argv);
+        exit(EXIT_FAILURE);
+    }
+
+    /*
+     * Initialization
+     */
+    set_terminal_mode();
+    struct NEDstate * state = malloc(sizeof(struct NEDstate));
+    state->prev_instruction_clock = malloc(sizeof(struct timespec));
+    clock_gettime(CLOCK_MONOTONIC, state->prev_instruction_clock);
+    state->clock_period_ns = clock_period;
+    for (size_t i=0; i < THREAD_COUNT; i++) {
+        state->thread[i] = malloc(sizeof(struct NEDthread));
+        state->thread[i]->psw = malloc(sizeof(struct NEDpsw));
+        state->thread[i]->debug = malloc(sizeof(struct DBGthread));
+    }
+    thread_init(state->thread[0]);
+    state->thread[0]->pc = 0x20000000;  /* Data region starts 512 MB into address space. */
+    state->active_thread = state->thread[0];  /* By convention, use thread 0 for init. */
+
+    /* NEDdebug should only be used in ned_sigint_handler() to print state while exiting. */
+    NEDdebug = state;
+    signal(SIGINT, ned_sigint_handler);
+
+    /* Load an initial image into memory. */
+    uint32_t temp_word;
+    uint32_t address = 0x20000000;
+    while(fread(&temp_word, 4, 1, input)) {
+        ram_w_word(state, address, temp_word);
+        address += 4;
+    }
+    fclose(input);
+
+    /*
+     * Main Loop
+     */
+    uint32_t iw = 0;
+    while (1) {
+        /* Check for interrupt requests. */
+        /* TODO */
+
+        /* Fetch new instruction word. */
+        iw = fetch_instruction_word(state);
+
+        /* Decode instruction word format and execute. */
+        if (iw & (0b1 << 31)) {                /* Instruction word is type A. */
+            wait_for_next_clock_cycle(state);
+            stack_push(state->active_thread, (iw << 1));
+            state->active_thread->debug->cycle_count += 1;
+        } else if ((iw & (0b11 << 30)) == 0) { /* Instruction word is type C. */
+            for (state->active_thread->sc = 0;
+                state->active_thread->sc < SPW;
+                state->active_thread->sc++
+            ) {
+                wait_for_next_clock_cycle(state);
+                uint8_t syllable = extract_syllable_from_word(iw, state->active_thread->sc);
+                execute_syllable(state, syllable);
+                state->active_thread->debug->cycle_count += 1;
+            }
+        } else {
+            fprintf(stderr, "ERROR: Attempted to execute illegal instruction.\n");
+            print_snapshot(state);
+            unset_terminal_mode();
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    unset_terminal_mode();
+    exit(EXIT_SUCCESS);
+}
diff --git a/software/4func_calculator/Makefile b/software/4func_calculator/Makefile
new file mode 100644 (file)
index 0000000..05769ed
--- /dev/null
@@ -0,0 +1,18 @@
+# © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+include ../../common/main.mk
+
+SRC = calc.asm
+BIN = $(SRC:.asm=.bin)
+
+all: calc
+
+calc:
+       $(NEDASM) -o $(BIN) -i $(SRC)
+
+clean:
+       @rm -f $(BIN)
+
+sim:
+       $(NEDSIM) -i $(BIN)
diff --git a/software/4func_calculator/README.md b/software/4func_calculator/README.md
new file mode 100644 (file)
index 0000000..3b6dd29
--- /dev/null
@@ -0,0 +1,28 @@
+Overview
+========
+
+This is an integer-only, stack-based, four function calculator written in NED
+assembly.
+
+Compatibility
+=============
+
+The calculator is compatible with `nedasm` v1 and `nedsim` v1.
+
+Instructions
+============
+
+    Command   Effect
+    ==============================================
+      0-9     Append digit to current stack entry
+    
+      .       Next stack entry
+      ,       Previous stack entry
+    
+      ;       Negate stack entry
+      '       Zero stack entry
+    
+      +       Addition
+      -       Subtraction
+      *       Multiplication
+      /       Division (with remainder)
diff --git a/software/4func_calculator/calc.asm b/software/4func_calculator/calc.asm
new file mode 100644 (file)
index 0000000..4386392
--- /dev/null
@@ -0,0 +1,1465 @@
+# (c) 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+# See LICENSE.txt file for copyright and license details.
+
+# Simple 4-function calculator in NED assembly.
+# 20181128 - Aaron Taylor
+
+calc
+    JSR>calcinit
+
+    calcloop
+        # Refresh the display.
+        JSR>printtopstackentry
+
+        # Get a character/command.
+        JSR>getchar
+
+        # Check for 'zero stack entry' command.
+        LDSP+0
+        JSR>evalzerostackentry
+
+        # Check for 'negate stack entry' command.
+        LDSP+0
+        JSR>evalnegatestackentry
+
+        # Check for stack navigation commands.
+        LDSP+0
+        JSR>evalstacknavigation
+
+        # Check for ASCII digit (append to stack entry) command.
+        LDSP+0
+        JSR>evalasciidigit
+
+        # Check for ASCII 'addition' command.
+        LDSP+0
+        JSR>evalmathaddition
+
+        # Check for ASCII 'subtraction' command.
+        LDSP+0
+        JSR>evalmathsubtraction
+
+        # Check for ASCII 'multiplication' command.
+        LDSP+0
+        JSR>evalmathmultiplication
+
+        # Check for ASCII 'division' command.
+        LDSP+0
+        JSR>evalmathdivision
+
+        # Dump existing character and get the next character.
+        TEST
+        JMP>calcloop
+
+# Should be unreachable.
+HALT
+
+##########################################################################################
+calcinit
+##########################################################################################
+# Description:
+#   Initializes stack for calc subroutine.
+#   Zeros the Data Stack in RAM.
+# Call Stack:
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address (=0x40000000)
+#   Data Stack Offset (=0)       <-- TOS
+##########################################################################################
+    # Setup program stack.
+    WORD_1073741824
+    SWAP
+    IM_0
+    SWAP
+
+    # Prepare to generate Data Stack pointers.
+    LDSP+2 # Data Stack Base Address
+    LDSP+2 # Data Stack Offset
+
+    # Zero the Data Stack.
+    calcinitzeroloop
+
+        # Increment Data Stack Offset, store 0 at location.
+        JSR>incrementstackindex
+        IM_0
+        LDSP+2
+        LDSP+2
+        ADD
+        STORE
+
+        # Check for loop termination
+        LDSP+0
+        TEST
+        BRZ>calcinitzeroloopend
+            JMP>calcinitzeroloop
+
+    calcinitzeroloopend
+
+    # Clean up stack and return.
+    TEST
+    TEST
+    RTS
+
+##########################################################################################
+incrementstackindex
+##########################################################################################
+# Description:
+#   Increment the Data Stack Offset, wrapping if approriate.
+# Call Stack:
+#   Data Stack Offset
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Offset
+##########################################################################################
+    # Increment the Data Stack Offset
+    SWAP
+    IM_4 # Four bytes per word
+    ADD
+
+    # See if the new Data Stack Offset should wrap. If so, set it to zero.
+    LDSP+0
+    IM_12       # Total of four stack entries (offsets: 0, 4, 8, 12).
+    JSR>subtract
+    TEST
+    IM_12       # Address of PSW register.
+    LOAD
+    IM_2        # Negative bit in PSW
+    AND
+    TEST
+    BRZ>incrementstackindexreturn
+        # Negative bit was set, so wrap to start of stack.
+        TEST
+        IM_0
+    incrementstackindexreturn
+    SWAP
+    RTS
+
+##########################################################################################
+decrementstackindex
+##########################################################################################
+# Description:
+#   Decrement the Data Stack Offset, wrapping if approriate.
+# Call Stack:
+#   Data Stack Offset
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Offset
+##########################################################################################
+    # Decrement the Data Stack Offset
+    SWAP
+    IM_4
+    SWAP
+    JSR>subtract
+
+    # See if new Data Stack Offset should wrap, indicated by value < 0.
+    # If so, set it to 12 (offsets: 0, 4, 8, 12).
+    LDSP+0
+    TEST
+    IM_12   # Address of PSW register.
+    LOAD
+    IM_2    # Negative bit in PSW register.
+    AND
+    TEST
+    BRZ>decrementstackindexreturn
+        # Negative bit was set, so wrap to end of stack.
+        TEST
+        IM_12
+    decrementstackindexreturn
+    SWAP
+    RTS
+
+##########################################################################################
+printstackindex
+##########################################################################################
+# Description:
+#   Prints the data stack index in user readable form.
+# Call Stack:
+#   Data Stack Offset
+#   Return PC <-- TOS
+# Return Stack:
+#   <empty>
+##########################################################################################
+    # Put Return PC on bottom of stack.
+    SWAP
+
+    # Put post-number portion of the string on the stack (colon, space, NUL).
+    IM_0    # ASCII NUL
+    SWAP
+    WORD_32 # ASCII ' '
+    SWAP
+    WORD_58 # ASCII ':'
+    SWAP
+
+    # Stack now looks like:
+    #   Return PC
+    #   ASCII NUL
+    #   ASCII ' '
+    #   ASCII ':'
+    #   Data Stack Offset
+
+    # Divide Data Stack Offset by 4 to generate index since there are 4 bytes per word.
+    IM_4
+    JSR>divide
+    TEST # DROP Remainder
+
+    # Convert index to ASCII and print the string.
+    JSR>itoa
+    JSR>printstring
+    RTS
+
+##########################################################################################
+printbinaryinteger
+##########################################################################################
+# Description:
+#   Prints a binary integer to ASCII terminal.
+# Call Stack:
+#   Integer
+#   Return PC <-- TOS
+# Return Stack:
+#   <empty>
+##########################################################################################
+    # Put Return PC on bottom of stack.
+    SWAP
+
+    # Put ASCII NUL on stack as end of string.
+    IM_0
+    SWAP
+
+    # Separate Integer into sign and magnitude.
+    LDSP+0
+    IM_4
+    LOAD
+    AND
+    SWAP
+    JSR>absolutevalue
+
+    # Stack now looks like:
+    #   Return PC
+    #   ASCII NUL
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   ABS(Integer)
+
+    # First check if the number is zero and print a single zero if true.
+    LDSP+0
+    TEST
+    BRZ>printbinaryintegerloopend
+
+    # Repeatedly divide by ten and push each digit onto the stack in ASCII.
+    printbinaryintegerloop
+
+    # Verify Integer still has digits to print (i.e. Integer != 0)
+    LDSP+0
+    TEST
+    BRZ>printbinaryintegerloopend
+
+        # Extract the least significant digit.
+        IM_10
+        JSR>divide
+
+        # Convert to ASCII and add to string on stack.
+        JSR>itoa
+        LDSP+2 # Sign flag
+        SWAP
+        STSP+2
+        SWAP
+
+        # Loop
+        JMP>printbinaryintegerloop
+
+    printbinaryintegerloopend
+    TEST # DROP ABS(Integer)
+
+    # Push a leading ASCII zero onto stack.
+    IM_0
+    JSR>itoa
+    SWAP
+
+    # Stack now looks like:
+    #   Return PC
+    #   ASCII NUL
+    #   ASCII digit
+    #   ...
+    #   ASCII digit
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+
+    # Push sign onto stack as ASCII character.
+    TEST
+    BRZ>printbinaryintegerpositive
+
+        # Add ASCII '-' sign to stack
+        WORD_44
+        IM_1
+        ADD
+        JMP>printbinaryintegerend
+
+    printbinaryintegerpositive
+
+        # Add ASCII '+' sign to stack
+        WORD_42
+        IM_1
+        ADD
+
+    printbinaryintegerend
+
+    # Print the string and return
+    JSR>printstring
+    RTS
+
+##########################################################################################
+printtopstackentry
+##########################################################################################
+# Description:
+#   Prints the current TOS entry.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   Return PC               <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset       <-- TOS
+##########################################################################################
+    # Prepare the screen and cursor.
+    JSR>ansiescapeclearscreen
+    JSR>ansiescapesetcursorcolumnone
+
+    # Print the data stack index
+    LDSP+1
+    JSR>printstackindex
+
+    # Print the TOS entry from the data stack in human readable form.
+    LDSP+2
+    LDSP+2
+    ADD
+    LOAD
+    JSR>printbinaryinteger
+    RTS
+
+##########################################################################################
+evalzerostackentry
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII "'", zero the current data stack entry.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII "'".
+    WORD_38 # ASCII "'"
+    IM_1
+    ADD
+    JSR>subtract
+    TEST
+    BRZ>evalzerostackentrymatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalzerostackentrymatch
+
+        # Matched, zero the stack entry and return.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+
+        IM_0
+        LDSP+4 # Data Stack Base Address
+        LDSP+4 # Data Stack Offset
+        ADD
+        STORE
+        RTS
+
+##########################################################################################
+evalstacknavigation
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII '.' or ',', inc/dec Data Stack Offset to next entry.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+    LDSP+0
+
+    # Test for ASCII ','
+    WORD_44 # ASCII ','
+    JSR>subtract
+    TEST
+    BRZ>evalstacknavigationprevmatch
+
+        # No match.
+        JMP>evalstacknavigationnexttest
+
+    evalstacknavigationprevmatch
+
+        # Matched, decrement Data Stack Offset.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+        #   ASCII character
+
+        # Decrement the Data Stack Offset
+        LDSP+3 # Data Stack Offset
+        JSR>decrementstackindex
+        STSP+3
+
+        # Clean up stack and return.
+        TEST
+        RTS
+
+    evalstacknavigationnexttest
+    # Test for ASCII '.'
+    WORD_46 # ASCII '.'
+    JSR>subtract
+    TEST
+    BRZ>evalstacknavigationnextmatch
+
+        # No match.
+        RTS
+
+    evalstacknavigationnextmatch
+
+        # Matched, increment Data Stack Offset
+
+        # Increment and return.
+        LDSP+2 # Data Stack Offset
+        JSR>incrementstackindex
+        STSP+2
+        RTS
+
+##########################################################################################
+evalnegatestackentry
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII ';', negate the current data stack entry.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII ';'
+    WORD_58 # ASCII ';'
+    IM_1
+    ADD
+    JSR>subtract
+    TEST
+    BRZ>evalnegatestackentrymatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalnegatestackentrymatch
+
+        # Matched, negate the stack entry and return.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+
+        LDSP+3 # Data Stack Base Address
+        LDSP+3 # Data Stack Offset
+        ADD
+        LDSP+0
+        LOAD
+        JSR>negate
+        SWAP
+        STORE
+        RTS
+
+##########################################################################################
+evalmathaddition
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII '+', perform addition on TOS and NOS.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII '+' (43).
+    IM_30
+    IM_13
+    ADD
+    JSR>subtract
+    TEST
+    BRZ>evalmathadditionmatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalmathadditionmatch
+
+        # Matched. Perform addition.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+
+        # Fetch the first operand.
+        LDSP+3  # Data Stack Base Address
+        LDSP+3  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Decrement Data Stack Offset.
+        LDSP+3  # Data Stack Offset
+        JSR>decrementstackindex
+        STSP+3
+
+        # Fetch the second operand.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Perform the addition.
+        ADD
+
+        # Store the result.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        STORE
+
+        # Return
+        RTS
+
+##########################################################################################
+evalmathsubtraction
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII '-', perform subtraction NOS-TOS.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII '-' (45).
+    IM_30
+    IM_15
+    ADD
+    JSR>subtract
+    TEST
+    BRZ>evalmathsubtractionmatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalmathsubtractionmatch
+
+        # Matched. Perform subtraction.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+
+        # Fetch the first operand.
+        LDSP+3  # Data Stack Base Address
+        LDSP+3  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Decrement Data Stack Offset.
+        LDSP+3  # Data Stack Offset
+        JSR>decrementstackindex
+        STSP+3
+
+        # Fetch the second operand.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Perform the subtraction.
+        JSR>subtract
+
+        # Store the result.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        STORE
+
+        # Return
+        RTS
+
+##########################################################################################
+evalmathmultiplication
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII '*', perform multiplication on TOS and NOS.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII '*' (42).
+    IM_30
+    IM_12
+    ADD
+    JSR>subtract
+    TEST
+    BRZ>evalmathmultiplicationmatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalmathmultiplicationmatch
+
+        # Matched. Perform multiplication.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+
+        # Fetch the first operand.
+        LDSP+3  # Data Stack Base Address
+        LDSP+3  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Decrement Data Stack Offset.
+        LDSP+3  # Data Stack Offset
+        JSR>decrementstackindex
+        STSP+3
+
+        # Fetch the second operand.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Perform the multiplication.
+        JSR>multiply
+
+        # Store the result.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        STORE
+
+        # Return
+        RTS
+
+##########################################################################################
+evalmathdivision
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII '/', perform division NOS/TOS.
+#   Returns quotient on TOS and remainder on NOS.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII '/' (47).
+    IM_30
+    IM_17
+    ADD
+    JSR>subtract
+    TEST
+    BRZ>evalmathdivisionmatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalmathdivisionmatch
+
+        # Matched. Perform division.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII character
+        #   Return PC <-- TOS
+
+        # Fetch the first operand.
+        LDSP+3  # Data Stack Base Address
+        LDSP+3  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Decrement Data Stack Offset.
+        LDSP+3  # Data Stack Offset
+        JSR>decrementstackindex
+        STSP+3
+
+        # Fetch the second operand.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        LOAD
+
+        # Perform the division.
+        SWAP
+        JSR>divide
+
+        # Store the remainder.
+        LDSP+5  # Data Stack Base Address
+        LDSP+5  # Data Stack Offset
+        ADD
+        STORE
+
+        # Increment the Data Stack Offset
+        LDSP+3  # Data Stack Offset
+        JSR>incrementstackindex
+        STSP+3
+
+        # Store the quotient.
+        LDSP+4  # Data Stack Base Address
+        LDSP+4  # Data Stack Offset
+        ADD
+        STORE
+
+        # Return
+        RTS
+
+##########################################################################################
+evalasciidigit
+##########################################################################################
+# Description:
+#   If character on TOS is ASCII digit, append it to current stack entry.
+# Call Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   Data Stack Base Address
+#   Data Stack Offset
+#   ASCII character
+##########################################################################################
+    SWAP
+
+    # Test for ASCII digit.
+    JSR>isasciidigit
+    TEST
+    BRZ>evalasciidigitmatch
+
+        # No match, return from subroutine
+        RTS
+
+    evalasciidigitmatch
+
+        # Matched, append result to current stack entry and return.
+
+        # Stack now looks like:
+        #   Data Stack Base Address
+        #   Data Stack Offset
+        #   ASCII digit
+        #   Return PC <-- TOS
+
+        # Load the existing entry from top of data stack.
+        LDSP+3 # Data Stack Base Address
+        LDSP+3 # Data Stack Offset
+        ADD
+        LDSP+0
+        LOAD
+
+        # Multiply existing entry by ten and add new digit to least-significant location.
+        IM_10
+        JSR>multiply
+        LDSP+3 # ASCII digit
+        JSR>atoi
+        ADD
+
+        # Put new entry back on top of data stack and return.
+        SWAP
+        STORE
+        RTS
+
+##########################################################################################
+isasciidigit
+##########################################################################################
+# Description:
+#   Tests if character is ASCII digit (0..9). Returns 0 if true, 1 if false.
+# Call Stack:
+#   ASCII character
+#   Return PC <-- TOS
+# Return Stack:
+#   <boolean, 0=true, 1=false>
+##########################################################################################
+    # Subtract ASCII '0' value, translating ASCII digit to integer.
+    SWAP
+    WORD_48         
+    SWAP
+    JSR>subtract
+    # Copy and test negative result. This would indicate an ASCII character below '0'.
+    LDSP+0
+    TEST
+    IM_12   # Address of PSW register
+    LOAD
+    IM_2
+    AND
+    TEST
+    BRZ>isasciidigitcontinued
+        # The result was negative, so clean up stack and return false.
+        IM_1
+        STSP+0
+        SWAP
+        RTS
+    isasciidigitcontinued
+    # Subtract another 10 and check for positive result. This indicates ASCII char > '9'.
+    IM_10
+    SWAP
+    JSR>subtract
+    TEST
+    IM_12
+    LOAD
+    IM_2
+    AND
+    TEST
+    BRZ>isasciidigitfalse
+        # The result was true, so clean up stack and return true.
+        IM_0
+        SWAP
+        RTS
+    isasciidigitfalse
+    # The result was false, so clean up stack and return false.
+    IM_1
+    SWAP
+    RTS
+
+##########################################################################################
+generatesignflag
+##########################################################################################
+# Description:
+#   Given a pair of twos-complement signed integers X and Y, generates a sign flag.
+#   Flag is non-zero if the product of X and Y would be negative, otherwise flag is zero.
+# Call Stack:
+#   Y Operand
+#   X Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Sign Flag <-- TOS
+##########################################################################################
+    # Place Return PC at bottom of stack.
+    LDSP+2
+    LDSP+1
+    STSP+3
+    STSP+0
+    
+    # Stack now looks like:
+    #   Return PC
+    #   X Operand
+    #   Y Operand <-- TOS
+
+    IM_4
+    LOAD
+    AND
+    SWAP
+    IM_4
+    LOAD
+    AND
+    XOR
+    SWAP
+    RTS
+    
+##########################################################################################
+negate
+##########################################################################################
+# Description:
+#   Returns the additive inverse of a twos-complement operand.
+# Call Stack:
+#   Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Result <-- TOS
+##########################################################################################
+    SWAP
+    NOT
+    IM_1
+    ADD
+    SWAP
+    RTS
+
+##########################################################################################
+absolutevalue
+##########################################################################################
+# Description:
+#   Returns the absolute value of a twos-complement operand.
+# Call Stack:
+#   Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Result <-- TOS
+##########################################################################################
+    SWAP
+    LDSP+0
+    IM_4
+    LOAD
+    AND
+    TEST
+    BRZ>absolutevaluereturn
+        JSR>negate
+
+    absolutevaluereturn
+        SWAP
+        RTS
+
+##########################################################################################
+multiply
+##########################################################################################
+# Description:
+#   Performs X*Y and returns result on TOS.
+# Call Stack:
+#   Y Operand
+#   X Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Result <-- TOS
+##########################################################################################
+    # Place Return PC at bottom of stack.
+    LDSP+2
+    LDSP+1
+    STSP+3
+    STSP+0
+    
+    # Stack now looks like:
+    #   Return PC
+    #   X Operand
+    #   Y Operand <-- TOS
+
+    # Generate a sign flag and store it behind the operands.
+    LDSP+1
+    LDSP+1
+    JSR>generatesignflag
+    LDSP+2
+    SWAP
+    STSP+2
+
+    # Stack now looks like:
+    #   Return PC
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   Y Operand
+    #   X Operand <-- TOS
+
+    # Convert both X and Y Operands to absolute value.
+    JSR>absolutevalue
+    SWAP
+    JSR>absolutevalue
+
+    # Stack now looks like:
+    #   Return PC
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   ABS(X Operand)
+    #   ABS(Y Operand) <-- TOS
+
+    # Prepare the stack for multiplication algorithm
+    IM_0                    # For magnitude of left/right shifts.
+    LDSP+0                  # Sets up stack location to build/store result.
+
+    # Check Nth bit of second operand.
+    testbit
+        LDSP+2              # Y Operand
+        LDSP+2              # Shift magnitude
+        SHIFT
+        IM_1
+        AND
+        TEST
+        BRZ>skipadd
+            # If indicated by a 1 bit, shift and add first operand to result.
+            LDSP+3          # X Operand
+            IM_4            # 0x80000000
+            LOAD
+            LDSP+3          # Shift magnitude
+            OR
+            SHIFT
+            ADD
+        skipadd
+
+    # Increment shift magnitude counter.
+    LDSP+1                  # Shift magnitude
+    IM_1
+    ADD
+    STSP+1
+
+    # Test for completion of multiplication algorithm (31 shifts).
+    LDSP+1                  # Shift magnitude
+    IM_30
+    JSR>subtract
+    TEST
+    BRZ>multiplycleanup
+        JMP>testbit
+
+    # Clean up the the stack after performing multiplication.
+    multiplycleanup
+        STSP+0
+        STSP+0
+        STSP+0
+
+    # Stack now looks like:
+    #   Return PC
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   ABS(X*Y) <-- TOS
+
+    # Set the sign of the product.
+    applysign
+        SWAP
+        TEST
+        BRZ>multiplyreturn
+            JSR>negate
+
+    # Clean up and return.
+    multiplyreturn
+        SWAP
+        RTS
+
+##########################################################################################
+divide
+##########################################################################################
+# Description:
+#   Division with remainder.
+#   Halts on zero divisor.
+# Call Stack:
+#   Dividend
+#   Divisor
+#   Return PC <-- TOS
+# Return Stack:
+#   Quotient
+#   Remainder <-- TOS
+##########################################################################################
+    # Move Return PC to bottom of stack.
+    LDSP+2
+    SWAP
+    STSP+2
+    SWAP
+
+    # Stack now looks like:
+    #   Return PC
+    #   Dividend
+    #   Divisor <-- TOS
+
+    # Check for zero divisor
+    LDSP+0
+    TEST
+    BRZ>divideexception
+
+    # Generate a sign flag and store it behind the operands.
+    LDSP+1
+    LDSP+1
+    JSR>generatesignflag
+    LDSP+2
+    SWAP
+    STSP+2
+
+    # Stack now looks like:
+    #   Return PC
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   Divisor
+    #   Dividend <-- TOS
+
+    # Convert both Dividend and Divisor to absolute value.
+    JSR>absolutevalue
+    SWAP
+    JSR>absolutevalue
+
+    # Stack now looks like:
+    #   Return PC
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   ABS(Dividend)
+    #   ABS(Divisor) <-- TOS
+
+    # Prepare stack for division algorithm.
+    IM_31           # Cycle Counter - Loop from n-1 -> 0 where n = 32 bits.
+    IM_0            # Quotient
+    IM_0            # Remainder
+
+    # Binary long division
+    divisionloop
+        # Check Cycle Counter for end-of-loop condition (CC = -1).
+        LDSP+2 # Cycle Counter
+        IM_1
+        ADD
+        TEST
+        BRZ>divisionloopend
+
+            # While Cycle Counter >= 0
+
+            # Left-shift Remainder by 1 bit.
+            IM_1
+            IM_4 # Address of 0x80000000 register
+            LOAD
+            OR
+            SHIFT
+    
+            # Set LSB of Remainder equal to bit (Cycle Counter) of Dividend.
+            LDSP+4 # Dividend
+            LDSP+3 # Cycle Counter
+            SHIFT
+            IM_1
+            AND
+            OR
+    
+            # Check if Remainder >= Divisor
+            LDSP+3 # Divisor
+            LDSP+1 # Remainder
+            JSR>subtract
+            TEST
+            IM_12 # Address of PSW register
+            LOAD
+            IM_2 # Bit for Negative Flag in PSW
+            AND
+            IM_2
+            XOR
+            TEST
+            BRZ>divisionsubloopend
+
+                # If Remainder >= Divisor
+
+                # Set Remainder = Remainder - Divisor
+                LDSP+3 # Divisor
+                SWAP
+                JSR>subtract
+
+                # Set bit (Cycle Counter) of Quotient equal to 1.
+                SWAP
+                IM_1
+                LDSP+3 # Cycle Counter
+                IM_4
+                LOAD
+                OR
+                SHIFT
+                OR
+                SWAP
+
+            divisionsubloopend
+
+            # Decrement Cycle Counter
+            IM_1
+            LDSP+3 # Cycle Counter
+            JSR>subtract
+            STSP+2
+
+            # Loop over next division cycle
+            JMP>divisionloop
+
+        divisionloopend
+
+        # Stack cleanup
+        STSP+1
+        STSP+1
+        STSP+1
+
+    # Stack now looks like:
+    #   Return PC
+    #   Sign flag (nonzero for negative result, 0 for positive result)
+    #   Remainder
+    #   Quotient <-- TOS
+
+    # Set sign of results.
+    LDSP+2 # Sign flag
+    TEST
+    BRZ>divisioncleanup
+        JSR>negate
+        SWAP
+        JSR>negate
+        SWAP
+
+    divisioncleanup
+
+    # Clean up and return
+    SWAP
+    LDSP+3 # Return PC
+    SWAP
+    STSP+2
+    SWAP
+    STSP+2
+    RTS
+
+    # For now we can only HALT on errors and let the PC inform our debugging.
+    divideexception
+        HALT
+
+##########################################################################################
+subtract
+##########################################################################################
+# Description:
+#   Performs X-Y and returns result on TOS.
+# Call Stack:
+#   Y Operand
+#   X Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Result <-- TOS
+##########################################################################################
+    # Place Return PC at bottom of stack.
+    LDSP+2
+    LDSP+1
+    STSP+3
+    STSP+0
+    # Stack now looks like:
+    #   Return PC
+    #   X Operand
+    #   Y Operand <-- TOS
+
+    JSR>negate
+    ADD
+    SWAP
+    RTS
+
+##########################################################################################
+atoi
+##########################################################################################
+# Description:
+#   Converts an ASCII decimal into the corresponding integer value.
+#   No bounds checks; assumed to already be performed in isasciidigit().
+# Call Stack:
+#   Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Result <-- TOS
+##########################################################################################
+    SWAP
+    WORD_48
+    SWAP
+    JSR>subtract
+    SWAP
+    RTS
+
+##########################################################################################
+itoa
+##########################################################################################
+# Description:
+#   Converts an integer (0..9) into the corresponding ASCII character.
+# Call Stack:
+#   Operand
+#   Return PC <-- TOS
+# Return Stack:
+#   Result <-- TOS
+##########################################################################################
+    # Copy operand to TOS
+    LDSP+1
+    # Verify that operand < 10
+    IM_10
+    LDSP+1
+    JSR>subtract
+    TEST # Set PSW according to difference.
+    # Branch if non-negative:
+    IM_12 # Address of PSW
+    LOAD
+    IM_2
+    AND
+    TEST
+    BRZ>itoahalt
+    # Verify that operand > -1
+    LDSP+0
+    TEST # Set PSW according to operand.
+    # Branch if negative:
+    IM_12
+    LOAD
+    IM_2
+    AND
+    IM_2
+    XOR
+    TEST
+    BRZ>itoahalt # Branch if operand was negative.
+    # Convert the integer to its ASCII representation and return to caller.
+    WORD_48
+    ADD
+    STSP+1
+    RTS
+    # Halt on error
+    itoahalt
+        HALT
+
+##########################################################################################
+putchar
+##########################################################################################
+# Description:
+#   Writes one character to the terminal.
+# Call Stack:
+#   Character to write
+#   Return PC <-- TOS
+# Return Stack:
+#   <empty>
+##########################################################################################
+    WORD_134217728      # XBUF
+    WORD_134217732      # XCSR
+    LDSP+3
+    SWAP
+    putcharloop
+        LDSP+0
+        LOAD
+        TEST
+        BRZ>putcharloop
+    TEST # Drop XCSR from stack
+    SWAP
+    STORE
+    # Wrote the character. Clean up stack and return.
+    STSP+0
+    RTS
+
+##########################################################################################
+getchar
+##########################################################################################
+# Description:
+#   Reads one character from the terminal.
+# Call Stack:
+#   Return PC <-- TOS
+# Return Stack:
+#   Character <-- TOS
+##########################################################################################
+    WORD_134217736      # RBUF
+    WORD_134217740      # RCSR
+    getcharloop
+        LDSP+0
+        LOAD
+        TEST
+        BRZ>getcharloop
+    LDSP+1
+    LOAD
+    # Found a character. Clean up stack and return.
+    STSP+0
+    STSP+0
+    SWAP
+    RTS
+
+##########################################################################################
+printstring
+##########################################################################################
+# Description:
+#   Prints a null-terminated string located on the stack.
+# Call Stack:
+#   ASCII NUL
+#   ASCII character
+#   ...
+#   ASCII character
+#   Return PC <-- TOS
+##########################################################################################
+    SWAP
+    BRZ>printstringreturn
+    JSR>putchar
+    JMP>printstring
+
+    printstringreturn
+        TEST
+        RTS
+
+##########################################################################################
+ansiescapeclearscreen
+##########################################################################################
+# Description:
+#   Clears the entire screen.
+# Call Stack:
+#   Return PC <-- TOS
+# Return Stack:
+#   <empty>
+##########################################################################################
+    # ASCII NUL
+    WORD_0
+    # ASCII 'J'
+    WORD_74
+    # ASCII '2'
+    WORD_50
+    # ASCII '['
+    WORD_90
+    IM_1
+    ADD
+    # ASCII ESC
+    WORD_26
+    IM_1
+    ADD
+    # Print string and return
+    JSR>printstring
+    RTS
+
+##########################################################################################
+ansiescapesetcursorcolumnone
+##########################################################################################
+# Description:
+#   Reset cursor to column 1 of screen.
+# Call Stack:
+#   Return PC <-- TOS
+# Return Stack:
+#   <empty>
+##########################################################################################
+    # ASCII NUL
+    WORD_0
+    # ASCII 'G'
+    WORD_70
+    IM_1
+    ADD
+    # ASCII '1'
+    WORD_48
+    IM_1
+    ADD
+    # ASCII '['
+    WORD_90
+    IM_1
+    ADD
+    # ASCII ESC
+    WORD_26
+    IM_1
+    ADD
+    # Print string and return
+    JSR>printstring
+    RTS
diff --git a/software/README.md b/software/README.md
new file mode 100644 (file)
index 0000000..737508e
--- /dev/null
@@ -0,0 +1,26 @@
+NED Software
+============
+
+This directory contains software written for NED.
+
+Each folder contains a `README.md` that specifies the required version of
+`nedasm`.  Remember to check `../docs/compat_matrix.md` to determine
+corresponding compatible `nedsim` versions.
+
+Most software in this directory includes targets `make` for building, `make
+sim` for execution, and `make clean` for cleanup. The first two require
+`nedasm` and `nedsim` in the user's `PATH` or a full path in
+`../common/main.mk` for the `NEDASM` and `NEDSIM` variables.
+
+Program Listing
+===============
+
+    4func_calculator/
+
+An integer only, four function, stack based calculator. The first non-trivial
+program written for NED.
+
+    assembly_fragments/
+
+This folder contains fragments of assembly code used during the development of
+nedasm. They are of little interest on their own.
diff --git a/software/assembly_fragments/README.md b/software/assembly_fragments/README.md
new file mode 100644 (file)
index 0000000..57f206a
--- /dev/null
@@ -0,0 +1,10 @@
+Overview
+========
+
+This directory contains fragments of assembly code used to test the first
+version of `nedasm`. They are compatible with at least `nedasm` v1 but no
+effort will be made to keep them up to date past this point.
+
+No makefile is provided. Assembly must be done manually. Multi-file programs
+like the subroutine test must be manually concatenated before passing to
+`nedasm`.
diff --git a/software/assembly_fragments/ansi_escape.asm b/software/assembly_fragments/ansi_escape.asm
new file mode 100644 (file)
index 0000000..e2bc5bd
--- /dev/null
@@ -0,0 +1,77 @@
+# ASCII NUL
+WORD_0
+
+# ASCII 'G'
+WORD_70
+IM_1
+ADD
+
+# ASCII '1'
+WORD_48
+IM_1
+ADD
+
+# ASCII '['
+WORD_90
+IM_1
+ADD
+
+# ASCII ESC
+WORD_26
+IM_1
+ADD
+
+# ASCII 'K'
+WORD_74
+IM_1
+ADD
+
+# ASCII '2'
+WORD_50
+
+# ASCII '['
+WORD_90
+IM_1
+ADD
+
+# ASCII ESC
+WORD_26
+IM_1
+ADD
+
+# ASCII 'H'
+WORD_72
+
+
+# Print characters
+printstring
+    LDSP+0
+    TEST
+    BRZ>halt
+        JSR>putchar
+        JMP>printstring
+halt
+    TEST # Clean the null terminator off the stack.
+    HALT
+
+putchar
+# Description:
+#   Writes one character to the terminal.
+# Stack Requirements:
+#   Character to write
+#   Return PC <-- TOS
+    WORD_134217728      # XBUF
+    WORD_134217732      # XCSR
+    LDSP+3
+    SWAP
+putcharloop
+    LDSP+0
+    LOAD
+    TEST
+    BRZ>putcharloop
+    TEST # Drop XCSR from stack
+    SWAP
+    STORE
+    # Wrote the character. Clean up stack and return.
+    STSP+0
+    RTS
diff --git a/software/assembly_fragments/echo.asm b/software/assembly_fragments/echo.asm
new file mode 100644 (file)
index 0000000..91e83e4
--- /dev/null
@@ -0,0 +1,25 @@
+echo
+    WORD_134217728      # XBUF
+    WORD_134217732      # XCSR
+    WORD_134217736      # RBUF
+    WORD_134217740      # RCSR
+
+getchar
+    LDSP+0
+    LOAD
+    TEST
+    BRZ>getchar
+    LDSP+1
+    LOAD
+                        # A character is now on the TOS and ready to be printed to the terminal.
+putchar
+    LDSP+3
+    LOAD
+    TEST
+    BRZ>putchar
+    LDSP+4
+    STORE
+                        # Now wait for the next incoming character.
+    IM_0
+    TEST
+    BRZ>getchar
diff --git a/software/assembly_fragments/labels.asm b/software/assembly_fragments/labels.asm
new file mode 100644 (file)
index 0000000..0602394
--- /dev/null
@@ -0,0 +1,13 @@
+# Operands
+WORD_174
+WORD_186
+
+# Label test
+
+IM_0
+TEST
+BRZ>continue
+HALT
+continue
+ADD
+HALT
diff --git a/software/assembly_fragments/multiply.asm b/software/assembly_fragments/multiply.asm
new file mode 100644 (file)
index 0000000..9787a7c
--- /dev/null
@@ -0,0 +1,55 @@
+# Operands
+WORD_174
+WORD_186
+
+# Multiplication routine
+# Restrictions:
+#   Positive numbers only (31 shifts max since operands are twos-complement)
+#   No overflow checking
+
+multiply
+    WORD_2147483648     # For use in setting sign for left shifts.
+    IM_0                # For magnitude of left/right shifts.
+    LDSP+0              # Sets up stack location to build/store result.
+                        #-----
+testbit                 #
+    LDSP+3              #
+    LDSP+2              #
+    SHIFT               # Check Nth bit of second operand.
+    IM_1                #
+    AND                 #
+    TEST                #
+                        #-----
+    BRZ>skipadd         #
+    LDSP+4              #
+    LDSP+3              #
+    LDSP+3              #
+    OR                  # Shift and add first operand to result if indicated.
+    SHIFT               #
+    ADD                 #
+skipadd                 #
+                        #-----
+    LDSP+1              #
+    IM_1                # Increment counter for shift magnitude
+    ADD                 #
+    STSP+1              #
+                        #-----
+    IM_30               #
+    NOT                 #
+    IM_1                #
+    ADD                 # Test for completion of multiplication subroutine (31 shifts).
+    LDSP+2              #
+    ADD                 #
+    TEST                #
+    BRZ>cleanup         # If finished, cleanup and return from subroutine with result on TOS.
+                        #-----
+    IM_0                #
+    TEST                # If not finished, repeat this process on the next bit.
+    BRZ>testbit         #
+                        #-----
+cleanup                 #
+    STSP+0              #
+    STSP+0              #
+    STSP+0              # Remove all subroutine temporaries from stack and return result on TOS.
+    STSP+0              #
+    HALT                #
diff --git a/software/assembly_fragments/negation.asm b/software/assembly_fragments/negation.asm
new file mode 100644 (file)
index 0000000..cedcced
--- /dev/null
@@ -0,0 +1,8 @@
+# Value to negate
+WORD_256
+
+#####################
+NOT
+IM_1
+ADD
+HALT
diff --git a/software/assembly_fragments/subroutines/sr_itoa.asm b/software/assembly_fragments/subroutines/sr_itoa.asm
new file mode 100644 (file)
index 0000000..6a478e1
--- /dev/null
@@ -0,0 +1,71 @@
+itoa
+# Description:
+#   Converts an integer in the decimal range 0..9 into the corresponding ASCII character value.
+# Stack Requirements:
+#   Operand
+#   Return PC <-- TOS
+
+
+# Copy operand to TOS
+LDSP+1
+
+
+# Verify that operand < 10
+IM_10
+LDSP+1
+# Call subtract subroutine
+# TODO: Put Return PC on TOS
+#       This requires a guarantee that I copy the PC in the 'correct' word, not too early. Will need NOP padding in nedasm.
+#       Make sure PC points to NEXT word, not to CURRENT word. Increment immediately after fetching a new word.
+#       Maybe define a JSR and RTS mnemonic pair that expands to the correct, real assembly in nedasm?
+#       Probably need to add a field to instruction_struct specifying that it should be the start of a new word. This also involves the code generation function.
+# Assume low memory is:
+#   0x00 Zero register
+#   0x04 0x8000000 register
+#   0x08 PC
+#   0x0C PSW
+#   0x10 reserved
+#   0x14 reserved
+#   0x18 reserved
+#   0x1C reserved
+# Implement this as:
+#   WORD_&subtract
+#   IM_0x08
+#   LOAD
+#   SWAP
+#   JMP
+JSR>subtract
+TEST # Set PSW according to difference.
+# Branch if non-negative:
+IM_12 # Address of PSW
+LOAD
+IM_2
+AND
+TEST
+BRZ>itoahalt
+
+
+# Verify that operand > -1
+LDSP+0
+TEST # Set PSW according to operand.
+# Branch if negative:
+IM_12
+LOAD
+IM_2
+AND
+IM_2
+XOR
+TEST
+BRZ>itoahalt # Branch if operand was negative.
+
+# Convert the integer to its ASCII representation and return to caller.
+WORD_48
+ADD
+STSP+1
+RTS # Implement as unconditional jump since Return PC is already on TOS.
+
+
+# Halt on error
+itoahalt
+HALT
+
diff --git a/software/assembly_fragments/subroutines/sr_subtract.asm b/software/assembly_fragments/subroutines/sr_subtract.asm
new file mode 100644 (file)
index 0000000..ace2155
--- /dev/null
@@ -0,0 +1,29 @@
+subtract
+# Description:
+#   Performs X-Y and returns result on TOS.
+# Stack Requirements:
+#   Y Operand
+#   X Operand
+#   Return PC <-- TOS
+
+# Place Return PC at bottom of stack.
+LDSP+2
+LDSP+1
+STSP+3
+STSP+0
+
+# Stack now looks like:
+#   Return PC
+#   X Operand
+#   Y Operand <-- TOS
+
+# Negate Y
+NOT
+IM_1
+ADD
+
+ADD # Perform X + (-Y)
+
+SWAP # Put result behind Return PC on the stack.
+RTS
+
diff --git a/software/assembly_fragments/subroutines/sr_terminalIO.asm b/software/assembly_fragments/subroutines/sr_terminalIO.asm
new file mode 100644 (file)
index 0000000..9b5d9be
--- /dev/null
@@ -0,0 +1,41 @@
+putchar
+# Description:
+#   Writes one character to the terminal.
+# Stack Requirements:
+#   Character to write
+#   Return PC <-- TOS
+    WORD_134217728      # XBUF
+    WORD_134217732      # XCSR
+    LDSP+3
+    SWAP
+putcharloop
+    LDSP+0
+    LOAD
+    TEST
+    BRZ>putcharloop
+    TEST # Drop XCSR from stack
+    SWAP
+    STORE
+    # Wrote the character. Clean up stack and return.
+    STSP+0
+    RTS
+
+getchar
+# Description:
+#   Reads one character from the terminal.
+# Stack Requirements:
+#   Return PC <-- TOS
+    WORD_134217736      # RBUF
+    WORD_134217740      # RCSR
+getcharloop
+    LDSP+0
+    LOAD
+    TEST
+    BRZ>getcharloop
+    LDSP+1
+    LOAD
+    # Found a character. Clean up stack and return.
+    STSP+0
+    STSP+0
+    SWAP
+    RTS
diff --git a/software/assembly_fragments/subroutines/subroutines.asm b/software/assembly_fragments/subroutines/subroutines.asm
new file mode 100644 (file)
index 0000000..4c5aa75
--- /dev/null
@@ -0,0 +1,8 @@
+# Subtract two numbers and print the (single decimal digit) result
+IM_9
+IM_15
+JSR>subtract
+JSR>itoa
+JSR>putchar
+HALT
+