From bc5b63cf94c1f263bbe3757237b242f40711203b Mon Sep 17 00:00:00 2001 From: Aaron Taylor Date: Sun, 23 Dec 2018 04:26:28 -0800 Subject: [PATCH] Initial commit of files related to NED architecture. Includes nedsim/nedasm/neddis and a calculator written in NED assembly. --- LICENSE.txt | 21 + Makefile | 14 + README.md | 29 + common/main.mk | 14 + docs/architecture_manual.md | 125 ++ docs/compat_matrix.md | 12 + docs/design_goals.md | 25 + docs/instruction_reference.md | 267 +++ misc/clocktest.c | 24 + nedasm/Makefile | 14 + nedasm/README.md | 25 + nedasm/nedasm.c | 220 +++ nedasm/nedasm_codegen.c | 154 ++ nedasm/nedasm_codegen.h | 13 + nedasm/nedasm_misc.h | 25 + nedasm/nedasm_parser.c | 224 +++ nedasm/nedasm_parser.h | 15 + nedasm/nedasm_parser_extensions.c | 63 + nedasm/nedasm_parser_extensions.h | 15 + nedasm/nedasm_structures.c | 103 ++ nedasm/nedasm_structures.h | 79 + neddis/Makefile | 14 + neddis/README.md | 20 + neddis/neddis.c | 221 +++ nedsim/Makefile | 14 + nedsim/README.md | 35 + nedsim/nedsim.c | 650 ++++++++ software/4func_calculator/Makefile | 18 + software/4func_calculator/README.md | 28 + software/4func_calculator/calc.asm | 1465 +++++++++++++++++ software/README.md | 26 + software/assembly_fragments/README.md | 10 + software/assembly_fragments/ansi_escape.asm | 77 + software/assembly_fragments/echo.asm | 25 + software/assembly_fragments/labels.asm | 13 + software/assembly_fragments/multiply.asm | 55 + software/assembly_fragments/negation.asm | 8 + .../subroutines/sr_itoa.asm | 71 + .../subroutines/sr_subtract.asm | 29 + .../subroutines/sr_terminalIO.asm | 41 + .../subroutines/subroutines.asm | 8 + 41 files changed, 4309 insertions(+) create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 common/main.mk create mode 100644 docs/architecture_manual.md create mode 100644 docs/compat_matrix.md create mode 100644 docs/design_goals.md create mode 100644 docs/instruction_reference.md create mode 100644 misc/clocktest.c create mode 100644 nedasm/Makefile create mode 100644 nedasm/README.md create mode 100644 nedasm/nedasm.c create mode 100644 nedasm/nedasm_codegen.c create mode 100644 nedasm/nedasm_codegen.h create mode 100644 nedasm/nedasm_misc.h create mode 100644 nedasm/nedasm_parser.c create mode 100644 nedasm/nedasm_parser.h create mode 100644 nedasm/nedasm_parser_extensions.c create mode 100644 nedasm/nedasm_parser_extensions.h create mode 100644 nedasm/nedasm_structures.c create mode 100644 nedasm/nedasm_structures.h create mode 100644 neddis/Makefile create mode 100644 neddis/README.md create mode 100644 neddis/neddis.c create mode 100644 nedsim/Makefile create mode 100644 nedsim/README.md create mode 100644 nedsim/nedsim.c create mode 100644 software/4func_calculator/Makefile create mode 100644 software/4func_calculator/README.md create mode 100644 software/4func_calculator/calc.asm create mode 100644 software/README.md create mode 100644 software/assembly_fragments/README.md create mode 100644 software/assembly_fragments/ansi_escape.asm create mode 100644 software/assembly_fragments/echo.asm create mode 100644 software/assembly_fragments/labels.asm create mode 100644 software/assembly_fragments/multiply.asm create mode 100644 software/assembly_fragments/negation.asm create mode 100644 software/assembly_fragments/subroutines/sr_itoa.asm create mode 100644 software/assembly_fragments/subroutines/sr_subtract.asm create mode 100644 software/assembly_fragments/subroutines/sr_terminalIO.asm create mode 100644 software/assembly_fragments/subroutines/subroutines.asm diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d6b0400 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT/X Consortium License + +© 2018 Aaron Taylor + +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 index 0000000..d45d9da --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +# © 2018 Aaron Taylor +# 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 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 index 0000000..e8c7578 --- /dev/null +++ b/common/main.mk @@ -0,0 +1,14 @@ +# © 2018 Aaron Taylor +# 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 index 0000000..262c179 --- /dev/null +++ b/docs/architecture_manual.md @@ -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 index 0000000..7e4bd2b --- /dev/null +++ b/docs/compat_matrix.md @@ -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 index 0000000..8cddd3c --- /dev/null +++ b/docs/design_goals.md @@ -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 index 0000000..d8bc473 --- /dev/null +++ b/docs/instruction_reference.md @@ -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 index 0000000..c3d3817 --- /dev/null +++ b/misc/clocktest.c @@ -0,0 +1,24 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include + +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 index 0000000..93fd797 --- /dev/null +++ b/nedasm/Makefile @@ -0,0 +1,14 @@ +# © 2018 Aaron Taylor +# 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 index 0000000..f67f010 --- /dev/null +++ b/nedasm/README.md @@ -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 index 0000000..4b4d3bc --- /dev/null +++ b/nedasm/nedasm.c @@ -0,0 +1,220 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include +#include +#include +#include + +#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 Specify file name of assembly code input.\n" + " -o 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 index 0000000..9484a1d --- /dev/null +++ b/nedasm/nedasm_codegen.c @@ -0,0 +1,154 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include +#include + +#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 index 0000000..64af0be --- /dev/null +++ b/nedasm/nedasm_codegen.h @@ -0,0 +1,13 @@ +/* + * © 2018 Aaron Taylor + * 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 index 0000000..1f43b12 --- /dev/null +++ b/nedasm/nedasm_misc.h @@ -0,0 +1,25 @@ +/* + * © 2018 Aaron Taylor + * 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 index 0000000..4dc2a41 --- /dev/null +++ b/nedasm/nedasm_parser.c @@ -0,0 +1,224 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include +#include +#include +#include + +#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 index 0000000..62c247f --- /dev/null +++ b/nedasm/nedasm_parser.h @@ -0,0 +1,15 @@ +/* + * © 2018 Aaron Taylor + * 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 index 0000000..d02e62b --- /dev/null +++ b/nedasm/nedasm_parser_extensions.c @@ -0,0 +1,63 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include + +#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 index 0000000..0f89ecc --- /dev/null +++ b/nedasm/nedasm_parser_extensions.h @@ -0,0 +1,15 @@ +/* + * © 2018 Aaron Taylor + * 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 index 0000000..a5ab3ef --- /dev/null +++ b/nedasm/nedasm_structures.c @@ -0,0 +1,103 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include + +#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 index 0000000..f295052 --- /dev/null +++ b/nedasm/nedasm_structures.h @@ -0,0 +1,79 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#ifndef NEDASM_STRUCTURES_H +#define NEDASM_STRUCTURES_H + +#include + +/* 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 index 0000000..b24f14e --- /dev/null +++ b/neddis/Makefile @@ -0,0 +1,14 @@ +# © 2018 Aaron Taylor +# 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 index 0000000..343fea6 --- /dev/null +++ b/neddis/README.md @@ -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 index 0000000..96256d0 --- /dev/null +++ b/neddis/neddis.c @@ -0,0 +1,221 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include +#include +#include +#include + +#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 \n" + " -h Help (prints this message)\n" + " -i 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 index 0000000..f34d74d --- /dev/null +++ b/nedsim/Makefile @@ -0,0 +1,14 @@ +# © 2018 Aaron Taylor +# 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 index 0000000..846cb34 --- /dev/null +++ b/nedsim/README.md @@ -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 index 0000000..9bdaa59 --- /dev/null +++ b/nedsim/nedsim.c @@ -0,0 +1,650 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \n" + " -h Help (prints this message)\n" + " -i Specify a binary image file to load in RAM.\n" + " -p 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, ¤t_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 index 0000000..05769ed --- /dev/null +++ b/software/4func_calculator/Makefile @@ -0,0 +1,18 @@ +# © 2018 Aaron Taylor +# 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 index 0000000..3b6dd29 --- /dev/null +++ b/software/4func_calculator/README.md @@ -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 index 0000000..4386392 --- /dev/null +++ b/software/4func_calculator/calc.asm @@ -0,0 +1,1465 @@ +# (c) 2018 Aaron Taylor +# 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: +# +########################################################################################## + # 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: +# +########################################################################################## + # 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: +# +########################################################################################## + # 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: +# +########################################################################################## + 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: +# +########################################################################################## + # 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: +# +########################################################################################## + # 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 index 0000000..737508e --- /dev/null +++ b/software/README.md @@ -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 index 0000000..57f206a --- /dev/null +++ b/software/assembly_fragments/README.md @@ -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 index 0000000..e2bc5bd --- /dev/null +++ b/software/assembly_fragments/ansi_escape.asm @@ -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 index 0000000..91e83e4 --- /dev/null +++ b/software/assembly_fragments/echo.asm @@ -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 index 0000000..0602394 --- /dev/null +++ b/software/assembly_fragments/labels.asm @@ -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 index 0000000..9787a7c --- /dev/null +++ b/software/assembly_fragments/multiply.asm @@ -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 index 0000000..cedcced --- /dev/null +++ b/software/assembly_fragments/negation.asm @@ -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 index 0000000..6a478e1 --- /dev/null +++ b/software/assembly_fragments/subroutines/sr_itoa.asm @@ -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 index 0000000..ace2155 --- /dev/null +++ b/software/assembly_fragments/subroutines/sr_subtract.asm @@ -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 index 0000000..9b5d9be --- /dev/null +++ b/software/assembly_fragments/subroutines/sr_terminalIO.asm @@ -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 index 0000000..4c5aa75 --- /dev/null +++ b/software/assembly_fragments/subroutines/subroutines.asm @@ -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 + -- 2.20.1