├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── SCHEMATIC.png ├── SPEC.txt ├── asm ├── asm.c ├── asm.h ├── directive.c ├── eval.c ├── lex.c ├── lex.h ├── macro.c ├── macros.asm └── microcode.c ├── circ ├── jdh8.circ └── microcode.asm ├── common ├── jdh8.h ├── jdh8util.c ├── jdh8util.h └── util.h ├── emu ├── emu.c ├── emu.h ├── kb.c ├── libemu.c ├── mod.c ├── screen.c └── win │ ├── mman.c │ ├── mman.h │ ├── win.c │ └── win.h ├── images ├── BUILD.png └── PONG.png ├── misc ├── aep │ ├── aep.ino │ ├── aep.py │ └── old │ │ ├── Makefile │ │ └── main.c ├── avt │ ├── Makefile │ └── main.c ├── chk │ ├── correct.txt │ └── gpuchk.py ├── images │ ├── gfx.png │ ├── image.png │ ├── image2.png │ ├── t4w.png │ └── test.png ├── imc │ └── imc.py └── tests │ ├── test.asm │ ├── test2.asm │ ├── test3.asm │ ├── test4.asm │ └── test5.asm ├── os ├── arch.asm ├── lib │ ├── font.asm │ ├── gfx.asm │ ├── math.asm │ ├── shift.asm │ ├── term.asm │ └── util.asm ├── os.asm ├── oscall.asm ├── ostest.asm └── ostext.asm ├── programs ├── demo.asm ├── holiday │ ├── holiday.asm │ ├── holiday.png │ └── image.asm └── pong.asm ├── syntax └── jdh8.vim └── test ├── char.asm ├── complete.asm ├── data.asm ├── esc.asm ├── eval.asm ├── extras.asm ├── fib.asm ├── jump.asm ├── mem.asm ├── mw.asm ├── ops.asm ├── proc.asm ├── stack.asm └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | asm/builtin_macros.c 2 | .DS_Store 3 | out.bin* 4 | compile_flags.txt 5 | 6 | # binaries 7 | bin/ 8 | 9 | # asm out files 10 | *.bin 11 | 12 | # Prerequisites 13 | *.d 14 | 15 | # Object files 16 | *.o 17 | *.ko 18 | *.obj 19 | *.elf 20 | 21 | # Linker output 22 | *.ilk 23 | *.map 24 | *.exp 25 | 26 | # Precompiled Headers 27 | *.gch 28 | *.pch 29 | 30 | # Libraries 31 | *.lib 32 | *.a 33 | *.la 34 | *.lo 35 | 36 | # Shared objects (inc. Windows DLLs) 37 | *.dll 38 | *.so 39 | *.so.* 40 | *.dylib 41 | 42 | # Executables 43 | *.exe 44 | *.out 45 | *.app 46 | *.i*86 47 | *.x86_64 48 | *.hex 49 | 50 | # Debug files 51 | *.dSYM/ 52 | *.su 53 | *.idb 54 | *.pdb 55 | 56 | # Kernel Module Compile Results 57 | *.mod* 58 | *.cmd 59 | .tmp_versions/ 60 | modules.order 61 | Module.symvers 62 | Mkfile.old 63 | dkms.conf 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 jdah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | XXD=xxd 2 | 3 | ifneq ($(OS),Windows_NT) 4 | ARCH_LINUX := $(shell grep "Arch Linux" /etc/os-release 2>/dev/null) 5 | 6 | ifeq ($(ARCH_LINUX),) 7 | SDL_NAME = sdl 8 | else 9 | SDL_NAME = SDL 10 | endif 11 | 12 | CC=clang 13 | LD=clang 14 | CCFLAGS = -std=c11 -O2 -g -Wall -Wextra -Wpedantic -Wstrict-aliasing 15 | CCFLAGS += -Wno-pointer-arith -Wno-unused-parameter 16 | CCFLAGS += -Wno-gnu-zero-variadic-macro-arguments -Wno-gnu-statement-expression 17 | CCFLAGS += $(shell sdl2-config --cflags) 18 | LDFLAGS = 19 | 20 | EMULD=-lreadline -lpthread $(shell sdl2-config --libs) 21 | ASMLD= 22 | TESTLD= 23 | 24 | EMU_WIN_SRC= 25 | EMU_WIN_OBJ= 26 | 27 | DIRCMD=mkdir -p bin 28 | CLEAN=clean_posix 29 | else 30 | CC=gcc 31 | LD=gcc 32 | LIBPATH=C:/msys64/mingw64 33 | SDL2CFG=$(C:/msys64/mingw64/bin/sdl-config --cflags --libs) 34 | 35 | CCFLAGS = -std=c11 -O2 -g -Wall -Wextra -Wpedantic -Wstrict-aliasing 36 | CCFLAGS += -I$(LIBPATH)/include/ -v -Wno-pointer-arith 37 | CCFLAGS += -Wno-unused-parameter -Wno-gnu-zero-variadic-macro-arguments 38 | CCFLAGS += -Wno-gnu-statement-expression 39 | LDFLAGS = 40 | EMULD = -lsdl2main -lsdl2 41 | ASMLD = 42 | TESTLD = 43 | EMU_WIN_SRC = $(wildcard emu/win/*.c) 44 | EMU_WIN_OBJ = $(EMU_WIN_SRC:.c=.o) 45 | DIRCMD = if not exist bin mkdir bin 46 | CLEAN = clean_win 47 | endif 48 | 49 | BUILTIN_MACROS_ASM = asm/macros.asm 50 | BUILTIN_MACROS_C = asm/builtin_macros.c 51 | BUILTIN_MACROS_O = $(BUILTIN_MACROS_C:.c=.o) 52 | 53 | EMU_SRC = $(wildcard emu/*.c) 54 | EMU_OBJ = $(EMU_SRC:.c=.o) 55 | EMU_OBJ += common/jdh8util.o 56 | 57 | ASM_SRC = $(wildcard asm/*.c) 58 | ASM_OBJ = $(ASM_SRC:.c=.o) 59 | 60 | TEST_SRC = $(wildcard test/*.c) 61 | TEST_OBJ = $(TEST_SRC:.c=.o) 62 | TEST_OBJ += emu/libemu.o 63 | 64 | EMU = bin/emu 65 | ASM = bin/asm 66 | TEST = bin/test 67 | 68 | all: emu asm test 69 | 70 | clean: $(CLEAN) 71 | 72 | clean_win: 73 | if exist bin rmdir /s /q bin 74 | del /S /Q *.o *.exe *.dll 75 | del /S /Q $(BUILTIN_MACROS_C) 76 | 77 | clean_posix: 78 | rm -f $(BUILTIN_MACROS_C) 79 | rm -rf ./bin 80 | rm -f ./**/*.o 81 | 82 | %.o: %.c 83 | $(CC) -o $@ -c $< $(CCFLAGS) 84 | 85 | dirs: 86 | $(DIRCMD) 87 | 88 | emu: dirs $(EMU_OBJ) $(EMU_WIN_OBJ) 89 | $(LD) -o $(EMU) $(filter %.o, $^) $(EMULD) $(LDFLAGS) 90 | 91 | builtin_macros: 92 | $(XXD) -i $(BUILTIN_MACROS_ASM) > $(BUILTIN_MACROS_C) 93 | $(CC) -o $(BUILTIN_MACROS_O) -c $(BUILTIN_MACROS_C) $(CCFLAGS) 94 | 95 | asm: dirs builtin_macros $(ASM_OBJ) 96 | $(LD) -o $(ASM) $(ASMLD) $(LDFLAGS) $(filter %.o, $^) $(BUILTIN_MACROS_O) 97 | 98 | test: dirs asm $(TEST_OBJ) 99 | $(LD) -o $(TEST) $(TESTLD) $(LDFLAGS) $(filter %.o, $^) 100 | 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDH-8 2 | A fully custom 8-bit minicomputer with a unique architecture. 3 | 4 | ![BUILD SCREENSHOT](images/BUILD.png) 5 | ![PONG SCREENSHOT](images/PONG.png) 6 | 7 | The program above is running in the emulator, see [`programs/pong.asm`](programs/pong.asm) for the source. 8 | 9 | [See the video on the design](https://youtu.be/7A1SzIIKMho). \ 10 | [and the video on the build](https://youtu.be/vaGZapAGvwM). 11 | 12 | ## Machine Description 13 | 14 | ### Features 15 | - 8-bit data width 16 | - 16-bit address bus (64 KiB available memory + banking) 17 | - 8 fully general purpose registers (5 normal + 2 indirect address + 1 flags) 18 | - 16 instruction RISC architecture 19 | - Port mapped I/O for device communication 20 | 21 | ### Instruction Set: 22 | ``` 23 | 0: MW reg, imm8/reg -> reg = imm8/reg 24 | 1: LW reg, [HL/imm16] -> reg = [HL/imm16] 25 | 2: SW [HL/imm16], reg -> [HL/imm16] = reg 26 | 3: PUSH imm8/reg -> [SP--] = imm8/reg 27 | 4: POP reg -> reg = [++SP] 28 | 5: LDA [imm16] -> HL = imm16 29 | 6: JNZ imm8/reg -> imm8/reg != 0 ? PC = HL : NOP 30 | 7: INB reg, imm8/reg -> reg = PORT[imm8/reg] 31 | 8: OUTB imm8/reg, reg -> PORT[imm8/reg] = reg 32 | 9: ADD* reg, imm8/reg -> reg = reg + imm8/reg 33 | A: ADC* reg, imm8/reg -> reg = reg + imm8/reg + c 34 | B: AND reg, imm8/reg -> reg = reg & imm8/reg 35 | C: OR reg, imm8/reg -> reg = reg | imm8/reg 36 | D: NOR reg, imm8/reg -> reg = ~(reg | imm8/reg) 37 | E: CMP* reg, imm8/reg -> f = compare reg, imm8/reg (see below) 38 | F: SBB* reg, imm8/reg -> reg = reg - imm8/reg - b 39 | 40 | *these instructions load the carry/borrow bits in the (F)lags register 41 | ``` 42 | 43 | ### Registers 44 | ``` 45 | A (0): GP register/arg 0 46 | B (1): GP register/arg 1 47 | C (2): GP register/arg 2 48 | D (3): GP register/arg 3 49 | L (4): GP register/(L)ow index register 50 | H (5): GP register/(H)igh index register 51 | Z (6): GP register/return value 52 | F (7): flags (LSB to MSB) 53 | LESS 54 | EQUAL 55 | CARRY 56 | BORROW 57 | ``` 58 | 59 | See [the spec](SPEC.txt) for more information. 60 | 61 | ## Schematic 62 | The schematic requires a modified version of Logisim Evolution to view, [see here](https://github.com/jdah/logisim-evolution). You will need to build it from source. 63 | 64 | ## Toolchain 65 | 66 | ### Compiling on POSIX systems 67 | Compile with `$ make` 68 | 69 | ### Compiling on Windows 70 | 1. Install MSYS2 (msys2.org) 71 | 2. Install MinGW-W64 from MSYS2 72 | 3. Get 64-bit SDL2 development tools (libsdl.org) 73 | 4. LIBPATH to your SDL location (`Makefile` automatically specifies C:/cdevlibs) 74 | 5. Change CC and LD to the location of GCC (including the executable name) in your MinGW-W64 install or add `gcc` and `ld` to PATH 75 | 6. Install xxd for Windows (https://sourceforge.net/projects/xxd-for-windows/) 76 | 7. Change variable XXD in `Makefile` to the location of XXD (including the executable name) or add `xxd` to PATH 77 | 8. Run mingw32-make inside project directory 78 | 79 | ### **Assembler** ([`asm`](./asm)) 80 | `./bin/asm [-h] [--help] [-v] [--verbose] [-n] [--no-builtin-macros] [-o file] file` 81 | 82 | ### **Emulator** ([`emu`](./emu)) 83 | `./bin/emu [-m/--mod module] [-r/--run] [-l/--load file address] [ROM file]` 84 | #### Commands 85 | - `SET `: set register data 86 | - `GET `: get register value 87 | - `PEEK
`: get memory value 88 | - `POKE
`: set memory data 89 | - `INB `: get port data 90 | - `OUTB `: write port data 91 | - `STEP`: step one instruction 92 | - `DUMP`: print current machine state 93 | - `LOAD
`: load ROM at address 94 | - `MODS`: list modules 95 | - `MOD `: load module 96 | - `DEVICES`: list devices 97 | - `RUN `: run at speed (hz) until halt 98 | - `QUIT`: quit 99 | -------------------------------------------------------------------------------- /SCHEMATIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/SCHEMATIC.png -------------------------------------------------------------------------------- /SPEC.txt: -------------------------------------------------------------------------------- 1 | ============================== 2 | = JDH-8 SPEC = 3 | ============================== 4 | 5 | === OVERVIEW === 6 | * Minicomputer designed to be implemented with TTL 7 | * 8-bit data width 8 | * 16-bit address bus/64 KiB accessible memory, more with banking 9 | * Memory banking via MB register allowing for 256 possible memory banks 10 | (total 24-bit address space, 16 MiB possible addressable memory) 11 | * Designed for 4 MHz clock speed, ~100-150k instructions/second 12 | * Device communication via dedicated I/O instructions 13 | 14 | === INSTRUCTIONS === 15 | 0: MW reg, imm8/reg -> reg = imm8/reg 16 | 1: LW reg, [imm16/HL] -> reg = [HL/imm16] 17 | 2: SW [imm16/HL], reg -> [HL/imm16] = reg 18 | 3: PUSH imm8/reg -> [SP--] = imm8/reg 19 | 4: POP reg -> reg = [++SP] 20 | 5: LDA [imm16] -> HL = imm16 21 | 6: JNZ imm8/reg -> PC = HL if imm8/reg != 0 else NOP 22 | 7: INB reg, imm8/reg -> reg = PORT[imm8/reg] 23 | 8: OUTB imm8/reg, reg -> PORT[imm8/reg] = reg 24 | 9: ADD^ reg, imm8/reg -> reg = reg + imm8/reg 25 | A: ADC^ reg, imm8/reg -> reg = reg + imm8/reg + c 26 | B: AND reg, imm8/reg -> reg = reg & imm8/reg 27 | C: OR reg, imm8/reg -> reg = reg | imm8/reg 28 | D: NOR reg, imm8/reg -> reg = ~(reg | imm8/reg) 29 | E: CMP^ reg, imm8/reg -> f = compare reg, imm8/reg (see below) 30 | F: SBB^ reg, imm8/reg -> reg = reg - imm8/reg - b 31 | 32 | * imm8/16 are the byte(s) immediately following the instruction byte in memory 33 | * with imm8/reg and imm16/HL, the choice is indicated by the y-bit 34 | (see INSTRUCTION LAYOUT) 35 | 36 | ^ these instructions load the (F)lags register 37 | 38 | === REGISTERS === 39 | A (0): GP register 40 | B (1): GP register 41 | C (2): GP register 42 | D (3): GP register 43 | L (4): GP register/(L)ow index register 44 | H (5): GP register/(H)igh index register 45 | Z (6): GP register 46 | F (7): flags (LSB to MSB) 47 | LESS 48 | EQUAL 49 | CARRY 50 | BORROW 51 | 52 | * Calling convention 53 | * A, B, C, D, Z for arguments 54 | * Use 16-bit pairs AB and CD if arguments are 16-bits wide 55 | * Skip register if necessary to pair registers together into 16 bits 56 | * Remaining arguments pushed to stack in order 57 | * Return value in Z 58 | 59 | === INSTRUCTION LAYOUT === 60 | Instruction layout is XXXXYZZZ where 61 | X: 4-bit instruction identifier (see INSTRUCTIONS) 62 | Y: 0 if argument is imm(8/16), 1 if argument is reg 63 | Z: 3-bit register identifier of first register argument (see REGISTERS) 64 | 65 | * instructions with reg, reg arguments have the second register encoded in the 66 | first three bits of the second instruction byte 67 | * For LW/SW, the Y-bit indicates imm16 (0) and HL (1). Z-bits are ALWAYS reg. 68 | * Instructions can be 1-3 bytes long 69 | * PUSH/POP with one register argument are one byte instructions 70 | * LW/SW with Y=0 (imm16) are 3 bytes each 71 | * LDA is always 3 bytes 72 | 73 | === MEMORY LAYOUT === 74 | 0x0000..0x7FFF: GENERAL PURPOSE ROM 75 | 0x8000..0xBFFF: GENERAL PURPOSE RAM (BANKED) 76 | 0xC000..0xFDFF: GENERAL PURPOSE RAM 77 | 0xFC00..0xFEFF: STACK (RECOMMENDED), else GP RAM 78 | 0xFF00..0xFFF9: GENERAL PURPOSE RAM 79 | 0xFFFA..0xFFFA: MB/(M)emory (B)ank 80 | 0xFFFB..0xFFFB: UNUSED 81 | 0xFFFC..0xFFFD: SP/(S)tack (P)ointer 82 | 0xFFFE..0xFFFF: PC/(P)rogram (C)ounter 83 | 84 | === MEMORY BANKING === 85 | 0x8000..0xBFFF can be swapped using the MB register, where MB=0 indicates that 86 | the built-in RAM is in use. MB=1 maps to the built-in VRAM. 87 | 88 | === PORTS === 89 | PORTS are used to communicate with devices or by the JDH-8 itself. See 90 | SPECIAL PORTS for a list of which ports are controlled by the JDH-8 and not 91 | external devices. 92 | 93 | === SPECIAL PORTS === 94 | 0x00: STATUS 95 | 96 | === STATUS REGISTER === 97 | The status register can be read/written with PORT 0 using the INB and OUTB 98 | instructions. It contains control information about the current JDH-8 state. 99 | Its bits are as follows, from LSB to MSB: 100 | UNUSED (RW) 101 | ERROR (RO) 102 | POWER (RW) 103 | HALT (RW) 104 | 105 | === VIDEO === 106 | The JDH-8 circuit/emulator include support for a graphics card which outputs 107 | an analog signal at 208x240 not-exactly-square black or white pixels using 108 | the same 4 MHz clock as the rest of the computer. This is done by using memory 109 | bank 1 as shared VRAM so the CPU has fast read/write access. The memory layout 110 | for the image is as follows: 111 | * VRAM starts at 0x8000 (start of banked memory, 0x0000 in the physical chip) 112 | * Each scanline (208 horizontal pixels) of the image spans 32 contiguous bytes 113 | in VRAM 114 | * Scanlines are layed out as follows: 115 | * [0..3] (4 bytes) padding 116 | * [4..29] (26 bytes) data, one bit per pixel 117 | * [30..31] (2 bytes) padding 118 | Note that depending on the screen, not all parts of the scanlines will be 119 | visible, so the "safe" drawing area is pixels 8..199 out of 0..207. All vertical 120 | lines will be visible due to large vertical padding built into the video card. 121 | -------------------------------------------------------------------------------- /asm/asm.h: -------------------------------------------------------------------------------- 1 | #ifndef ASM_H 2 | #define ASM_H 3 | 4 | #include "../common/util.h" 5 | #include "../common/jdh8.h" 6 | 7 | #include "lex.h" 8 | 9 | // checks buffer _b, asmrealloc-ing it/doubling its size if necessary to add a 10 | // new element 11 | #define BUFCHK(_b) \ 12 | if ((_b).buffer == NULL || (_b).size + 1 > (_b).capacity) { \ 13 | (_b).capacity = (_b).capacity == 0 ? \ 14 | (_b).capacity_min : \ 15 | (_b).capacity * 2; \ 16 | (_b).buffer = asmrealloc( \ 17 | (_b).buffer, \ 18 | (_b).capacity * sizeof((_b).buffer[0]) \ 19 | ); \ 20 | } 21 | 22 | // appends new *element _p to buffer _b, checking if it can be added first and 23 | // creating the buffer if it does not already exist 24 | #define BUFADDP(_b, _p) do { \ 25 | BUFCHK((_b)); \ 26 | (_b).buffer[(_b).size++] = *(_p); \ 27 | } while (0); 28 | 29 | // BUFPADD, but not a pointer 30 | #define BUFADD(_b, _e) do { \ 31 | __typeof__(_e) __e = (_e), *_p = &__e; \ 32 | BUFADDP((_b), (_p)); \ 33 | } while (0); 34 | 35 | #define BUFPUSH(_b, _e) BUFADD((_b), (_e)) 36 | 37 | #define BUFPUSHP(_b, _p) BUFADDP((_b), (_p)) 38 | 39 | #define BUFPOP(_b) ({ \ 40 | if ((_b).size == 0) warn("Empty buffer %p", &(_b)); \ 41 | (_b).buffer[--(_b).size]; \ 42 | }) 43 | 44 | #define BUFPEEKP(_b) ({ \ 45 | if ((_b).size == 0) warn("Empty buffer %p", &(_b)); \ 46 | &(_b).buffer[(_b).size - 1]; \ 47 | }) 48 | 49 | #define BUFPEEK(_b) (*BUFPEEKP((_b))) 50 | 51 | // value for Context::org indicating not set in program text 52 | #define ORG_UNSET ((u16) 0xFFFF) 53 | 54 | // undefined in the context of expression evaluation (see eval.c) 55 | #define EVAL_EXP_UNDEFINED LONG_MAX 56 | 57 | struct Input { 58 | char name[256]; 59 | const char *data, *current, *line; 60 | usize line_no; 61 | 62 | struct Input *parent; 63 | }; 64 | 65 | // token flags 66 | enum TokenFlags { 67 | // indicates a symbol (label) is a sublabel 68 | TF_SUB = 0, 69 | 70 | // indicates a token should be ignored 71 | TF_IGNORE = 1 72 | }; 73 | 74 | #define A_IMM8_REG (A_IMM8 | A_REG) 75 | #define A_IMM16_HL (A_IMM16 | A_HL | A_ADDR) 76 | #define A_IMM (A_IMM16 | A_IMM8) 77 | enum Arg { 78 | A_REG = (1 << 0), 79 | A_IMM8 = (1 << 1), 80 | A_IMM16 = (1 << 2), 81 | A_ADDR = (1 << 3), 82 | A_HL = (1 << 4) 83 | }; 84 | 85 | struct Op { 86 | char name[16]; 87 | 88 | usize num_args; 89 | enum Arg args[8]; 90 | 91 | bool macro; 92 | 93 | union { 94 | // instruction 95 | struct { 96 | enum Instruction instruction; 97 | }; 98 | 99 | // macro 100 | struct { 101 | char arg_names[8][16]; 102 | struct Token *start, *end; 103 | }; 104 | }; 105 | }; 106 | 107 | struct Define { 108 | char *name, *value; 109 | struct Define *next; 110 | }; 111 | 112 | struct Label { 113 | char *name; 114 | bool sub; 115 | u16 value; 116 | struct Label *prev, *next; 117 | }; 118 | 119 | struct Patch { 120 | struct Token *symbol; 121 | struct Label *label; 122 | u16 location; 123 | struct Patch *next; 124 | }; 125 | 126 | // defined in microcode.c 127 | struct MicrocodeContext; 128 | 129 | struct Context { 130 | // current input 131 | struct Input *input; 132 | 133 | // always points into input->data, backed up in input->current on push 134 | const char *current; 135 | 136 | // @define'd values 137 | struct Define *defines; 138 | 139 | // labels 140 | struct Label *labels; 141 | 142 | // patches 143 | struct Patch *patches; 144 | 145 | struct { 146 | struct Op *buffer; 147 | usize size, capacity, capacity_min; 148 | } ops; 149 | 150 | // output 151 | struct { 152 | u8 *buffer; 153 | usize size, capacity, capacity_min; 154 | } out; 155 | 156 | // preprocessor ifs 157 | struct { 158 | #define IFF_SUCCESS 1 159 | #define IFF_FAILURE 2 160 | #define IFF_PARENT_FAILURE 3 161 | u8 *buffer; 162 | usize size, capacity, capacity_min; 163 | } ifs; 164 | 165 | // origin of output 166 | u16 org; 167 | 168 | struct MicrocodeContext *mctx; 169 | 170 | // flags 171 | struct { 172 | bool macro: 1; 173 | bool microcode: 1; 174 | bool verbose: 1; 175 | }; 176 | }; 177 | 178 | #define asmwrn(ctx, token, m, ...)\ 179 | asmprint( \ 180 | (ctx), (token), ANSI_YELLOW "WARN", \ 181 | __FILE__, __LINE__, (m), ##__VA_ARGS__ \ 182 | ); 183 | 184 | #define asmchk(e, ctx, token, m, ...) if (!(e)) { \ 185 | asmprint( \ 186 | (ctx), (token), ANSI_RED "ERROR", \ 187 | __FILE__, __LINE__, (m), ##__VA_ARGS__ \ 188 | ); \ 189 | exit(1); \ 190 | } 191 | 192 | #define asmdbg(ctx, token, m, ...)\ 193 | asmprint((ctx), (token), "DEBUG", __FILE__, __LINE__, (m), ##__VA_ARGS__) 194 | 195 | void asmprint( 196 | const struct Context *ctx, 197 | const struct Token *token, 198 | const char *tag, 199 | const char *filename, 200 | usize line, 201 | const char *fmt, 202 | ... 203 | ); 204 | 205 | void *asmalloc(usize n); 206 | void *asmrealloc(void *ptr, usize n); 207 | 208 | int push_input_buffer(struct Context *ctx, const char *name, const char *buf); 209 | int push_input(struct Context *ctx, const char *filename); 210 | void pop_input(struct Context *ctx); 211 | 212 | void eval_labels( 213 | struct Context *ctx, 214 | struct Token *start, 215 | struct Token *end 216 | ); 217 | 218 | 219 | // directive.c 220 | void directive( 221 | struct Context *ctx, 222 | struct Token *first, 223 | bool lex 224 | ); 225 | 226 | // eval.c 227 | i64 eval_exp(struct Context *ctx, struct Token *start, struct Token *end); 228 | struct Token *eval_between( 229 | struct Context *ctx, 230 | struct Token *start, 231 | struct Token *end 232 | ); 233 | 234 | // macro.c 235 | void parse_macro( 236 | struct Context *ctx, 237 | struct Token *start, 238 | struct Token *end 239 | ); 240 | 241 | struct Token *expand_macro( 242 | struct Context *ctx, 243 | struct Op *op, 244 | struct Token *start, 245 | struct Token *end, 246 | enum Arg *args, 247 | struct Token **arg_tokens, 248 | usize num_args 249 | ); 250 | 251 | // microcode.c 252 | struct Token *microcode_parse(struct Context *, struct Token *); 253 | void microcode_emit(struct Context *); 254 | 255 | #endif 256 | -------------------------------------------------------------------------------- /asm/directive.c: -------------------------------------------------------------------------------- 1 | #include "asm.h" 2 | 3 | struct Directive { 4 | const char *name; 5 | bool lex; 6 | void (*handler)(struct Context *, struct Token *); 7 | }; 8 | 9 | static void dir_ifdef(struct Context *ctx, struct Token *first) { 10 | char *def_kind = token_data(first->next, NULL, 0); 11 | assert(def_kind); 12 | 13 | bool negate = !strcasecmp(def_kind, "ifndef"); 14 | 15 | if (ctx->ifs.size != 0 && (BUFPEEK(ctx->ifs) != IFF_SUCCESS)) { 16 | BUFPUSH(ctx->ifs, IFF_PARENT_FAILURE); 17 | asmdbg(ctx, first, "@if(n)def fail due to parent"); 18 | } else { 19 | struct Token *def = first->next->next; 20 | asmchk(def, ctx, def, "Malformed @%s", def_kind); 21 | asmchk(def->next->kind == TK_EOL, ctx, def, "Malformed @%s", def_kind); 22 | 23 | char *def_name = token_data(def, NULL, 0); 24 | assert(def_name); 25 | 26 | // check if defined 27 | struct Define *d = ctx->defines; 28 | while (d != NULL) { 29 | if (!strcasecmp(def_name, d->name)) { 30 | break; 31 | } 32 | 33 | d = d->next; 34 | } 35 | 36 | bool defined = d != NULL; 37 | asmdbg( 38 | ctx, first->next->next, 39 | "%s is %sdefined", def_name, defined ? "" : "not "); 40 | 41 | BUFPUSH(ctx->ifs, negate != defined ? IFF_SUCCESS : IFF_FAILURE); 42 | asmdbg( 43 | ctx, first, 44 | "@if(n)def %s", negate != defined ? "success" : "failure"); 45 | } 46 | } 47 | 48 | static void dir_else(struct Context *ctx, struct Token *first) { 49 | asmchk( 50 | first->next->next->kind == TK_EOL, ctx, first->next, 51 | "Malformed @else"); 52 | asmchk( 53 | ctx->ifs.size != 0, ctx, first->next, 54 | "@else must follow @if(n)def"); 55 | 56 | u8 *state = BUFPEEKP(ctx->ifs); 57 | 58 | if (*state != IFF_PARENT_FAILURE) { 59 | *state = *state == IFF_SUCCESS ? IFF_FAILURE : IFF_SUCCESS; 60 | asmdbg(ctx, first, "@else, flipping"); 61 | } else { 62 | asmdbg(ctx, first, "@else ignored due to parent failure"); 63 | } 64 | } 65 | 66 | static void dir_endif(struct Context *ctx, struct Token *first) { 67 | asmchk( 68 | first->next->next->kind == TK_EOL, ctx, first->next, 69 | "Malformed @endif"); 70 | asmchk( 71 | ctx->ifs.size != 0, ctx, first->next, 72 | "@endif must follow @if(n)def"); 73 | BUFPOP(ctx->ifs); 74 | asmdbg(ctx, first, "Popped @if(n)def"); 75 | } 76 | 77 | static void dir_microcode(struct Context *ctx, struct Token *first) { 78 | asmchk(first->next->next->kind == TK_EOL, ctx, first, "Invalid directive"); 79 | asmdbg(ctx, NULL, "Microcode mode enabled"); 80 | ctx->microcode = true; 81 | } 82 | 83 | static void dir_include(struct Context *ctx, struct Token *first) { 84 | struct Token *path = first->next->next; 85 | 86 | char filename[256]; 87 | asmchk(path->kind == TK_STRING, ctx, path, "Invalid path"); 88 | assert(token_data(path, filename, sizeof(filename))); 89 | strstrip(filename); 90 | 91 | asmchk( 92 | !push_input(ctx, filename), 93 | ctx, 94 | path, 95 | "Cannot open file %s", 96 | filename 97 | ); 98 | } 99 | 100 | static void dir_define(struct Context *ctx, struct Token *first) { 101 | struct Token *name = first->next->next; 102 | asmchk(name, ctx, name, "Malformed @define"); 103 | 104 | char *def_name = token_data(name, NULL, 0), 105 | *def_value = NULL; 106 | 107 | assert(def_name); 108 | 109 | if (name->next->kind != TK_EOL) { 110 | struct Token *eol = name; 111 | while (eol->kind != TK_EOL) { 112 | eol = eol->next; 113 | } 114 | 115 | def_value = token_between(name->next, eol->prev, NULL, 0); 116 | assert(def_value); 117 | strstrip(def_value); 118 | } 119 | 120 | asmchk( 121 | !def_value || strcasecmp(def_name, def_value), 122 | ctx, name, 123 | "%s defined as itself, this will not end well", 124 | def_name 125 | ); 126 | 127 | // check for redefinition 128 | struct Define *d = ctx->defines; 129 | while (d != NULL) { 130 | if (!strcasecmp(def_name, d->name)) { 131 | asmwrn(ctx, name, "Redefinition of %s", def_name); 132 | break; 133 | } 134 | 135 | d = d->next; 136 | } 137 | 138 | struct Define *old = ctx->defines; 139 | ctx->defines = asmalloc(sizeof(struct Define)); 140 | *ctx->defines = (struct Define) { 141 | .name = def_name, 142 | .value = def_value, 143 | .next = old 144 | }; 145 | 146 | asmdbg( 147 | ctx, NULL, 148 | "Added define %s=%s", 149 | def_name, def_value 150 | ); 151 | } 152 | 153 | static void dir_macro(struct Context *ctx, struct Token *first) { 154 | ctx->macro = true; 155 | } 156 | 157 | static void eval_expressions(struct Context *ctx, struct Token *first) { 158 | struct Token *eol = first; 159 | while (eol->kind != TK_EOL) { 160 | eol = eol->next; 161 | } 162 | 163 | eval_labels(ctx, first->next->next, eol->prev); 164 | 165 | struct Token *tk; 166 | asmchk( 167 | !(tk = eval_between(ctx, first->next->next, eol->prev)), 168 | ctx, tk, 169 | "Error parsing expression" 170 | ); 171 | } 172 | 173 | static void dir_ds(struct Context *ctx, struct Token *first) { 174 | struct Token *str = first->next->next; 175 | asmchk(str->kind == TK_STRING, ctx, str, "Expected string"); 176 | 177 | const char *data = token_data(str, NULL, 0); 178 | 179 | // TODO: do escaped character processing elsewhere 180 | for (usize i = 0; i < str->len; i++) { 181 | BUFADD(ctx->out, data[i] == '\\' ? escchar(data[++i]) : data[i]); 182 | } 183 | 184 | BUFADD(ctx->out, 0); 185 | } 186 | 187 | static void dir_db(struct Context *ctx, struct Token *first) { 188 | struct Token *t = first->next->next, *eol = t; 189 | 190 | while (eol->kind != TK_EOL) { 191 | eol = eol->next; 192 | } 193 | 194 | eval_expressions(ctx, first); 195 | eval_labels(ctx, t, eol->prev); 196 | 197 | while (t->kind != TK_EOL) { 198 | asmchk(t->kind == TK_NUMBER, ctx, t, "Expected byte"); 199 | asmchk( 200 | t->value >= INT8_MIN && t->value <= UINT8_MAX, 201 | ctx, t, 202 | "Value out of bounds" 203 | ); 204 | 205 | BUFADD(ctx->out, (u8) (t->value)); 206 | t = t->next; 207 | } 208 | } 209 | 210 | static void dir_dd(struct Context *ctx, struct Token *first) { 211 | struct Token *t = first->next->next, *eol = t; 212 | 213 | while (eol->kind != TK_EOL) { 214 | eol = eol->next; 215 | } 216 | 217 | eval_expressions(ctx, first); 218 | eval_labels(ctx, t, eol->prev); 219 | 220 | while (t->kind != TK_EOL) { 221 | if (t->kind == TK_LBRACKET) { 222 | asmchk( 223 | t->next->kind == TK_NUMBER && 224 | t->next->next->kind == TK_RBRACKET, 225 | ctx, t, 226 | "Malformed address" 227 | ); 228 | t = t->next; 229 | t->next = t->next->next; 230 | t->next->prev = t; 231 | } 232 | 233 | asmchk(t->kind == TK_NUMBER, ctx, t, "Expected double word"); 234 | asmchk( 235 | t->value >= INT16_MIN && t->value <= UINT16_MAX, 236 | ctx, t, 237 | "Value out of bounds" 238 | ); 239 | 240 | u16 value = t->value; 241 | BUFADD(ctx->out, (value >> 0) & 0xFF); 242 | BUFADD(ctx->out, (value >> 8) & 0xFF); 243 | t = t->next; 244 | } 245 | } 246 | 247 | static void dir_dn(struct Context *ctx, struct Token *first) { 248 | struct Token *n = first->next->next; 249 | asmchk(n->kind == TK_NUMBER, ctx, n, "Expected number") 250 | asmchk(n->next->kind == TK_EOL, ctx, n->next, "Expected line end"); 251 | 252 | u16 v = n->value; 253 | while (v > 0) { 254 | BUFADD(ctx->out, 0); 255 | v--; 256 | } 257 | } 258 | 259 | static void dir_org(struct Context *ctx, struct Token *first) { 260 | asmchk(ctx->org == ORG_UNSET, ctx, first, "Multiple values for origin"); 261 | 262 | struct Token *value = first->next->next; 263 | asmchk( 264 | value->kind == TK_NUMBER && value->next->kind == TK_EOL, 265 | ctx, value, 266 | "Illegal arguments to org directive" 267 | ); 268 | 269 | i64 org = value->value; 270 | asmchk( 271 | org >= 0 && org <= 0xFFFF, ctx, value, 272 | "Origin must be in range 0x0000..0xFFFF" 273 | ); 274 | ctx->org = org; 275 | asmdbg(ctx, NULL, "Org set to %" PRIu16, ctx->org); 276 | } 277 | 278 | static const struct Directive DIRECTIVES[] = { 279 | { "microcode", true, dir_microcode }, 280 | { "include", true, dir_include }, 281 | { "define", true, dir_define }, 282 | { "macro", false, dir_macro }, 283 | { "ds", false, dir_ds }, 284 | { "db", false, dir_db }, 285 | { "dd", false, dir_dd }, 286 | { "dn", false, dir_dn }, 287 | { "org", false, dir_org }, 288 | { "ifdef", true, dir_ifdef }, 289 | { "ifndef", true, dir_ifdef }, 290 | { "else", true, dir_else }, 291 | { "endif", true, dir_endif } 292 | }; 293 | 294 | void directive( 295 | struct Context *ctx, 296 | struct Token *first, 297 | bool lex 298 | ) { 299 | if (first->flags & TF_IGNORE) { 300 | return; 301 | } 302 | 303 | assert(first->kind == TK_AT); 304 | assert(first->next->kind == TK_SYMBOL); 305 | 306 | char symbol[256]; 307 | assert(token_data(first->next, symbol, sizeof(symbol))); 308 | 309 | for (usize i = 0; i < ARRLEN(DIRECTIVES); i++) { 310 | const struct Directive *d = &DIRECTIVES[i]; 311 | 312 | if (!strcasecmp(d->name, symbol)) { 313 | if (lex == d->lex) { 314 | asmdbg(ctx, first, "Processing directive %s", symbol); 315 | d->handler(ctx, first); 316 | } 317 | 318 | // skip processing and don't error if name matches but in wrong step 319 | return; 320 | } 321 | } 322 | 323 | // only fail on unrecognized non-lex directives 324 | asmchk(lex, ctx, first->next, "Unrecognized directive"); 325 | } 326 | -------------------------------------------------------------------------------- /asm/eval.c: -------------------------------------------------------------------------------- 1 | #include "asm.h" 2 | 3 | static i64 eval_exp_op( 4 | struct Context *ctx, struct Token *op, i64 lhs, i64 rhs 5 | ) { 6 | switch (op->kind) { 7 | case TK_MINUS: 8 | if (lhs == EVAL_EXP_UNDEFINED) { 9 | return -rhs; 10 | } 11 | return lhs - rhs; 12 | case TK_NOT: 13 | assert(lhs == EVAL_EXP_UNDEFINED); 14 | return ~rhs; 15 | case TK_PLUS: return lhs + rhs; 16 | case TK_AMPERSAND: return lhs & rhs; 17 | case TK_PIPE: return lhs | rhs; 18 | case TK_STAR: return lhs * rhs; 19 | case TK_CARET: return lhs ^ rhs; 20 | case TK_SLASH: return lhs / rhs; 21 | case TK_LESS: return lhs << rhs; 22 | case TK_GREATER: return lhs >> rhs; 23 | default: 24 | asmchk(false, ctx, op, "Invalid operation"); 25 | } 26 | 27 | assert(false); 28 | return EVAL_EXP_UNDEFINED; 29 | } 30 | 31 | i64 eval_exp(struct Context *ctx, struct Token *start, struct Token *end) { 32 | assert(start->kind == TK_LPAREN); 33 | assert(end->kind == TK_RPAREN); 34 | asmchk((usize) (start - end) > 1, ctx, start, "Empty expression"); 35 | i64 acc = EVAL_EXP_UNDEFINED, tmp = EVAL_EXP_UNDEFINED; 36 | 37 | struct Token *op = NULL, *t = start->next, *u = NULL; 38 | while (t != end) { 39 | switch (t->kind) { 40 | case TK_NUMBER: 41 | tmp = t->value; 42 | break; 43 | case TK_LPAREN: 44 | u = t; 45 | 46 | usize depth = 0; 47 | while (u != end) { 48 | if (u->kind == TK_LPAREN) { 49 | depth++; 50 | } else if (u->kind == TK_RPAREN) { 51 | depth--; 52 | } 53 | 54 | if (depth == 0) { 55 | break; 56 | } 57 | 58 | u = u->next; 59 | } 60 | 61 | asmchk(depth == 0, ctx, u, "Unbalanced expression"); 62 | 63 | tmp = eval_exp(ctx, t, u); 64 | t = u; 65 | break; 66 | default: 67 | asmchk(!op, ctx, t, "Double operation"); 68 | op = t; 69 | t = t->next; 70 | 71 | // do not attempt to eval, wait for rhs 72 | continue; 73 | } 74 | 75 | if (op) { 76 | acc = eval_exp_op(ctx, op, acc, tmp); 77 | op = NULL; 78 | } else { 79 | acc = tmp; 80 | } 81 | 82 | tmp = EVAL_EXP_UNDEFINED; 83 | t = t->next; 84 | } 85 | 86 | assert(acc != EVAL_EXP_UNDEFINED); 87 | asmchk( 88 | (acc < 0 && acc >= INT16_MIN) || 89 | (acc >= 0 && acc <= 0xFFFF), 90 | ctx, start, "Expression out of bounds (value is %" PRId64 ")", 91 | acc 92 | ); 93 | asmdbg( 94 | ctx, NULL, 95 | "Evaluated %s to %" PRIu16, 96 | token_between(start, end, NULL, 0), acc 97 | ); 98 | return acc; 99 | } 100 | 101 | // evaluates expressions between the two tokens (inclusive), 102 | // leaving other tokens alone 103 | // returns erroneous token on error, otherwise returns NULL 104 | struct Token *eval_between( 105 | struct Context *ctx, 106 | struct Token *start, 107 | struct Token *end 108 | ) { 109 | struct Token *tk = start; 110 | 111 | while (tk != end->next) { 112 | if (tk->kind != TK_LPAREN) { 113 | tk = tk->next; 114 | continue; 115 | } 116 | 117 | struct Token *end = tk; 118 | usize depth = 0; 119 | while (end->kind != TK_EOL) { 120 | if (end->kind == TK_LPAREN) { 121 | depth++; 122 | } else if (end->kind == TK_RPAREN) { 123 | depth--; 124 | } 125 | 126 | if (depth == 0) { 127 | break; 128 | } 129 | 130 | end = end->next; 131 | } 132 | 133 | if (depth != 0) { 134 | return tk; 135 | } 136 | 137 | i64 value = eval_exp(ctx, tk, end); 138 | tk->kind = TK_NUMBER; 139 | tk->value = value; 140 | 141 | // skip tokens in between 142 | tk->next = end->next; 143 | 144 | tk = end->next; 145 | } 146 | 147 | return NULL; 148 | } 149 | -------------------------------------------------------------------------------- /asm/lex.h: -------------------------------------------------------------------------------- 1 | #ifndef LEX_H 2 | #define LEX_H 3 | 4 | #include "../common/util.h" 5 | 6 | // forward declarations from asm.h 7 | struct Input; 8 | struct Context; 9 | 10 | #define TK_COUNT (TK_GREATER + 1) 11 | enum TokenKind { 12 | TK_NOP = 0, 13 | TK_EOL, 14 | TK_SYMBOL, 15 | TK_NUMBER, 16 | TK_STRING, 17 | TK_LPAREN, 18 | TK_RPAREN, 19 | TK_AT, 20 | TK_LBRACKET, 21 | TK_RBRACKET, 22 | TK_COLON, 23 | TK_PERCENT, 24 | TK_PLUS, 25 | TK_MINUS, 26 | TK_AMPERSAND, 27 | TK_PIPE, 28 | TK_NOT, 29 | TK_DOLLAR, 30 | TK_STAR, 31 | TK_CARET, 32 | TK_SLASH, 33 | TK_DOT, 34 | TK_LESS, 35 | TK_GREATER 36 | }; 37 | 38 | struct Token { 39 | enum TokenKind kind; 40 | 41 | // neighboring tokens in stream 42 | struct Token *prev, *next; 43 | 44 | // origin info 45 | const struct Input *input; 46 | const char *line; 47 | usize line_no, line_index; 48 | 49 | // for TK_SYMBOL, TK_STRING, TK_NUMBER 50 | const char *data; 51 | usize len; 52 | 53 | // for TK_NUMBER 54 | i64 value; 55 | 56 | // for arbitrary information on all tokens 57 | u64 flags; 58 | }; 59 | 60 | char *token_data(const struct Token *token, char *buf, usize n); 61 | char *token_line(const struct Token *token, char *buf, usize n); 62 | char *token_between( 63 | const struct Token *start, 64 | const struct Token *end, 65 | char *buf, 66 | usize n 67 | ); 68 | struct Token *lex( 69 | struct Context *ctx, 70 | void (*linecb)(struct Context*, struct Token*) 71 | ); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /asm/macro.c: -------------------------------------------------------------------------------- 1 | #include "asm.h" 2 | 3 | static enum Arg parse_arg_type(struct Context *ctx, struct Token *symbol) { 4 | char name[64]; 5 | assert(token_data(symbol, name, sizeof(name))); 6 | 7 | switch (name[0]) { 8 | case 'r': return A_REG; 9 | case 'x': return A_IMM8_REG; 10 | case 'a': return A_IMM16 | A_ADDR; 11 | case 'i': return A_IMM; 12 | default: 13 | asmchk( 14 | false, ctx, symbol, 15 | "Illegal argument name (must be r, x, a, or i)" 16 | ); 17 | return A_REG; 18 | } 19 | } 20 | 21 | void parse_macro( 22 | struct Context *ctx, 23 | struct Token *start, 24 | struct Token *end 25 | ) { 26 | asmchk(start->kind == TK_SYMBOL, ctx, start, "Expected symbol"); 27 | 28 | struct Op op; 29 | memset(&op, 0, sizeof(op)); 30 | op.macro = true; 31 | assert(token_data(start, op.name, sizeof(op.name))); 32 | 33 | isize num_addr_args = -1; 34 | 35 | // parse arguments 36 | struct Token *tk = start->next; 37 | while (tk->kind != TK_COLON) { 38 | switch (tk->kind) { 39 | case TK_PERCENT: 40 | tk = tk->next; 41 | asmchk(tk->kind == TK_SYMBOL, ctx, tk, "Expected symbol"); 42 | assert(token_data( 43 | tk, 44 | op.arg_names[op.num_args], 45 | sizeof(op.arg_names[0]) 46 | )); 47 | // ensure argument name is not duplicate 48 | for (usize i = 0; i < op.num_args; i++) { 49 | if (!strcasecmp( 50 | op.arg_names[i], 51 | op.arg_names[op.num_args])) { 52 | asmchk(false, ctx, tk, "Duplicate argument"); 53 | } 54 | } 55 | 56 | op.args[op.num_args++] = parse_arg_type(ctx, tk); 57 | 58 | if (num_addr_args >= 0) { 59 | op.args[op.num_args - 1] |= A_ADDR; 60 | num_addr_args++; 61 | } 62 | 63 | break; 64 | case TK_LBRACKET: 65 | num_addr_args = 0; 66 | break; 67 | case TK_RBRACKET: 68 | asmchk( 69 | num_addr_args > 0, ctx, tk, 70 | "Must have at least one address argument" 71 | ); 72 | asmchk( 73 | num_addr_args < 3, ctx, tk, 74 | "Too many address arguments" 75 | ); 76 | asmchk( 77 | num_addr_args != 1 || 78 | (op.args[op.num_args - 1] & A_ADDR) || 79 | (op.args[op.num_args - 1] & A_IMM16), 80 | ctx, tk, 81 | "Single address arguments must be (a)ddress or (i)mmediate" 82 | ); 83 | asmchk( 84 | num_addr_args != 2 || 85 | ((op.args[op.num_args - 1] & A_REG) && 86 | (op.args[op.num_args - 2] & A_REG)), 87 | ctx, tk, 88 | "Double address arguments must be (r)egister" 89 | ); 90 | num_addr_args = -1; 91 | break; 92 | default: 93 | asmchk(false, ctx, tk, "Unexpected token"); 94 | } 95 | 96 | tk = tk->next; 97 | } 98 | 99 | // skip TK_COLON 100 | asmchk( 101 | tk->kind == TK_COLON && tk->next->kind == TK_EOL, 102 | ctx, tk, "Malformed macro" 103 | ); 104 | tk = tk->next; 105 | 106 | op.start = tk->next; 107 | op.end = end; 108 | 109 | bool empty = true; 110 | while (tk && tk != end) { 111 | if (tk->kind != TK_EOL) { 112 | empty = false; 113 | break; 114 | } 115 | 116 | tk = tk->next; 117 | } 118 | 119 | asmchk(!empty, ctx, op.start, "Empty macro"); 120 | BUFADD(ctx->ops, op); 121 | } 122 | 123 | 124 | static struct Token *duplicate_macro(struct Op *op) { 125 | struct Token *optok = op->start, 126 | *duptok = NULL, 127 | *prev = NULL, 128 | *result = NULL; 129 | 130 | while (optok != op->end->next) { 131 | duptok = asmalloc(sizeof(struct Token)); 132 | memcpy(duptok, optok, sizeof(struct Token)); 133 | 134 | duptok->prev = prev; 135 | 136 | if (duptok->prev) { 137 | duptok->prev->next = duptok; 138 | } 139 | 140 | prev = duptok; 141 | 142 | result = result ? result : duptok; 143 | optok = optok->next; 144 | } 145 | 146 | duptok->next = NULL; 147 | return result; 148 | } 149 | 150 | struct Token *expand_macro( 151 | struct Context *ctx, 152 | struct Op *op, 153 | struct Token *start, 154 | struct Token *end, 155 | enum Arg *args, 156 | struct Token **arg_tokens, 157 | usize num_args 158 | ) { 159 | asmdbg(ctx, start, "Expanding macro %s", op->name); 160 | 161 | // duplicate macro tokens 162 | struct Token *tokens = duplicate_macro(op); 163 | 164 | // replace % with argument values 165 | struct Token *t = tokens, *last = tokens; 166 | while (t != NULL) { 167 | last = t; 168 | 169 | if (t->kind != TK_PERCENT) { 170 | t = t->next; 171 | continue; 172 | } 173 | 174 | asmchk( 175 | t->next && t->next->kind == TK_SYMBOL, 176 | ctx, t, "Expected argument name" 177 | ); 178 | 179 | // skip percent 180 | t->prev->next = t->next; 181 | t = t->next; 182 | 183 | // replace symbol with argument 184 | char arg_name[256]; 185 | assert(token_data(t, arg_name, sizeof(arg_name))); 186 | 187 | struct Token *argtok = NULL; 188 | 189 | for (usize i = 0; i < op->num_args; i++) { 190 | if (!strcasecmp(arg_name, op->arg_names[i])) { 191 | argtok = arg_tokens[i]; 192 | } 193 | } 194 | 195 | asmchk(argtok, ctx, t, "No such argument %s\n", arg_name); 196 | 197 | // replace with argument by direct token copy 198 | struct Token *tp = t->prev, *tn = t->next; 199 | 200 | memcpy(t, argtok, sizeof(struct Token)); 201 | t->prev = tp; 202 | t->next = tn; 203 | 204 | t = t->next; 205 | } 206 | 207 | // splice new macro tokens into token stream 208 | assert(start->prev && end->next); 209 | 210 | tokens->prev = start->prev; 211 | start->prev->next = tokens; 212 | 213 | last->next = end->next; 214 | end->next->prev = last; 215 | 216 | // return to parse() at the start of the new tokens 217 | return tokens; 218 | } 219 | -------------------------------------------------------------------------------- /asm/macros.asm: -------------------------------------------------------------------------------- 1 | ; BUILT-IN ASSEMBLER MACROS 2 | ; NOTES: 3 | ; Some macros might trash the F register. 4 | ; Macros dealing with memory might trash the HL registers. 5 | 6 | ; sets HALT bit in the STATUS REGISTER 7 | @macro 8 | HALT: 9 | inb f, 0x00 10 | or f, 0x08 11 | outb 0x00, f 12 | 13 | ; clear flags 14 | @macro 15 | CLF: 16 | mw f, 0x00 17 | 18 | ; subtract without borrow 19 | @macro 20 | SUB %r0, %x1: 21 | clb 22 | sbb %r0, %x1 23 | 24 | ; write byte to port 25 | @macro 26 | OUTB %i0, %i1: 27 | mw f, %i1 28 | outb %i0, f 29 | 30 | ; increment 31 | @macro 32 | INC %r0: 33 | add %r0, 1 34 | 35 | ; decrement, do not clear borrow 36 | @macro 37 | FDEC %r0: 38 | sbb %r0, 1 39 | 40 | ; decrement, clear borrow first 41 | @macro 42 | DEC %r0: 43 | clb 44 | sbb %r0, 1 45 | 46 | ; check equality of two 16-bit numbers 47 | ; modifies flag bit 48 | @macro 49 | EQ16 %r0, %r1, %i2: 50 | cmp %r0, ((%i2 > 8) & 0xFF) 51 | mw h, f 52 | cmp %r1, ((%i2 > 0) & 0xFF) 53 | and f, h 54 | and f, 0x02 55 | 56 | ; check equality of two 16-bit numbers 57 | ; modifies flag bit 58 | @macro 59 | EQ16 %r0, %r1, %r2, %r3: 60 | cmp %r0, %r2 61 | mw h, f 62 | cmp %r1, %r3 63 | and f, h 64 | and f, 0x02 65 | 66 | ; increments 16-bit number 67 | @macro 68 | INC16 %r0, %r1: 69 | add %r1, 1 70 | adc %r0, 0 71 | 72 | ; decrements 16-bit number 73 | @macro 74 | DEC16 %r0, %r1: 75 | clb 76 | sbb %r1, 1 77 | sbb %r0, 0 78 | 79 | ; subtracts 8-bit register from 16-bit number 80 | @macro 81 | SUB16 %r0, %r1, %r2: 82 | sbb %r1, %r2 83 | sbb %r0, 0 84 | 85 | ; subtracts 16-bit number from 16-bit number 86 | @macro 87 | SUB16 %r0, %r1, %r2, %r3: 88 | sbb %r1, %r3 89 | sbb %r0, %r2 90 | 91 | ; subtracts 16-bit constant from 16-bit number 92 | @macro 93 | SUB16 %r0, %r1, %i2: 94 | sbb %r1, ((%i2 > 0) & 0xFF) 95 | sbb %r0, ((%i2 > 8) & 0xFF) 96 | 97 | ; adds 8-bit register to 16-bit number 98 | @macro 99 | ADD16 %r0, %r1, %r2: 100 | add %r1, %r2 101 | adc %r0, 0 102 | 103 | ; adds up to 16-bit constant to 16-bit number 104 | @macro 105 | ADD16 %r0, %r1, %i2: 106 | add %r1, ((%i2 > 0) & 0xFF) 107 | adc %r0, ((%i2 > 8) & 0xFF) 108 | 109 | ; adds two 16-bit numbers 110 | @macro 111 | ADD16 %r0, %r1, %r2, %r3: 112 | add %r1, %r3 113 | adc %r0, %r2 114 | 115 | ; f = %x0 < 0 ? 1 : 0 116 | @macro 117 | SIGN %x0: 118 | mw f, %r0 119 | and f, 0x80 120 | 121 | ; f = %r0, %r1 < 0 ? 1 : 0 122 | @macro 123 | SIGN16 %r0, %r1: 124 | sign %r0 125 | 126 | ; moves 16-bit constant into registers 127 | @macro 128 | MW16 %r0, %r1, %i2: 129 | mw %r0, ((%i2 > 8) & 0xFF) 130 | mw %r1, ((%i2 > 0) & 0xFF) 131 | 132 | ; moves 16-bit constant across registers 133 | @macro 134 | MW16 %r0, %r1, %r2, %r3: 135 | mw %r0, %r2 136 | mw %r1, %r3 137 | 138 | ; bitwise not 139 | @macro 140 | NOT %r0: 141 | nor %r0, %r0 142 | 143 | ; bitwise nand 144 | @macro 145 | NAND %r0, %x1: 146 | and %r0, %x1 147 | not %r0 148 | 149 | ; bitwise xnor 150 | @macro 151 | XNOR %r0, %x1: 152 | mw f, %r0 153 | nor %r0, %x1 154 | and f, %x1 155 | or %r0, f 156 | 157 | ; bitwwise xor 158 | @macro 159 | XOR %r0, %x1: 160 | mw f, %x1 161 | or f, %r0 162 | nand %r0, %x1 163 | and %r0, f 164 | 165 | ; lda override with destination as any two registers 166 | @macro 167 | LDA %r0, %r1, [%i2]: 168 | lda [%i2] 169 | mw %r0, h 170 | mw %r1, l 171 | 172 | ; lda override with registers instead of constant 173 | @macro 174 | LDA %r0, %r1: 175 | mw h, %r0 176 | mw l, %r1 177 | 178 | ; 16-bit load word override 179 | @macro 180 | LW16 %r0, %r1, %a2: 181 | lw %r1, [(%a2 + 0)] 182 | lw %r0, [(%a2 + 1)] 183 | 184 | ; 16-bit load word HL override 185 | @macro 186 | LW16 %r0, %r1: 187 | lw %r1 188 | inc16 h, l 189 | lw %r0 190 | 191 | ; 16-bit store word override 192 | @macro 193 | SW16 %a0, %r1, %r2: 194 | sw [(%a0 + 0)], %r2 195 | sw [(%a0 + 1)], %r1 196 | 197 | ; 16-bit store word immediate override 198 | @macro 199 | SW16 %a0, %i1: 200 | sw [(%a0 + 0)], ((%i1 > 0) & 0xFF) 201 | sw [(%a0 + 1)], ((%i1 > 8) & 0xFF) 202 | 203 | ; 16-bit store word HL override 204 | @macro 205 | SW16 %r0, %r1: 206 | sw %r1 207 | inc16 h, l 208 | sw %r0 209 | 210 | ; LW override which supports two register arguments as an address 211 | @macro 212 | LW %r0, %r1, %r2: 213 | mw h, %r1 214 | mw l, %r2 215 | lw %r0 216 | 217 | ; SW override which supports two register arguments as an address 218 | @macro 219 | SW %r0, %r1, %r2: 220 | mw h, %r0 221 | mw l, %r1 222 | sw %r2 223 | 224 | ; SW override which stores an immediate value via f 225 | @macro 226 | SW [%a0], %i1: 227 | mw f, %i1 228 | sw [%a0], f 229 | 230 | ; SW override which stores an immediate value via f to register address 231 | @macro 232 | SW %r0, %r1, %i2: 233 | mw f, %i2 234 | sw %r0, %r1, f 235 | 236 | ; calls a subroutine at a known location 237 | @macro 238 | CALL [%i0]: 239 | push (($ + 9) > 8) ; 2 bytes 240 | push (($ + 7) & 0xFF) ; 2 bytes 241 | lda [%i0] ; 3 bytes 242 | jnz 1 ; 2 bytes 243 | 244 | ; calls a subroutine at a register location 245 | @macro 246 | CALL %r0, %r1: 247 | push (($ + 10) > 8) ; 2 bytes 248 | push (($ + 8) & 0xFF) ; 2 bytes 249 | mw h, %r0 ; 2 bytes 250 | mw l, %r1 ; 2 bytes 251 | jnz 1 ; 2 bytes 252 | 253 | ; returns from a subroutine 254 | @macro 255 | RET: 256 | pop l 257 | pop h 258 | jmp 259 | 260 | ; JNZ to register adress 261 | @macro 262 | JNZ %x0, %r1, %r2: 263 | mw h, %r0 264 | mw l, %r1 265 | jnz %x0 266 | 267 | ; JNZ to constant address 268 | @macro 269 | JNZ %x0, [%i1]: 270 | lda [%i1] 271 | jnz %x0 272 | 273 | ; unconditional jump to [HL] 274 | @macro 275 | JMP: 276 | jnz 1 277 | 278 | ; unconditional jump to register address 279 | @macro 280 | JMP %r0, %r1: 281 | mw h, %r0 282 | mw l, %r1 283 | jmp 284 | 285 | ; unconditional jump to constant address 286 | @macro 287 | JMP [%i0]: 288 | lda [%i0] 289 | jmp 290 | 291 | ; jump if mask 292 | @macro 293 | JMS %r0, %x1, [%a2]: 294 | mw f, %r0 295 | and f, %x1 296 | jnz f, [%a2] 297 | 298 | ; jump if not mask 299 | @macro 300 | JMN %r0, %x1, [%a2]: 301 | mw f, %r0 302 | not f 303 | and f, %x1 304 | jnz f, [%a2] 305 | 306 | ; jump if less than 307 | @macro 308 | JLT %r0, %x1, [%a2]: 309 | cmp %r0, %x1 310 | and f, 0x1 311 | jnz f, [%a2] 312 | 313 | ; jump if less than 314 | @macro 315 | JLT [%a0]: 316 | and f, 0x1 317 | jnz f, [%a0] 318 | 319 | ; jump if less than or equal 320 | @macro 321 | JLE %r0, %x1, [%a2]: 322 | cmp %r0, %x1 323 | and f, 0x3 324 | jnz f, [%a2] 325 | 326 | ; jump if less than or equal 327 | @macro 328 | JLE [%a0]: 329 | and f 0x3, 330 | jnz f, [%a0] 331 | 332 | ; jump if equal 333 | @macro 334 | JEQ %r0, %x1, [%a2]: 335 | cmp %r0, %x1 336 | and f, 0x2 337 | jnz f, [%a2] 338 | 339 | ; jump if equal 340 | @macro 341 | JEQ [%a0]: 342 | and f, 0x2 343 | jnz f, [%a0] 344 | 345 | ; jump if not equal 346 | @macro 347 | JNE %r0, %x1, [%a2]: 348 | cmp %r0, %x1 349 | not f 350 | and f, 0x2 351 | jnz f, [%a2] 352 | 353 | ; jump if not equal 354 | @macro 355 | JNE [%a0]: 356 | not f 357 | and f, 0x2 358 | jnz f, [%a0] 359 | 360 | ; jump if greater than or equal 361 | @macro 362 | JGE %r0, %x1, [%a2]: 363 | cmp %r0, %x1 364 | nand f, 0x1 365 | and f, 0x3 366 | jnz f, [%a2] 367 | 368 | ; jump if greater than or equal 369 | @macro 370 | JGE [%a0]: 371 | nand f, 0x1 372 | and f, 0x3 373 | jnz f, [%a0] 374 | 375 | ; jump if greater than 376 | @macro 377 | JGT %r0, %x1, [%a2]: 378 | cmp %r0, %x1 379 | and f, 0x3 380 | jz f, [%a2] 381 | 382 | ; jump if greater than 383 | @macro 384 | JGT [%a0]: 385 | and f, 0x3 386 | jz f, [%a0] 387 | 388 | ; jump if zero 389 | @macro 390 | JZ %x0, [%a1]: 391 | jeq %x0, 0, [%a1] 392 | 393 | ; jump if carry 394 | @macro 395 | JC [%a0]: 396 | and f, 0x4 397 | jnz f, [%a0] 398 | 399 | ; jump if no carry 400 | @macro 401 | JNC [%a0]: 402 | not f 403 | and f, 0x4 404 | jnz f, [%a0] 405 | 406 | ; jump if borrow 407 | @macro 408 | JB [%a0]: 409 | and f, 0x8 410 | jnz f, [%a0] 411 | 412 | ; jump if no borrow 413 | @macro 414 | JNB [%a0]: 415 | not f 416 | and f, 0x8 417 | jnz f, [%a0] 418 | 419 | ; set less than flag 420 | @macro 421 | STL: 422 | or f, 0x1 423 | 424 | ; clear less than flag 425 | @macro 426 | CLL: 427 | and f, (~0x1) 428 | 429 | ; set equal flag 430 | @macro 431 | STE: 432 | or f, 0x2 433 | 434 | ; clear equal flag 435 | @macro 436 | CLE: 437 | and f, (~0x2) 438 | 439 | ; set carry flag 440 | @macro 441 | STC: 442 | or f, 0x4 443 | 444 | ; clear carry flag 445 | @macro 446 | CLC: 447 | and f, (~0x4) 448 | 449 | ; set borrow flag 450 | @macro 451 | STB: 452 | or f, 0x8 453 | 454 | ; clear borrow flag 455 | @macro 456 | CLB: 457 | and f, (~0x8) 458 | 459 | ; push two registers 460 | @macro 461 | PUSH %r0, %r1: 462 | push %r0 463 | push %r1 464 | 465 | ; push three registers 466 | @macro 467 | PUSH %r0, %r1, %r2: 468 | push %r0 469 | push %r1, %r2 470 | 471 | ; push four registers 472 | @macro 473 | PUSH %r0, %r1, %r2, %r3: 474 | push %r0 475 | push %r1, %r2, %r3 476 | 477 | ; push five registers 478 | @macro 479 | PUSH %r0, %r1, %r2, %r3, %r4: 480 | push %r0 481 | push %r1, %r2, %r3, %r4 482 | 483 | ; pop two registers 484 | @macro 485 | POP %r0, %r1: 486 | pop %r0 487 | pop %r1 488 | 489 | ; pop three registers 490 | @macro 491 | POP %r0, %r1, %r2: 492 | pop %r0 493 | pop %r1, %r2 494 | 495 | ; pop four registers 496 | @macro 497 | POP %r0, %r1, %r2, %r3: 498 | pop %r0 499 | pop %r1, %r2, %r3 500 | 501 | ; pop five registers 502 | @macro 503 | POP %r0, %r1, %r2, %r3, %r4: 504 | pop %r0 505 | pop %r1, %r2, %r3, %r4 506 | 507 | ; push all general purpose registers 508 | @macro 509 | PUSHA: 510 | push a, b, c, d, z 511 | 512 | ; pop all general purpose registers 513 | @macro 514 | POPA: 515 | pop z, d, c, b, a 516 | 517 | ; END OF BUILT-IN MACROS 518 | -------------------------------------------------------------------------------- /asm/microcode.c: -------------------------------------------------------------------------------- 1 | #include "asm.h" 2 | 3 | #define MOP(_n, _t, _b)\ 4 | [(_t * 16) + _b] = ((struct MicroOp) { #_n, true, _t, _b }) 5 | struct MicroOp { 6 | const char *name; 7 | bool present; 8 | u8 type, index; 9 | }; 10 | 11 | // microcode types 12 | #define MTA 0 13 | #define MTB 1 14 | #define MTG 2 15 | #define MTD 4 16 | 17 | struct MicroOp MICRO_OPS[] = { 18 | // TYPE A MICROCODE 19 | MOP(EMEM, MTA, 0), 20 | MOP(ASP, MTA, 1), 21 | MOP(AIMM, MTA, 2), 22 | MOP(LMEM, MTA, 3), 23 | MOP(SPINC, MTA, 4), 24 | MOP(SPDEC, MTA, 5), 25 | MOP(JNZ, MTA, 6), 26 | 27 | // TYPE B MICROCODE 28 | MOP(LDX, MTB, 0), 29 | MOP(LDY, MTB, 1), 30 | MOP(LDF, MTB, 2), 31 | MOP(EALU, MTB, 3), 32 | MOP(LDH, MTB, 4), 33 | MOP(LDL, MTB, 5), 34 | MOP(LPRT, MTB, 6), 35 | 36 | // GENERIC MICROCODE 37 | MOP(EREG, MTG, 7), 38 | MOP(LREG, MTG, 8), 39 | MOP(SEL, MTG, 9), 40 | MOP(EOP1, MTG, 10), 41 | MOP(EOP2, MTG, 11), 42 | MOP(EDEV, MTG, 12), 43 | MOP(LDEV, MTG, 13), 44 | MOP(CC, MTG, 14), 45 | 46 | // DUMMY MICROCODE 47 | MOP(AHL, MTD, 0), 48 | }; 49 | 50 | struct MicrocodeContext { 51 | // instructions * const/reg * microcode entries 52 | u16 code[I_COUNT][2][8]; 53 | 54 | enum Instruction instruction; 55 | bool reg; 56 | u8 index; 57 | }; 58 | 59 | const struct MicroOp *find_op(const char *name) { 60 | const struct MicroOp *op = NULL; 61 | 62 | for (usize i = 0; i < ARRLEN(MICRO_OPS); i++) { 63 | if (MICRO_OPS[i].present && 64 | !strcasecmp(name, MICRO_OPS[i].name)) { 65 | op = &MICRO_OPS[i]; 66 | break; 67 | } 68 | } 69 | 70 | return op; 71 | } 72 | 73 | struct Token *microcode_parse(struct Context *ctx, struct Token *first) { 74 | if (!ctx->mctx) { 75 | ctx->mctx = asmalloc(sizeof(struct MicrocodeContext)); 76 | memset(ctx->mctx, 0, sizeof(struct MicrocodeContext)); 77 | } 78 | 79 | struct Token *last, *eol = first; 80 | while (eol->kind != TK_EOL) { 81 | eol = eol->next; 82 | } 83 | last = eol->prev; 84 | 85 | // label 86 | if (last->kind == TK_COLON) { 87 | // must be .const or .reg 88 | if (first->kind == TK_DOT) { 89 | asmchk( 90 | first->next->kind == TK_SYMBOL, 91 | ctx, first->next, 92 | "Expected symbol" 93 | ); 94 | 95 | char symbol[256]; 96 | assert(token_data(first->next, symbol, sizeof(symbol))); 97 | 98 | if (!strcasecmp(symbol, "const")) { 99 | ctx->mctx->reg = false; 100 | } else if (!strcasecmp(symbol, "reg")) { 101 | ctx->mctx->reg = true; 102 | } else { 103 | asmchk(false, ctx, first->next, "Illegal microcode label"); 104 | } 105 | 106 | ctx->mctx->index = 0; 107 | } else { 108 | asmchk( 109 | first->kind == TK_SYMBOL, 110 | ctx, first, 111 | "Expected symbol" 112 | ); 113 | 114 | char symbol[256]; 115 | assert(token_data(first, symbol, sizeof(symbol))); 116 | 117 | isize i; 118 | asmchk( 119 | (i = strcasetab(I_NAMES, I_COUNT, symbol)) != -1, 120 | ctx, first, 121 | "Unknown instruction for microcode" 122 | ); 123 | asmdbg(ctx, first, "Parsing microcode for %s", symbol); 124 | 125 | ctx->mctx->instruction = (enum Instruction) i; 126 | ctx->mctx->reg = false; 127 | ctx->mctx->index = 0; 128 | } 129 | 130 | return eol->next; 131 | } 132 | 133 | asmchk( 134 | first->kind == TK_SYMBOL || first->kind == TK_NOT, 135 | ctx, first, 136 | "Expected symbol or ~" 137 | ); 138 | 139 | struct Token *t = first; 140 | 141 | const struct MicroOp *op; 142 | char symbol[256]; 143 | 144 | u16 cval = 0; 145 | u8 ctype = MTG; 146 | 147 | // check if macro 148 | if (t->kind == TK_SYMBOL && t->next == eol) { 149 | assert(token_data(t, symbol, sizeof(symbol))); 150 | 151 | struct Op *op = &ctx->ops.buffer[0]; 152 | while (op != &ctx->ops.buffer[ctx->ops.size]) { 153 | if (!op->macro || strcasecmp(symbol, op->name)) { 154 | op++; 155 | continue; 156 | } 157 | 158 | return expand_macro(ctx, op, first, eol->prev, NULL, NULL, 0); 159 | } 160 | } 161 | 162 | while (t != eol) { 163 | switch (t->kind) { 164 | case TK_NOT: 165 | asmchk(t->prev->kind != TK_NOT, ctx, t->prev, "Too many ~"); 166 | t = t->next; 167 | continue; 168 | case TK_SYMBOL: 169 | assert(token_data(t, symbol, sizeof(symbol))); 170 | asmchk(op = find_op(symbol), ctx, t, "Unknown op"); 171 | 172 | // skip dummy operations 173 | if (op->type == MTD || t->prev->kind == TK_NOT) { 174 | t = t->next; 175 | continue; 176 | } 177 | 178 | asmchk( 179 | ctype == MTG || op->type == MTG || op->type == ctype, 180 | ctx, t, 181 | "Cannot combine ops of different types" 182 | ); 183 | ctype = op->type != MTG ? op->type : ctype; 184 | cval |= 1 << op->index; 185 | break; 186 | default: 187 | asmchk(false, ctx, t, "Expected ~ or symbol"); 188 | } 189 | 190 | t = t->next; 191 | } 192 | 193 | cval = BIT_SET(cval, 15, ctype == MTB ? 1 : 0); 194 | ctx->mctx->code 195 | [ctx->mctx->instruction] 196 | [ctx->mctx->reg ? 1 : 0] 197 | [ctx->mctx->index++] = cval; 198 | 199 | return eol->next; 200 | } 201 | 202 | void microcode_emit(struct Context *ctx) { 203 | for (enum Instruction i = 0; i < I_COUNT; i++) { 204 | for (u8 reg = 0; reg < 2; reg++) { 205 | const u16 *imc = ctx->mctx->code[i][reg]; 206 | 207 | for (u8 j = 0; j < 8; j++) { 208 | u16 data = imc[j]; 209 | 210 | if (j == 7 || imc[j + 1] == 0) { 211 | data |= 1 << 14; 212 | } 213 | 214 | BUFADD(ctx->out, ((data >> 0) & 0xFF)); 215 | BUFADD(ctx->out, ((data >> 8) & 0xFF)); 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /circ/microcode.asm: -------------------------------------------------------------------------------- 1 | ; JDH-8 MICROCODE 2 | ; FOR BOTH LOGISIM AND PHYSICAL CIRCUIT 3 | 4 | @microcode 5 | 6 | mw: 7 | .const: 8 | eop1, ~sel, lreg 9 | .reg: 10 | ereg, sel, ldy 11 | ealu, ~sel, lreg 12 | 13 | lw: 14 | .const: 15 | aimm, ~sel, emem, lreg 16 | .reg: 17 | ahl, ~sel, emem, lreg 18 | 19 | sw: 20 | .const: 21 | aimm, ~sel, ereg, lmem 22 | .reg: 23 | ahl, ~sel, ereg, lmem 24 | 25 | push: 26 | .const: 27 | asp, eop1, lmem 28 | spdec 29 | .reg: 30 | asp, ~sel, ereg, lmem 31 | spdec 32 | 33 | pop: 34 | .const: 35 | .reg: 36 | spinc 37 | asp, ~sel, emem, lreg 38 | 39 | lda: 40 | .const: 41 | eop1, ldl 42 | eop2, ldh 43 | .reg: 44 | 45 | jnz: 46 | .const: 47 | eop1, jnz 48 | .reg: 49 | ~sel, ereg, jnz 50 | 51 | inb: 52 | .const: 53 | eop1, lprt 54 | edev, ~sel, lreg 55 | .reg: 56 | sel, ereg, lprt 57 | edev, ~sel, lreg 58 | 59 | outb: 60 | .const: 61 | eop1, lprt 62 | ~sel, ereg, ldev 63 | .reg: 64 | sel, ereg, lprt 65 | ~sel, ereg, ldev 66 | 67 | cmp: 68 | .const: 69 | ~sel, ereg, ldx 70 | eop1, ldy 71 | ldf 72 | .reg: 73 | ~sel, ereg, ldx 74 | sel, ereg, ldy 75 | ldf 76 | 77 | @macro 78 | a_const: 79 | ~sel, ereg, ldx 80 | eop1, ldy 81 | ~sel, ealu, lreg 82 | 83 | @macro 84 | a_reg: 85 | ~sel, ereg, ldx 86 | sel, ereg, ldy 87 | ~sel, ealu, lreg 88 | 89 | add: 90 | .const: 91 | a_const 92 | ldf 93 | .reg: 94 | a_reg 95 | ldf 96 | 97 | adc: 98 | .const: 99 | a_const 100 | ldf 101 | .reg: 102 | a_reg 103 | ldf 104 | 105 | and: 106 | .const: 107 | a_const 108 | .reg: 109 | a_reg 110 | 111 | or: 112 | .const: 113 | a_const 114 | .reg: 115 | a_reg 116 | 117 | nor: 118 | .const: 119 | a_const 120 | .reg: 121 | a_reg 122 | 123 | sbb: 124 | .const: 125 | a_const 126 | ldf 127 | .reg: 128 | a_reg 129 | ldf 130 | -------------------------------------------------------------------------------- /common/jdh8.h: -------------------------------------------------------------------------------- 1 | #ifndef JDH8_H 2 | #define JDH8_H 3 | 4 | #include "util.h" 5 | 6 | #define I_NAMES ((const char *[]) { \ 7 | "MW", "LW", "SW", "PUSH", "POP", "LDA", "JNZ", "INB", "OUTB", \ 8 | "ADD", "ADC", "AND", "OR", "NOR", "CMP", "SBB",}) 9 | #define I_NAME(_i) (I_NAMES)[(_i)] 10 | #define I_COUNT (I_SBB + 1) 11 | enum Instruction { 12 | I_MW = 0x00, 13 | I_LW = 0x01, 14 | I_SW = 0x02, 15 | I_PUSH = 0x03, 16 | I_POP = 0x04, 17 | I_LDA = 0x05, 18 | I_JNZ = 0x06, 19 | I_INB = 0x07, 20 | I_OUTB = 0x08, 21 | I_ADD = 0x09, 22 | I_ADC = 0x0A, 23 | I_AND = 0x0B, 24 | I_OR = 0x0C, 25 | I_NOR = 0x0D, 26 | I_CMP = 0x0E, 27 | I_SBB = 0x0F 28 | }; 29 | 30 | #define R_NAMES ((const char *[]){"A", "B", "C", "D", "L", "H", "Z", "F"}) 31 | #define R_NAME(_r) ((R_NAMES)[(_r)]) 32 | #define R_COUNT (R_F + 1) 33 | enum Register { 34 | R_A = 0x00, 35 | R_B = 0x01, 36 | R_C = 0x02, 37 | R_D = 0x03, 38 | R_L = 0x04, 39 | R_H = 0x05, 40 | R_Z = 0x06, 41 | R_F = 0x07, 42 | }; 43 | 44 | #define SP_NAMES ((const char *[]){"MB", "SP", "PC"}) 45 | #define SP_NAME(_sp) ((SP_NAMES)[(_sp)]) 46 | #define SP_COUNT (SP_PC + 1) 47 | enum Special { 48 | SP_MB = 0x00, 49 | SP_SP = 0x01, 50 | SP_PC = 0x02, 51 | }; 52 | 53 | #define F_NAMES ((const char *[]){"LESS", "EQUAL", "CARRY", "BORROW"}) 54 | #define F_NAME(_f) ((F_NAMES)[(_f)]) 55 | #define F_COUNT (F_BORROW + 1) 56 | enum Flag { 57 | F_LESS = 0x00, 58 | F_EQUAL = 0x01, 59 | F_CARRY = 0x02, 60 | F_BORROW = 0x03, 61 | }; 62 | 63 | enum Port { 64 | P_STATUS_REGISTER = 0x00 65 | }; 66 | 67 | #define S_NAMES ((const char *[]){"UNUSED", "ERROR", "UNUSED", "HALT"}) 68 | #define S_NAME(_f) ((S_NAMES)[(_f)]) 69 | #define S_COUNT (S_HALT + 1) 70 | enum Status { 71 | S_UNUSED0 = 0x00, 72 | S_ERROR = 0x01, 73 | S_UNUSED1 = 0x02, 74 | S_HALT = 0x03 75 | }; 76 | 77 | #define ADDR_MB 0xFFFA 78 | #define ADDR_SP 0xFFFC 79 | #define ADDR_PC 0xFFFE 80 | 81 | #define SIZE_ROM 0x8000 82 | #define SIZE_BANK 0x4000 83 | #define SIZE_RAM 0x4000 84 | 85 | #define ADDR_ROM 0x0000 86 | #define ADDR_BANK (ADDR_ROM + SIZE_ROM) 87 | #define ADDR_RAM (ADDR_BANK + SIZE_BANK) 88 | 89 | // forward declaration 90 | struct JDH8; 91 | 92 | // Limited by number of available ports - max possible. 93 | #define DEVICE_MAX 125 94 | struct Device { 95 | u8 id; 96 | char name[32]; 97 | void *state; 98 | void (*tick)(struct JDH8 *, struct Device *); 99 | u8 (*send)(struct JDH8 *, struct Device *); 100 | void (*receive)(struct JDH8 *, struct Device *, u8 data); 101 | void (*destroy)(struct JDH8*, struct Device *); 102 | }; 103 | 104 | #define FOR_DEVICES(_s, _i, _d) \ 105 | usize _i; \ 106 | struct Device *_d; \ 107 | for (_i = 0, _d = &(_s)->devices[_i]; \ 108 | _i < DEVICE_MAX; \ 109 | _i++,_d = &(_s)->devices[_i]) \ 110 | 111 | // maximum number of memory banks 112 | #define BANKS_MAX 256 113 | 114 | struct JDH8 { 115 | u8 status; 116 | 117 | union { 118 | struct { 119 | u16 mb, sp, pc; 120 | }; 121 | 122 | u16 raw[3]; 123 | } special; 124 | 125 | union { 126 | struct { 127 | u8 rom[SIZE_ROM]; 128 | u8 bank[SIZE_BANK]; 129 | u8 ram[SIZE_RAM]; 130 | }; 131 | 132 | u8 raw[0xFFFF]; 133 | } memory; 134 | 135 | // non-NULL if present 136 | void *banks[BANKS_MAX]; 137 | 138 | union { 139 | struct { 140 | u8 a, b, c, d, l, h, z, f; 141 | }; 142 | 143 | struct { 144 | u16 __ignore0[2]; 145 | u16 hl; 146 | u16 __ignore1; 147 | }; 148 | 149 | u8 raw[8]; 150 | } registers; 151 | 152 | struct Device devices[DEVICE_MAX]; 153 | bool write_protect; 154 | bool simulating; 155 | }; 156 | 157 | static inline u8 *ppeek(struct JDH8 *state, u16 addr) { 158 | if (addr >= ADDR_MB) { 159 | return &((u8 *) state->special.raw)[addr - ADDR_MB]; 160 | } else if (state->special.mb != 0 && addr >= 0x8000 && addr < 0xC000) { 161 | void *bank = state->banks[state->special.mb]; 162 | 163 | if (!bank) { 164 | warn( 165 | "Read/write attempt for non-present bank 0x%04x\n", 166 | state->special.mb 167 | ); 168 | return NULL; 169 | } 170 | 171 | return &((u8 *) bank)[addr - 0x8000]; 172 | } 173 | 174 | return &state->memory.raw[addr]; 175 | } 176 | 177 | static inline u8 peek(struct JDH8 *state, u16 addr) { 178 | u8 *p = ppeek(state, addr); 179 | return p ? *p : 0; 180 | } 181 | 182 | static inline u16 peek16(struct JDH8 *state, u16 addr) { 183 | return 184 | (((u16) (peek(state, addr + 0))) << 0) | 185 | (((u16) (peek(state, addr + 1))) << 8); 186 | } 187 | 188 | static inline void poke(struct JDH8 *state, u16 addr, u8 v) { 189 | if (state->write_protect && 190 | addr >= ADDR_ROM && addr < ADDR_ROM + SIZE_ROM) { 191 | warn( 192 | "Attempt to write to ROM at [0x%04X] (pc=[0x%04X])\n", 193 | addr, state->special.pc); 194 | return; 195 | } 196 | 197 | u8 *p = ppeek(state, addr); 198 | if (p) { 199 | *p = v; 200 | } 201 | } 202 | 203 | static inline void poke16(struct JDH8 *state, u16 addr, u16 v) { 204 | poke(state, addr + 0, (v >> 0) & 0xFF); 205 | poke(state, addr + 1, (v >> 8) & 0xFF); 206 | } 207 | 208 | static inline void emu_memcpy( 209 | struct JDH8 *state, 210 | u16 dest, 211 | const void *src, 212 | usize n 213 | ) { 214 | usize i = 0; 215 | while (i != n) { 216 | poke(state, dest + i, ((const u8 *) src)[i]); 217 | i++; 218 | } 219 | } 220 | 221 | static inline void emu_memset( 222 | struct JDH8 *state, 223 | u16 dest, 224 | u8 data, 225 | usize n 226 | ) { 227 | while (n-- != 0) { 228 | poke(state, dest++, data); 229 | } 230 | } 231 | 232 | #endif 233 | -------------------------------------------------------------------------------- /common/jdh8util.c: -------------------------------------------------------------------------------- 1 | #include "jdh8util.h" 2 | #include "jdh8.h" 3 | 4 | // reverse assembles some bytes 5 | // returns non-zero on failure 6 | int asm2str(char *dst, usize dst_size, u8 *data, usize n) { 7 | #define __asm2str_append(_s) do { \ 8 | const char *__s = (_s); \ 9 | usize _sz = strlen(__s); \ 10 | if (p + _sz >= dst + dst_size) { \ 11 | return -1; \ 12 | } \ 13 | memcpy(p, __s, _sz); \ 14 | p += _sz; \ 15 | } while (0); 16 | 17 | #define __asm2str_expect_size(_n) if (n <= (_n)) { return -1; } 18 | 19 | #define __asm2str_append_addr() do { \ 20 | __asm2str_expect_size(3); \ 21 | addr = *((u16 *) (data + 1)); \ 22 | snprintf(buf, sizeof(buf), "0x%04x", addr); \ 23 | __asm2str_append(buf); \ 24 | } while (0); 25 | 26 | #define __asm2str_illegal() \ 27 | do { p = dst; __asm2str_append("ILLEGAL INSTRUCTION"); } while (0); 28 | 29 | if (dst_size < 1) { 30 | return -1; 31 | } 32 | 33 | char buf[256], *p = dst; 34 | 35 | u16 addr; 36 | u8 op0 = data[0], op1, 37 | inst = (op0 >> 4) & 0xF, 38 | y = (op0 >> 3) & 1, 39 | reg = op0 & 0x7; 40 | 41 | // write instruction name 42 | __asm2str_append(I_NAMES[inst]); 43 | __asm2str_append(" "); 44 | 45 | // reg, imm8/reg 46 | if (inst == I_MW || inst == I_INB || inst >= I_ADD) { 47 | __asm2str_expect_size(2); 48 | op1 = data[1]; 49 | 50 | __asm2str_append(R_NAMES[reg]); 51 | __asm2str_append(", "); 52 | 53 | if (y) { 54 | __asm2str_append(R_NAMES[op1]); 55 | } else { 56 | snprintf(buf, sizeof(buf), "0x%02x", op1); 57 | __asm2str_append(buf); 58 | } 59 | } else if (inst == I_LW) { 60 | __asm2str_append(R_NAMES[reg]); 61 | 62 | if (!y) { 63 | __asm2str_append(", "); 64 | __asm2str_append_addr(); 65 | } 66 | } else if (inst == I_SW) { 67 | if (!y) { 68 | __asm2str_append_addr(); 69 | __asm2str_append(", "); 70 | } 71 | 72 | __asm2str_append(R_NAMES[reg]); 73 | } else if (inst == I_PUSH || inst == I_JNZ) { 74 | if (y) { 75 | __asm2str_append(R_NAMES[reg]); 76 | } else { 77 | __asm2str_expect_size(2); 78 | op1 = data[1]; 79 | snprintf(buf, sizeof(buf), "0x%02x", op1); 80 | __asm2str_append(buf); 81 | } 82 | } else if (inst == I_POP) { 83 | if (y) { 84 | __asm2str_append(R_NAMES[reg]); 85 | } else { 86 | __asm2str_illegal(); 87 | } 88 | } else if (inst == I_LDA) { 89 | __asm2str_append_addr(); 90 | } else if (inst == I_OUTB) { 91 | __asm2str_expect_size(2); 92 | op1 = data[1]; 93 | 94 | if (y) { 95 | __asm2str_append(R_NAMES[op1 & 0x7]); 96 | __asm2str_append(", "); 97 | } else { 98 | snprintf(buf, sizeof(buf), "0x%02x", op1); 99 | __asm2str_append(buf); 100 | __asm2str_append(", "); 101 | } 102 | 103 | __asm2str_append(R_NAMES[reg]); 104 | } 105 | 106 | *p = '\0'; 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /common/jdh8util.h: -------------------------------------------------------------------------------- 1 | #ifndef JDH8_EXTRA 2 | #define JDH8_EXTRA 3 | 4 | #include "util.h" 5 | 6 | int asm2str(char *dst, usize dst_size, u8 *start, usize n); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /common/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) 16 | 17 | #define MIN(_a, _b) __extension__({ \ 18 | __typeof__(_a) __a = (_a); \ 19 | __typeof__(_b) __b = (_b); \ 20 | __a < __b ? __a : __b; \ 21 | }) 22 | 23 | #define MAX(_a, _b) __extension__({ \ 24 | __typeof__(_a) __a = (_a); \ 25 | __typeof__(_b) __b = (_b); \ 26 | __a > __b ? __a : __b; \ 27 | }) 28 | 29 | typedef uint8_t u8; 30 | typedef uint16_t u16; 31 | typedef uint32_t u32; 32 | typedef uint64_t u64; 33 | typedef uint64_t usize; 34 | typedef uintptr_t uptr; 35 | 36 | typedef int8_t i8; 37 | typedef int16_t i16; 38 | typedef int32_t i32; 39 | typedef int64_t i64; 40 | typedef ssize_t isize; 41 | typedef intptr_t iptr; 42 | 43 | #define BIT_SET(_v, _n, _x) BIT_SET_MASK((_v), (1 << (_n)), (_x)) 44 | 45 | #define BIT_SET_MASK(_v, _m, _x) __extension__({ \ 46 | __typeof__(_v) __v = (_v); \ 47 | __v ^ ((-(_x) ^ __v) & (_m)); \ 48 | }) 49 | 50 | // true if _a prefixes _b 51 | #define strpre(_a, _b) (strcmp((_a), (_b), strlen(_a)) == 0) 52 | 53 | // true if _a suffixes _b 54 | #define strsuf(_a, _b) __extension__({ \ 55 | const char *__a = (_a), *__b = (_b); \ 56 | usize _al = strlen(__a), _bl = strlen(__b); \ 57 | _al > _bl ? \ 58 | 0 : !strncmp(__b + _bl - _al, __a, _al); \ 59 | }) 60 | 61 | // finds index of _s in char *_a[_n] if present (-1 if not) 62 | #define strtab(_a, _n, _s) _strtab_impl(_a, _n, _s, strcmp) 63 | #define strcasetab(_a, _n, _s) _strtab_impl(_a, _n, _s, strcasecmp) 64 | 65 | #define _strtab_impl(_a, _n, _s, _cmp) __extension__({ \ 66 | usize _i, __n = (_n); \ 67 | char **__a = (char **) (_a); \ 68 | for (_i = 0; _i < __n; _i++) { \ 69 | if (!_cmp(__a[_i], _s)) { \ 70 | break; \ 71 | } \ 72 | } \ 73 | _i == __n ? (usize) -1 : _i; \ 74 | }) 75 | 76 | // converts _s to uppercase in-place 77 | #ifndef strupr 78 | #define strupr(_s) __extension__({ \ 79 | char *__s = (_s); \ 80 | do { \ 81 | *__s = toupper(*__s); \ 82 | } while (*(__s++) != 0); \ 83 | _s; \ 84 | }) 85 | #endif 86 | 87 | // from https://c-for-dummies.com/blog/?p=3886 88 | #if !defined(strlcpy) 89 | #define strlcpy __strlcpy_impl 90 | #endif 91 | 92 | static inline size_t __strlcpy_impl( 93 | char *dst, 94 | const char *src, 95 | size_t dstsize 96 | ) { 97 | size_t offset; 98 | 99 | offset = 0; 100 | 101 | if (dstsize > 0) { 102 | while (*(src + offset) != '\0') { 103 | if (offset == dstsize) { 104 | offset--; 105 | break; 106 | } 107 | 108 | *(dst + offset) = *(src + offset); 109 | offset++; 110 | } 111 | } 112 | 113 | *(dst + offset) = '\0'; 114 | 115 | while(*(src+offset) != '\0') { 116 | offset++; 117 | } 118 | 119 | return(offset); 120 | } 121 | 122 | static inline char *strlstrip(char *str) { 123 | usize len = strlen(str); 124 | while (isspace(*str)) memmove(str, str + 1, --len); 125 | str[len] = '\0'; 126 | return str; 127 | } 128 | 129 | static inline char *strrstrip(char *str) { 130 | usize len = strlen(str); 131 | while (isspace(str[len - 1])) str[--len] = '\0'; 132 | return str; 133 | } 134 | 135 | static inline char *strstrip(char *str) { 136 | return strlstrip(strrstrip(str)); 137 | } 138 | 139 | static inline int strtoi64(const char *s, int base, i64 *l) { 140 | char *t = NULL; 141 | errno = 0; 142 | i64 m = (i64) strtoll(s, &t, base); 143 | if (*t || ( 144 | (m == LONG_MIN || m == LONG_MAX) && errno == ERANGE)) { 145 | return -1; 146 | } 147 | 148 | *l = m; 149 | return 0; 150 | } 151 | 152 | static inline int strtou64(const char *s, int base, u64 *l) { 153 | char *t = NULL; 154 | errno = 0; 155 | u64 m = (u64) strtoull(s, &t, base); 156 | if (*t || (m == ULONG_MAX && errno == ERANGE)) { 157 | return -1; 158 | } 159 | *l = m; 160 | return 0; 161 | } 162 | 163 | #define _DECL_STRTOX(_u, _t, _s, _min, _max) \ 164 | static inline int strto##_s(const char *s, int base, _t *i) { \ 165 | _u##64 r; \ 166 | int v = strto##_u##64(s, base, &r); \ 167 | *i = 0; \ 168 | if (v) { \ 169 | return v; \ 170 | } else if (r >= (_min) && r <= (_max)) { \ 171 | *i = (_t) r; \ 172 | return 0; \ 173 | } \ 174 | return -1; \ 175 | } 176 | 177 | _DECL_STRTOX(i, i32, i32, INT_MIN, INT_MAX) 178 | _DECL_STRTOX(u, u32, u32, 0, UINT_MAX) 179 | _DECL_STRTOX(i, i16, i16, SHRT_MIN, SHRT_MAX) 180 | _DECL_STRTOX(u, u16, u16, 0, USHRT_MAX) 181 | _DECL_STRTOX(i, i8, i8, CHAR_MIN, CHAR_MAX) 182 | _DECL_STRTOX(u, u8, u8, 0, INT_MAX) 183 | 184 | static inline char escchar(char c) { 185 | switch (c) { 186 | case 'a': c = 0x07; break; 187 | case 'b': c = 0x08; break; 188 | case 'e': c = 0x1B; break; 189 | case 'f': c = 0x0C; break; 190 | case 'n': c = 0x0A; break; 191 | case 'r': c = 0x0D; break; 192 | case 't': c = 0x09; break; 193 | case 'v': c = 0x0B; break; 194 | case '\\': c = 0x5C; break; 195 | case '\'': c = 0x27; break; 196 | case '\"': c = 0x22; break; 197 | case '?': c = 0x3F; break; 198 | } 199 | return c; 200 | } 201 | 202 | // for fancy terminal output 203 | #define ANSI_RED "\x1b[31m" 204 | #define ANSI_GREEN "\x1b[32m" 205 | #define ANSI_YELLOW "\x1b[33m" 206 | #define ANSI_BLUE "\x1b[34m" 207 | #define ANSI_MAGENTA "\x1b[35m" 208 | #define ANSI_CYAN "\x1b[36m" 209 | #define ANSI_RESET "\x1b[0m" 210 | 211 | #define assert(_e) ( _assert((_e), false, __FILE__, __LINE__)) 212 | static inline void _assert(bool e, bool fatal, const char *file, usize line) { 213 | if (!e) { 214 | fprintf( 215 | stderr, 216 | ANSI_RED "Assertion failed. (%s:%" PRIu64 ")" ANSI_RESET "\n", 217 | file, 218 | line 219 | ); 220 | exit(1); 221 | } 222 | } 223 | 224 | static inline void warn(const char *fmt, ...) { 225 | va_list args; 226 | printf("%s", ANSI_YELLOW); 227 | va_start(args, fmt); 228 | vprintf(fmt, args); 229 | va_end(args); 230 | printf("%s", ANSI_RESET); 231 | } 232 | 233 | #endif 234 | -------------------------------------------------------------------------------- /emu/emu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #if defined(WIN32) || defined(_WIN32) 8 | #include "win/win.h" 9 | #else 10 | // POSIX specifics 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | #include "emu.h" 17 | #include "../common/jdh8util.h" 18 | 19 | // TODO: illegal instruction warnings 20 | 21 | static bool stop_simulation(struct JDH8 *state) { 22 | struct pollfd pfds = { .fd = fileno(stdin), .events = POLLIN }; 23 | 24 | if (poll(&pfds, 1, 0) > 0 && (pfds.events & POLLIN)) { 25 | char c; 26 | return read(fileno(stdin), &c, 1) > 0; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | static int run(struct JDH8 *state, const char *speed, char *out, usize n) { 33 | // un-halt 34 | state->status = BIT_SET(state->status, S_HALT, 0); 35 | 36 | u64 hz = 16384; 37 | 38 | if (speed != NULL) { 39 | if (!strcasecmp(speed, "realtime")) { 40 | hz = 100000000; // 100 MHz (fastest possible) 41 | } else if (!strcasecmp(speed, "fast")) { 42 | hz = 2000000; // 2 MHz 43 | } else if (!strcasecmp(speed, "normal")) { 44 | hz = 1000000; // 1 MHz 45 | } else if (!strcasecmp(speed, "slow")) { 46 | hz = 8000; // 8 KHz 47 | } else if (!strcasecmp(speed, "veryslow")) { 48 | hz = 16; // 0.015 KHz 49 | } else if (strtou64(speed, 0, &hz)) { 50 | return -1; 51 | } 52 | } 53 | 54 | // TODO: bad 55 | fprintf(stdout, "Press enter to stop simulation\n"); 56 | int res = simulate(state, hz, stop_simulation); 57 | 58 | switch (res) { 59 | case SIMULATE_ERROR: 60 | snprintf(out, n, "Error in simulation"); 61 | break; 62 | case SIMULATE_OK: 63 | snprintf(out, n, "Done"); 64 | break; 65 | case SIMULATE_STOP_HALT: 66 | snprintf(out, n, "Halted"); 67 | break; 68 | case SIMULATE_STOP_MANUAL: 69 | snprintf(out, n, "Stopped"); 70 | break; 71 | default: 72 | snprintf(out, n, "Unexpected simulation exit code"); 73 | break; 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | void command(struct JDH8 *state, const char *text, char *out, usize n) { 80 | #define cmdchk(_e, _s) do {\ 81 | if (!(_e)) {\ 82 | strncpy(out, (_s), n);\ 83 | return;\ 84 | }\ 85 | } while (0); 86 | 87 | #define WRONG_ARGS "Wrong number of arguments." 88 | #define WRONG_NUMBER "Malformatted/out of range number" 89 | 90 | char str[256], *token, *tokens[] = { NULL, NULL, NULL }; 91 | usize num_tokens = 0; 92 | 93 | // default to returning nothing 94 | if (n > 0) { 95 | *out = 0; 96 | } 97 | 98 | if (strlen(text) > sizeof(str) - 1) { 99 | strncpy(out, "Illegal command (too long).", n); 100 | return; 101 | } 102 | 103 | strlcpy(str, text, sizeof(str)); 104 | strupr(str); 105 | 106 | token = strtok(str, " "); 107 | while (token != NULL) { 108 | if (num_tokens >= 3) { 109 | strncpy(out, "Illegal command (too many tokens).", n); 110 | return; 111 | } 112 | 113 | tokens[num_tokens++] = token; 114 | token = strtok(NULL, " "); 115 | } 116 | 117 | if (num_tokens == 0) { 118 | strncpy(out, "Empty command", n); 119 | } else if (!strcmp("SET", tokens[0])) { 120 | cmdchk(num_tokens == 3, WRONG_ARGS); 121 | usize i; 122 | 123 | if ((i = strtab(R_NAMES, R_COUNT, tokens[1])) != (usize) -1) { 124 | u8 data; 125 | cmdchk(!strtou8(tokens[2], 0, &data), WRONG_NUMBER); 126 | state->registers.raw[i] = data; 127 | } else if ((i = strtab(SP_NAMES, SP_COUNT, tokens[1])) != (usize) -1) { 128 | u16 data; 129 | cmdchk(!strtou16(tokens[2], 0, &data), WRONG_NUMBER); 130 | state->special.raw[i] = data; 131 | } else { 132 | strncpy(out, "Unknown register", n); 133 | } 134 | } else if (!strcmp("GET", tokens[0])) { 135 | cmdchk(num_tokens == 2, WRONG_ARGS); 136 | usize i; 137 | 138 | if ((i = strtab(R_NAMES, R_COUNT, tokens[1])) != (usize) -1) { 139 | u8 data = state->registers.raw[i]; 140 | snprintf(out, n, "0x%02x (%d)\n", data, data); 141 | } else if ((i = strtab(SP_NAMES, SP_COUNT, tokens[1])) != (usize) -1) { 142 | u16 data = state->special.raw[i]; 143 | snprintf(out, n, "0x%04x (%d)\n", data, data); 144 | } else { 145 | strncpy(out, "Unknown register", n); 146 | } 147 | } else if (!strcmp("PEEK", tokens[0])) { 148 | cmdchk(num_tokens == 2, WRONG_ARGS); 149 | u16 addr; 150 | cmdchk(!strtou16(tokens[1], 0, &addr), WRONG_NUMBER); 151 | u8 data = peek(state, addr); 152 | snprintf(out, n, "0x%02x (%d)\n", data, data); 153 | } else if (!strcmp("POKE", tokens[0])) { 154 | cmdchk(num_tokens == 3, WRONG_ARGS); 155 | u16 addr; 156 | cmdchk(!strtou16(tokens[1], 0, &addr), WRONG_NUMBER); 157 | u8 data; 158 | cmdchk(!strtou8(tokens[2], 0, &data), WRONG_NUMBER); 159 | poke(state, addr, data); 160 | } else if (!strcmp("INB", tokens[0])) { 161 | cmdchk(num_tokens == 2, WRONG_ARGS); 162 | u8 port; 163 | cmdchk(!strtou8(tokens[1], 0, &port), WRONG_NUMBER); 164 | u8 data = inb(state, port); 165 | snprintf(out, n, "0x%02x (%d)\n", data, data); 166 | } else if (!strcmp("OUTB", tokens[0])) { 167 | cmdchk(num_tokens == 3, WRONG_ARGS); 168 | u8 port, data; 169 | cmdchk(!strtou8(tokens[1], 0, &port), WRONG_NUMBER); 170 | cmdchk(!strtou8(tokens[2], 0, &data), WRONG_NUMBER); 171 | outb(state, port, data); 172 | } else if (!strcmp("DUMP", tokens[0])) { 173 | cmdchk(num_tokens == 1, WRONG_ARGS); 174 | FILE *f = fmemopen((void *) out, n, "wb"); 175 | assert(f != NULL); 176 | dump(f, state); 177 | assert(!fclose(f)); 178 | } else if (!strcmp("STEP", tokens[0])) { 179 | cmdchk(num_tokens == 1, WRONG_ARGS); 180 | step(state); 181 | } else if (!strcmp("LOAD", tokens[0])) { 182 | cmdchk(num_tokens != 1, WRONG_ARGS); 183 | u16 addr = 0; 184 | 185 | if (num_tokens == 3) { 186 | cmdchk(!strtou16(tokens[2], 0, &addr), WRONG_NUMBER); 187 | } 188 | 189 | cmdchk(!load(state, tokens[1], addr), "Error loading file"); 190 | } else if (!strcmp("QUIT", tokens[0])) { 191 | cmdchk(num_tokens == 1, WRONG_ARGS); 192 | exit(0); 193 | } else if (!strcmp("MODS", tokens[0])) { 194 | cmdchk(num_tokens == 1, WRONG_ARGS); 195 | 196 | char buf[4096]; 197 | buf[0] = '\0'; 198 | 199 | const char *names[256]; 200 | usize num = mod_list(names, ARRLEN(names)); 201 | 202 | if (num == 0) { 203 | snprintf(out, n, "%s\n", "No modules"); 204 | } else { 205 | str[0] = '\0'; 206 | for (usize i = 0; i < num; i++) { 207 | if (i != 0) { 208 | strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); 209 | } 210 | 211 | strncat(buf, names[i], sizeof(buf) - strlen(buf) - 1); 212 | } 213 | 214 | snprintf(out, n, "Modules: %s\n", buf); 215 | } 216 | } else if (!strcmp("MOD", tokens[0])) { 217 | cmdchk(num_tokens == 2, WRONG_ARGS); 218 | cmdchk(!mod_load(state, tokens[1]), "Error loading module"); 219 | } else if (!strcmp("DEVICES", tokens[0])) { 220 | cmdchk(num_tokens == 1, WRONG_ARGS); 221 | 222 | for (usize i = 0; i < DEVICE_MAX; i++) { 223 | struct Device *dev = &state->devices[i]; 224 | 225 | if (dev->id == 0) { 226 | continue; 227 | } 228 | 229 | snprintf( 230 | out + strlen(out), 231 | n - strlen(out), 232 | "[%d] %s\n", 233 | dev->id, dev->name 234 | ); 235 | dev++; 236 | } 237 | } else if (!strcmp("RUN", tokens[0])) { 238 | cmdchk(num_tokens == 1 || num_tokens == 2, WRONG_ARGS); 239 | cmdchk( 240 | !run(state, num_tokens == 2 ? tokens[1] : NULL, out, n), 241 | "Invalid run speed" 242 | ); 243 | } else if (!strcmp("INST", tokens[0])) { 244 | cmdchk(num_tokens == 2, WRONG_ARGS); 245 | u16 addr; 246 | cmdchk(!strtou16(tokens[1], 0, &addr), WRONG_NUMBER); 247 | asm2str(out, n, ppeek(state, addr), 0x10000 - addr); 248 | } else { 249 | snprintf(out, n, "Unrecognized command %s\n", tokens[0]); 250 | } 251 | } 252 | 253 | int main(int argc, const char *argv[]) { 254 | struct JDH8 state; 255 | memset(&state, 0, sizeof(state)); 256 | 257 | // disallow writes to ROM 258 | state.write_protect = true; 259 | 260 | mod_init(); 261 | 262 | bool loaded = false; 263 | const char *run_speed = NULL; 264 | 265 | for (int i = 1; i < argc; i++) { 266 | if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mod")) { 267 | assert(i + 1 != argc); 268 | mod_load(&state, argv[++i]); 269 | } else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--run")) { 270 | assert(i + 1 != argc); 271 | run_speed = argv[++i]; 272 | } else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--load")) { 273 | assert(i + 2 < argc); 274 | u16 addr; 275 | assert(!strtou16(argv[i + 2], 0, &addr)); 276 | fprintf(stdout, "Loading ROM from %s at 0x%04x..\n", argv[i + 1], addr); 277 | load(&state, argv[i + 1], addr); 278 | loaded |= true; 279 | i += 2; 280 | } else { 281 | fprintf(stdout, "Loading ROM from %s...\n", argv[1]); 282 | load(&state, argv[1], 0); 283 | loaded |= true; 284 | } 285 | } 286 | 287 | if (!loaded) { 288 | fprintf(stdout, "%s\n", "No ROM loaded"); 289 | } 290 | 291 | while (true) { 292 | char out[8192]; 293 | 294 | if (run_speed) { 295 | run(&state, run_speed, out, sizeof(out)); 296 | run_speed = NULL; 297 | } else { 298 | char *buf = readline(">"); 299 | 300 | // EOF (^D) 301 | if (!buf) { 302 | exit(0); 303 | } 304 | 305 | buf[strcspn(buf, "\n")] = 0; 306 | add_history(buf); 307 | 308 | out[0] = '\0'; 309 | command(&state, buf, out, sizeof(out)); 310 | 311 | usize outlen = strlen(out); 312 | if (outlen != 0 && out[outlen - 1] != '\n') { 313 | out[outlen] = '\n'; 314 | out[outlen + 1] = '\0'; 315 | } 316 | 317 | fprintf(stdout, "%s", out); 318 | free(buf); 319 | } 320 | } 321 | 322 | return 0; 323 | } 324 | -------------------------------------------------------------------------------- /emu/emu.h: -------------------------------------------------------------------------------- 1 | #ifndef EMU_H 2 | #define EMU_H 3 | 4 | #include "../common/util.h" 5 | #include "../common/jdh8.h" 6 | 7 | // libemu.c 8 | #define SIMULATE_ERROR -1 9 | #define SIMULATE_OK 0 10 | #define SIMULATE_STOP_HALT 1 11 | #define SIMULATE_STOP_MANUAL 2 12 | 13 | int simulate(struct JDH8 *, u64, bool (*)(struct JDH8*)); 14 | void dump(FILE *f, struct JDH8 *state); 15 | 16 | #define has_device(_state, _id) ((_state)->devices[_id].id != 0) 17 | void add_device(struct JDH8*, struct Device); 18 | void remove_device(struct JDH8*, u8 id); 19 | void remove_devices(struct JDH8*); 20 | 21 | void *add_bank(struct JDH8 *, u8); 22 | void remove_bank(struct JDH8 *, u8); 23 | void push(struct JDH8*, u8); 24 | void push16(struct JDH8*, u16); 25 | u8 pop(struct JDH8*); 26 | u16 pop16(struct JDH8*); 27 | void interrupt(struct JDH8*, u8); 28 | u8 inb(struct JDH8*, u8); 29 | void outb(struct JDH8*, u8, u8); 30 | int load(struct JDH8* state, const char*, u16); 31 | bool halted(struct JDH8 *state); 32 | void step(struct JDH8*); 33 | 34 | // mod.c 35 | void mod_init(); 36 | usize mod_list(const char **names, usize n); 37 | int mod_load(struct JDH8 *state, const char *name); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /emu/kb.c: -------------------------------------------------------------------------------- 1 | #include "emu.h" 2 | 3 | static u8 data; 4 | 5 | static u8 kb_send(struct JDH8 *state, struct Device *dev) { 6 | return data; 7 | } 8 | 9 | // called from screen.c 10 | void kb_set_data(struct JDH8 *state, struct Device *dev, u8 d) { 11 | data = d; 12 | } 13 | 14 | void kb_init(struct JDH8 *state, struct Device *dev) { 15 | *dev = (struct Device) { 16 | .id = 2, 17 | .send = kb_send, 18 | .receive = NULL, 19 | .tick = NULL, 20 | .destroy = NULL 21 | }; 22 | strncpy(dev->name, "KEYBOARD", sizeof(dev->name)); 23 | } 24 | -------------------------------------------------------------------------------- /emu/mod.c: -------------------------------------------------------------------------------- 1 | #include "emu.h" 2 | 3 | struct Module { 4 | const char *name; 5 | void (*init)(struct JDH8*, struct Device*); 6 | }; 7 | 8 | #define MODULES_MAX 128 9 | static usize num_modules = 0; 10 | static struct Module modules[MODULES_MAX]; 11 | 12 | #define MOD_DECL(_name) do { \ 13 | extern void _name##_init(struct JDH8 *, struct Device*);\ 14 | modules[num_modules++] = (struct Module) { \ 15 | .name = #_name, \ 16 | .init = _name##_init \ 17 | }; \ 18 | } while (0); 19 | 20 | void mod_init() { 21 | MOD_DECL(screen); 22 | MOD_DECL(kb); 23 | } 24 | 25 | usize mod_list(const char **names, usize n) { 26 | usize i = 0; 27 | for (; i < num_modules && i < n; i++) { 28 | names[i] = modules[i].name; 29 | } 30 | return i; 31 | } 32 | 33 | int mod_load(struct JDH8 *state, const char *name) { 34 | usize i; 35 | struct Module *mod; 36 | 37 | for (i = 0; i < num_modules; i++) { 38 | mod = &modules[i]; 39 | 40 | if (!strcasecmp(name, mod->name)) { 41 | break; 42 | } 43 | } 44 | 45 | if (i == num_modules) { 46 | return -1; 47 | } 48 | 49 | struct Device dev; 50 | mod->init(state, &dev); 51 | add_device(state, dev); 52 | return 0; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /emu/screen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #if defined(WIN32) || defined(_WIN32) 6 | #include "win/win.h" 7 | #else 8 | #include 9 | #endif 10 | 11 | #include 12 | 13 | #include "emu.h" 14 | 15 | #define WINDOW_WIDTH 640 16 | #define WINDOW_HEIGHT 480 17 | 18 | #define SCANLINE_WIDTH 256 19 | #define SCREEN_WIDTH 208 20 | #define SCREEN_HEIGHT 240 21 | #define SCANLINE_OFFSET (4 * 8) 22 | 23 | // shared global 24 | static struct { 25 | u8 *bank; 26 | bool stop; 27 | } screen; 28 | 29 | static pthread_t child; 30 | static SDL_Window *window; 31 | static SDL_Renderer *renderer; 32 | static SDL_Texture *texture; 33 | 34 | static void screen_destroy(struct JDH8 *state, struct Device *dev) { 35 | screen.stop = true; 36 | pthread_join(child, NULL); 37 | SDL_DestroyTexture(texture); 38 | SDL_DestroyRenderer(renderer); 39 | SDL_DestroyWindow(window); 40 | SDL_Quit(); 41 | remove_bank(state, 1); 42 | } 43 | 44 | static u8 screen_send(struct JDH8 *state, struct Device *dev) { 45 | return 0; 46 | } 47 | 48 | static void screen_receive(struct JDH8 *state, struct Device *dev, u8 data) { 49 | 50 | } 51 | 52 | static void screen_tick(struct JDH8 *state, struct Device *dev) { 53 | if (screen.stop) { 54 | remove_device(state, dev->id); 55 | } else { 56 | // SDL events must be polled on main thread 57 | for (SDL_Event e; SDL_PollEvent(&e);) { 58 | // defined in kb.c 59 | extern void kb_set_data(struct JDH8*, struct Device*, u8); 60 | 61 | switch (e.type) { 62 | case SDL_KEYDOWN: 63 | case SDL_KEYUP: 64 | if (has_device(state, 2)) { 65 | kb_set_data( 66 | state, &state->devices[2], 67 | (e.type == SDL_KEYUP ? 0x80 : 0x00) | 68 | e.key.keysym.scancode 69 | ); 70 | } 71 | break; 72 | case SDL_QUIT: 73 | screen.stop = true; 74 | pthread_join(child, NULL); 75 | remove_device(state, dev->id); 76 | break; 77 | default: 78 | break; 79 | } 80 | } 81 | } 82 | } 83 | 84 | static void fchild(struct Device *dev) { 85 | u32 data[SCREEN_HEIGHT][SCREEN_WIDTH]; 86 | 87 | while (!screen.stop) { 88 | // convert memory bank into texture data 89 | for (usize y = 0; y < SCREEN_HEIGHT; y++) { 90 | for (usize x = 0; x < SCREEN_WIDTH; x++) { 91 | const usize i = 92 | (y * (SCANLINE_WIDTH / 8)) 93 | + ((x / 8) + (SCANLINE_OFFSET / 8)); 94 | data[y][x] = 95 | (((((u8 *) screen.bank)[i]) >> (x % 8)) & 0x01) 96 | ? 0xFFFFFFFF : 0x00000000; 97 | } 98 | } 99 | 100 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 101 | SDL_RenderClear(renderer); 102 | SDL_UpdateTexture(texture, NULL, data, SCREEN_WIDTH * 4); 103 | SDL_RenderCopy( 104 | renderer, texture, 105 | &((SDL_Rect) { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT }), 106 | &((SDL_Rect) { 107 | (WINDOW_WIDTH - (SCREEN_WIDTH * 2)) / 2, 108 | (WINDOW_HEIGHT - (SCREEN_HEIGHT * 2)) / 2, 109 | SCREEN_WIDTH * 2, 110 | SCREEN_HEIGHT * 2 })); 111 | SDL_RenderPresent(renderer); 112 | SDL_UpdateWindowSurface(window); 113 | 114 | // TODO: find a better framerate cap mechanism 115 | SDL_Delay(16); 116 | } 117 | } 118 | 119 | void screen_init(struct JDH8 *state, struct Device *dev) { 120 | *dev = (struct Device) { 121 | .id = 64, 122 | .send = screen_send, 123 | .receive = screen_receive, 124 | .tick = screen_tick, 125 | .destroy = screen_destroy 126 | }; 127 | strncpy(dev->name, "SCREEN", sizeof(dev->name)); 128 | 129 | screen.bank = add_bank(state, 1); 130 | screen.stop = false; 131 | 132 | assert(!SDL_Init(SDL_INIT_VIDEO)); 133 | window = SDL_CreateWindow( 134 | "JDH8", 135 | SDL_WINDOWPOS_UNDEFINED, 136 | SDL_WINDOWPOS_UNDEFINED, 137 | WINDOW_WIDTH, WINDOW_HEIGHT, 0 138 | ); 139 | assert(window); 140 | renderer = SDL_CreateRenderer( 141 | window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC 142 | ); 143 | assert(renderer); 144 | texture = SDL_CreateTexture( 145 | renderer, 146 | SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 147 | SCREEN_WIDTH, SCREEN_HEIGHT 148 | ); 149 | assert(texture); 150 | assert(!pthread_create(&child, NULL, (void* (*)(void*)) fchild, dev)); 151 | } 152 | -------------------------------------------------------------------------------- /emu/win/mman.c: -------------------------------------------------------------------------------- 1 | // from https://github.com/klauspost/mman-win32/blob/master/mman.c 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mman.h" 8 | 9 | #ifndef FILE_MAP_EXECUTE 10 | #define FILE_MAP_EXECUTE 0x0020 11 | #endif /* FILE_MAP_EXECUTE */ 12 | 13 | static int __map_mman_error(const DWORD err, const int deferr) 14 | { 15 | if (err == 0) 16 | return 0; 17 | //TODO: implement 18 | return err; 19 | } 20 | 21 | static DWORD __map_mmap_prot_page(const int prot) 22 | { 23 | DWORD protect = 0; 24 | 25 | if (prot == PROT_NONE) 26 | return protect; 27 | 28 | if ((prot & PROT_EXEC) != 0) 29 | { 30 | protect = ((prot & PROT_WRITE) != 0) ? 31 | PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 32 | } 33 | else 34 | { 35 | protect = ((prot & PROT_WRITE) != 0) ? 36 | PAGE_READWRITE : PAGE_READONLY; 37 | } 38 | 39 | return protect; 40 | } 41 | 42 | static DWORD __map_mmap_prot_file(const int prot) 43 | { 44 | DWORD desiredAccess = 0; 45 | 46 | if (prot == PROT_NONE) 47 | return desiredAccess; 48 | 49 | if ((prot & PROT_READ) != 0) 50 | desiredAccess |= FILE_MAP_READ; 51 | if ((prot & PROT_WRITE) != 0) 52 | desiredAccess |= FILE_MAP_WRITE; 53 | if ((prot & PROT_EXEC) != 0) 54 | desiredAccess |= FILE_MAP_EXECUTE; 55 | 56 | return desiredAccess; 57 | } 58 | 59 | void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) 60 | { 61 | HANDLE fm, h; 62 | 63 | void * map = MAP_FAILED; 64 | 65 | #ifdef _MSC_VER 66 | #pragma warning(push) 67 | #pragma warning(disable: 4293) 68 | #endif 69 | 70 | const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ? 71 | (DWORD)off : (DWORD)(off & 0xFFFFFFFFL); 72 | const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ? 73 | (DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL); 74 | const DWORD protect = __map_mmap_prot_page(prot); 75 | const DWORD desiredAccess = __map_mmap_prot_file(prot); 76 | 77 | const off_t maxSize = off + (off_t)len; 78 | 79 | const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ? 80 | (DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL); 81 | const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ? 82 | (DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); 83 | 84 | #ifdef _MSC_VER 85 | #pragma warning(pop) 86 | #endif 87 | 88 | errno = 0; 89 | 90 | if (len == 0 91 | /* Unsupported flag combinations */ 92 | || (flags & MAP_FIXED) != 0 93 | /* Usupported protection combinations */ 94 | || prot == PROT_EXEC) 95 | { 96 | errno = EINVAL; 97 | return MAP_FAILED; 98 | } 99 | 100 | h = ((flags & MAP_ANONYMOUS) == 0) ? 101 | (HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE; 102 | 103 | if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) 104 | { 105 | errno = EBADF; 106 | return MAP_FAILED; 107 | } 108 | 109 | fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); 110 | 111 | if (fm == NULL) 112 | { 113 | errno = __map_mman_error(GetLastError(), EPERM); 114 | return MAP_FAILED; 115 | } 116 | 117 | map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); 118 | 119 | CloseHandle(fm); 120 | 121 | if (map == NULL) 122 | { 123 | errno = __map_mman_error(GetLastError(), EPERM); 124 | return MAP_FAILED; 125 | } 126 | 127 | return map; 128 | } 129 | 130 | int munmap(void *addr, size_t len) 131 | { 132 | if (UnmapViewOfFile(addr)) 133 | return 0; 134 | 135 | errno = __map_mman_error(GetLastError(), EPERM); 136 | 137 | return -1; 138 | } 139 | 140 | int mprotect(void *addr, size_t len, int prot) 141 | { 142 | DWORD newProtect = __map_mmap_prot_page(prot); 143 | DWORD oldProtect = 0; 144 | 145 | if (VirtualProtect(addr, len, newProtect, &oldProtect)) 146 | return 0; 147 | 148 | errno = __map_mman_error(GetLastError(), EPERM); 149 | 150 | return -1; 151 | } 152 | 153 | int msync(void *addr, size_t len, int flags) 154 | { 155 | if (FlushViewOfFile(addr, len)) 156 | return 0; 157 | 158 | errno = __map_mman_error(GetLastError(), EPERM); 159 | 160 | return -1; 161 | } 162 | 163 | int mlock(const void *addr, size_t len) 164 | { 165 | if (VirtualLock((LPVOID)addr, len)) 166 | return 0; 167 | 168 | errno = __map_mman_error(GetLastError(), EPERM); 169 | 170 | return -1; 171 | } 172 | 173 | int munlock(const void *addr, size_t len) 174 | { 175 | if (VirtualUnlock((LPVOID)addr, len)) 176 | return 0; 177 | 178 | errno = __map_mman_error(GetLastError(), EPERM); 179 | 180 | return -1; 181 | } 182 | -------------------------------------------------------------------------------- /emu/win/mman.h: -------------------------------------------------------------------------------- 1 | // from https://github.com/klauspost/mman-win32/blob/master/mman.h 2 | 3 | #ifndef WIN_MMAN_H 4 | #define WIN_MMAN_H 5 | 6 | #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. 7 | #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. 8 | #endif 9 | 10 | /* All the headers include this file. */ 11 | #ifndef _MSC_VER 12 | #include <_mingw.h> 13 | #endif 14 | 15 | #include 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #define PROT_NONE 0 22 | #define PROT_READ 1 23 | #define PROT_WRITE 2 24 | #define PROT_EXEC 4 25 | 26 | #define MAP_FILE 0 27 | #define MAP_SHARED 1 28 | #define MAP_PRIVATE 2 29 | #define MAP_TYPE 0xf 30 | #define MAP_FIXED 0x10 31 | #define MAP_ANONYMOUS 0x20 32 | #define MAP_ANON MAP_ANONYMOUS 33 | 34 | #define MAP_FAILED ((void *)-1) 35 | 36 | /* Flags for msync. */ 37 | #define MS_ASYNC 1 38 | #define MS_SYNC 2 39 | #define MS_INVALIDATE 4 40 | 41 | void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); 42 | int munmap(void *addr, size_t len); 43 | int mprotect(void *addr, size_t len, int prot); 44 | int msync(void *addr, size_t len, int flags); 45 | int mlock(const void *addr, size_t len); 46 | int munlock(const void *addr, size_t len); 47 | 48 | #ifdef __cplusplus 49 | }; 50 | #endif 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /emu/win/win.c: -------------------------------------------------------------------------------- 1 | #include "win.h" 2 | #include "mman.h" 3 | 4 | // from https://narkive.com/oLyNbGSV 5 | int poll(struct pollfd *p, int num, int timeout) { 6 | struct timeval tv; 7 | fd_set read, write, except; 8 | int i, n, ret; 9 | 10 | FD_ZERO(&read); 11 | FD_ZERO(&write); 12 | FD_ZERO(&except); 13 | 14 | n = -1; 15 | for (i = 0; i < num; i++) { 16 | if (p[i].fd < 0) continue; 17 | if (p[i].events & POLLIN) FD_SET(p[i].fd, &read); 18 | if (p[i].events & POLLOUT) FD_SET(p[i].fd, &write); 19 | if (p[i].events & POLLERR) FD_SET(p[i].fd, &except); 20 | if (p[i].fd > n) n = p[i].fd; 21 | } 22 | 23 | if (n == -1) { 24 | return 0; 25 | } 26 | 27 | if (timeout < 0) { 28 | ret = select(n + 1, &read, &write, &except, NULL); 29 | } else { 30 | tv.tv_sec = timeout / 1000; 31 | tv.tv_usec = 1000 * (timeout % 1000); 32 | ret = select(n + 1, &read, &write, &except, &tv); 33 | } 34 | 35 | for (i = 0; ret >= 0 && i < num; i++) { 36 | p[i].revents = 0; 37 | if (FD_ISSET(p[i].fd, &read)) p[i].revents |= POLLIN; 38 | if (FD_ISSET(p[i].fd, &write)) p[i].revents |= POLLOUT; 39 | if (FD_ISSET(p[i].fd, &except)) p[i].revents |= POLLERR; 40 | } 41 | 42 | return ret; 43 | } 44 | // from https://github.com/Arryboom/fmemopen_windows/blob/master/libfmemopen.c 45 | 46 | FILE *fmemopen(void *buf, size_t len, const char *type) { 47 | int fd; 48 | FILE *fp; 49 | char tp[MAX_PATH - 13]; 50 | char fn[MAX_PATH + 1]; 51 | int * pfd = &fd; 52 | int retner = -1; 53 | char tfname[] = "MemTF_"; 54 | if (!GetTempPathA(sizeof(tp), tp)) 55 | return NULL; 56 | if (!GetTempFileNameA(tp, tfname, 0, fn)) 57 | return NULL; 58 | retner = _sopen_s( 59 | pfd, fn, 60 | _O_CREAT | _O_SHORT_LIVED | _O_TEMPORARY | _O_RDWR | _O_BINARY 61 | | _O_NOINHERIT, _SH_DENYRW, _S_IREAD | _S_IWRITE 62 | ); 63 | if (retner != 0) 64 | return NULL; 65 | if (fd == -1) 66 | return NULL; 67 | fp = _fdopen(fd, "wb+"); 68 | if (!fp) { 69 | _close(fd); 70 | return NULL; 71 | } 72 | /*File descriptors passed into _fdopen are owned by the returned FILE * stream.If _fdopen is successful, do not call _close on the file descriptor.Calling fclose on the returned FILE * also closes the file descriptor.*/ 73 | fwrite(buf, len, 1, fp); 74 | rewind(fp); 75 | return fp; 76 | } 77 | 78 | char* readline(char* prompt) { 79 | char* retval = NULL; 80 | printf("> "); 81 | scanf("%s", retval); 82 | return retval; 83 | } 84 | 85 | void add_history(const char* string) {} 86 | -------------------------------------------------------------------------------- /emu/win/win.h: -------------------------------------------------------------------------------- 1 | #ifndef _WIN_H_ 2 | #define _WIN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define POLLIN 0x0001 12 | #define POLLPRI 0x0002 /* not used */ 13 | #define POLLOUT 0x0004 14 | #define POLLERR 0x0008 15 | #define POLLHUP 0x0010 /* not used */ 16 | #define POLLNVAL 0x0020 /* not used */ 17 | 18 | struct pollfd { 19 | int fd; 20 | int events; /* in param: what to poll for */ 21 | int revents; /* out param: what events occured */ 22 | }; 23 | int poll (struct pollfd *p, int num, int timeout); 24 | 25 | FILE *fmemopen(void *buf, size_t len, const char *type); 26 | 27 | char* readline(char* prompt); 28 | void add_history(const char* string); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /images/BUILD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/images/BUILD.png -------------------------------------------------------------------------------- /images/PONG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/images/PONG.png -------------------------------------------------------------------------------- /misc/aep/aep.ino: -------------------------------------------------------------------------------- 1 | #define PIN_IO0 2 2 | #define PIN_IO1 3 3 | #define PIN_IO2 4 4 | #define PIN_IO3 5 5 | #define PIN_IO4 6 6 | #define PIN_IO5 7 7 | #define PIN_IO6 8 8 | #define PIN_IO7 9 9 | 10 | #define PIN_NWE 10 11 | 12 | #define PIN_SER 11 13 | #define PIN_RCLK 12 14 | #define PIN_SRCLK 13 15 | 16 | #define T_CLK 100 17 | #define T_WC 10000 18 | 19 | const int IO_PINS[8] = { 20 | PIN_IO0, PIN_IO1, PIN_IO2, PIN_IO3, 21 | PIN_IO4, PIN_IO5, PIN_IO6, PIN_IO7, 22 | }; 23 | 24 | enum RW { 25 | RW_READ, 26 | RW_WRITE 27 | }; 28 | 29 | uint8_t write_buffer[128]; 30 | 31 | void e_set_rw(RW mode) { 32 | for (int i = 0; i < 8; i++) { 33 | pinMode(IO_PINS[i], mode == RW_READ ? INPUT : OUTPUT); 34 | } 35 | } 36 | 37 | void e_io_write(uint8_t data) { 38 | for (int i = 0; i < 8; i++) { 39 | digitalWrite(IO_PINS[i], (data >> i) & 0x01); 40 | } 41 | } 42 | 43 | uint8_t e_io_read() { 44 | uint8_t res = 0; 45 | for (int i = 0; i < 8; i++) { 46 | res |= digitalRead(IO_PINS[i]) << i; 47 | } 48 | return res; 49 | } 50 | 51 | void e_addr(uint16_t addr, bool oe) { 52 | shiftOut(PIN_SER, PIN_SRCLK, MSBFIRST, (uint8_t) ((addr >> 8) | (((uint8_t) (!oe)) << 7))); 53 | shiftOut(PIN_SER, PIN_SRCLK, MSBFIRST, (uint8_t) (addr & 0xFF)); 54 | 55 | digitalWrite(PIN_RCLK, LOW); 56 | digitalWrite(PIN_RCLK, HIGH); 57 | digitalWrite(PIN_RCLK, LOW); 58 | } 59 | 60 | void e_write(uint16_t addr, uint8_t data) { 61 | e_set_rw(RW_WRITE); 62 | e_addr(addr, false); 63 | e_io_write(data); 64 | digitalWrite(PIN_NWE, LOW); 65 | delayMicroseconds(T_WC); 66 | digitalWrite(PIN_NWE, HIGH); 67 | delayMicroseconds(T_WC); 68 | } 69 | 70 | bool e_can_page_write(uint16_t addr, uint16_t len) { 71 | return (addr >> 6) == ((addr + len) >> 6); 72 | } 73 | 74 | void e_page_write(uint16_t addr, uint8_t *data, uint16_t len) { 75 | e_set_rw(RW_WRITE); 76 | 77 | for (int i = 0; i < len; i++) { 78 | e_addr(addr + i, false); 79 | e_io_write(data[i]); 80 | digitalWrite(PIN_NWE, LOW); 81 | delayMicroseconds(10); 82 | digitalWrite(PIN_NWE, HIGH); 83 | } 84 | 85 | delayMicroseconds(T_WC); 86 | } 87 | 88 | void e_write_multi(uint16_t addr, uint8_t *data, uint16_t len) { 89 | // if (e_can_page_write(addr, len)) { 90 | // e_page_write(addr, data, len); 91 | // } else { 92 | for (uint16_t i = 0; i < len; i++) { 93 | e_write(addr + i, data[i]); 94 | } 95 | // } 96 | } 97 | 98 | uint8_t e_read(uint16_t addr) { 99 | digitalWrite(PIN_NWE, HIGH); 100 | e_set_rw(RW_READ); 101 | e_addr(addr, true); 102 | delayMicroseconds(T_CLK); 103 | return e_io_read(); 104 | } 105 | 106 | void e_dump() { 107 | for (int i = 0; i < (1 << 12); i += 8) { 108 | uint8_t bytes[8]; 109 | for (int j = 0; j < 8; j++) { 110 | bytes[j] = e_read(i + j); 111 | } 112 | 113 | char buffer[256]; 114 | snprintf( 115 | buffer, 256, 116 | "%" PRIx8 " %" PRIx8 " %" PRIx8 " %" PRIx8 117 | " %" PRIx8 " %" PRIx8 " %" PRIx8 " %" PRIx8 "\r\n", 118 | bytes[0], bytes[1], bytes[2], bytes[3], 119 | bytes[4], bytes[5], bytes[6], bytes[7] 120 | ); 121 | Serial.print(buffer); 122 | } 123 | } 124 | 125 | void setup() { 126 | Serial.begin(9600); 127 | 128 | pinMode(PIN_NWE, OUTPUT); 129 | pinMode(PIN_SRCLK, OUTPUT); 130 | pinMode(PIN_RCLK, OUTPUT); 131 | pinMode(PIN_SER, OUTPUT); 132 | digitalWrite(PIN_NWE, HIGH); 133 | 134 | while (Serial.available()) { 135 | read_wait(); 136 | } 137 | 138 | // write startup code (0xAA) 139 | Serial.write(0xAA); 140 | } 141 | 142 | #define EOC() \ 143 | Serial.write((uint8_t) 0xFF); \ 144 | Serial.flush(); \ 145 | 146 | uint8_t read_wait() { 147 | int res; 148 | while ((res = Serial.read()) == -1); 149 | return (uint8_t) res; 150 | } 151 | 152 | void loop() { 153 | while (Serial.available() < 3) { 154 | } 155 | 156 | uint8_t cmd = read_wait(); 157 | uint16_t cmdlen = ((uint16_t) read_wait()) | (((uint16_t) read_wait()) << 8); 158 | 159 | // wait for at least cmdlen - 3 more bytes 160 | // while (Serial.available() < (cmdlen - 3)) { 161 | // delay(10); 162 | // } 163 | 164 | if (cmd == 1 /* READY */) { 165 | EOC(); 166 | } else if (cmd == 2 /* READ */) { 167 | uint16_t 168 | addr = ((uint16_t) read_wait()) | (((uint16_t) read_wait()) << 8), 169 | len = ((uint16_t) read_wait()) | (((uint16_t) read_wait()) << 8); 170 | 171 | while (len != 0) { 172 | Serial.write((uint8_t) e_read(addr++)); 173 | len--; 174 | } 175 | 176 | EOC(); 177 | } else if (cmd == 3 /* WRITE */) { 178 | uint16_t 179 | addr = ((uint16_t) read_wait()) | (((uint16_t) read_wait()) << 8), 180 | len = ((uint16_t) read_wait()) | (((uint16_t) read_wait()) << 8); 181 | 182 | uint16_t i = 0; 183 | 184 | while (i != len) { 185 | write_buffer[i++] = read_wait(); 186 | } 187 | 188 | e_write_multi(addr, (uint8_t *) write_buffer, len); 189 | 190 | EOC(); 191 | } else { 192 | EOC(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /misc/aep/aep.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import serial 3 | import readline 4 | 5 | import PIL 6 | from PIL import Image 7 | 8 | AEPC_READY = 1 9 | AEPC_READ = 2 10 | AEPC_WRITE = 3 11 | 12 | READY = 0xAA 13 | EOC = 0xFF 14 | 15 | if len(sys.argv) != 3: 16 | print('Not enough arguments.') 17 | print('usage: aep.py ') 18 | exit(1) 19 | 20 | se = serial.Serial( 21 | port=sys.argv[2], 22 | baudrate=int(sys.argv[1]), 23 | ) 24 | 25 | def se_read(): 26 | try: 27 | return se.read(1)[0] 28 | except KeyboardInterrupt: 29 | exit(1) 30 | except: 31 | print('Timeout :(') 32 | return 0 33 | 34 | def se_read16(): 35 | return se_read() | (se_read() << 8) 36 | 37 | def se_write(data): 38 | if se.write(bytes([data & 0xFF])) != 1: 39 | raise Exception() 40 | 41 | def se_write16(data): 42 | se_write((data >> 0) & 0xFF) 43 | se_write((data >> 8) & 0xFF) 44 | 45 | def rom_read(addr, length): 46 | data = [] 47 | se_write(AEPC_READ) 48 | se_write16(3 + 4) 49 | se_write16(addr) 50 | se_write16(length) 51 | 52 | for _ in range(0, length): 53 | data.append(se_read()) 54 | 55 | eoc = se_read() 56 | if eoc != EOC: 57 | raise Exception('Invalid EOC 0x{:02X}'.format(eoc)) 58 | 59 | return data 60 | 61 | def rom_write(addr, data): 62 | se_write(AEPC_WRITE) 63 | se_write16(3 + 4 + len(data)) 64 | se_write16(addr) 65 | se_write16(len(data)) 66 | 67 | for x in data: 68 | se_write(x) 69 | 70 | eoc = se_read() 71 | if eoc != EOC: 72 | raise Exception('Invalid EOC 0x{:02X}'.format(eoc)) 73 | 74 | def rom_write_chunked(addr, data, chunk_size=16): 75 | chunks = [data[x:x+chunk_size] for x in range(0, len(data), chunk_size)] 76 | 77 | for i, chunk in enumerate(chunks): 78 | base = addr + (i * chunk_size) 79 | last = base + len(chunk) - 1 80 | 81 | rom_write(base, chunk) 82 | yield (base, last) 83 | 84 | def rom_verify(addr, data, chunk_size=48): 85 | chunks = [data[x:x+chunk_size] for x in range(0, len(data), chunk_size)] 86 | 87 | for i, chunk in enumerate(chunks): 88 | base = addr + (i * chunk_size) 89 | last = base + len(chunk) - 1 90 | 91 | rom_chunk = rom_read(base, len(chunk)) 92 | for j, (a, b) in enumerate(zip(chunk, rom_chunk)): 93 | if a != b: 94 | raise Exception( 95 | 'Verification failure at 0x{:04X}: expected 0x{:02X}, got 0x{:02X}' 96 | .format(base + j, a, b)) 97 | 98 | yield (base, last) 99 | 100 | def rom_clear(): 101 | CLEAR_BLOCK = 48 102 | 103 | print('CLEARING...') 104 | for i in range(0, 0xFFFF, CLEAR_BLOCK): 105 | rom_write(i, [0xFF] * CLEAR_BLOCK) 106 | print(' 0x{:04X}'.format(i)) 107 | 108 | print('CHECKING...') 109 | for i in range(0, 0xFFFF, CLEAR_BLOCK): 110 | data = rom_read(i, CLEAR_BLOCK) 111 | if any([x != 0xFF for x in data]): 112 | print('Error in 0x{:04X} block'.format(i)) 113 | print(' 0x{:04X}'.format(i)) 114 | 115 | print('DONE') 116 | 117 | # wait for startup 118 | while se_read() != READY: 119 | pass 120 | 121 | while True: 122 | line = input('> ') 123 | tokens = line.split(' ') 124 | cmd = tokens[0].lower() 125 | 126 | if cmd == 'help': 127 | print(r''' 128 | > help : print this message 129 | > ping : checks that programmer is ready 130 | > read : read a single byte 131 | > read : read n bytes 132 | > write : write a single byte 133 | > write : writes a file at the specified address 134 | > image : writes an image at the specified address 135 | > clear : zeroes out entire ROM 136 | > quit : quit 137 | ''') 138 | elif cmd == 'quit': 139 | exit(0) 140 | elif cmd == 'clear': 141 | rom_clear() 142 | elif cmd == 'read': 143 | if len(tokens) == 1: 144 | print('Not enough arguments') 145 | continue 146 | 147 | try: 148 | addr = int(tokens[1], 0) 149 | except: 150 | print('Invalid address') 151 | continue 152 | 153 | n = 1 154 | 155 | if len(tokens) == 3: 156 | try: 157 | n = int(tokens[2], 0) 158 | except: 159 | print('Invalid length') 160 | continue 161 | 162 | # send command 163 | data = rom_read(addr, n) 164 | chunks = [data[x:x+8] for x in range(0, len(data), 8)] 165 | 166 | for chunk in chunks: 167 | for i, x in enumerate(chunk): 168 | sys.stdout.write('0x{:02X}{}'.format(x, ' ' if i != len(chunk) - 1 else '')) 169 | sys.stdout.write('\n') 170 | elif cmd == 'write': 171 | if len(tokens) == 1: 172 | print('Not enough arguments') 173 | continue 174 | 175 | try: 176 | addr = int(tokens[1], 0) 177 | except: 178 | print('Invalid address') 179 | continue 180 | 181 | byte, path = None, tokens[2] 182 | 183 | try: 184 | byte = int(tokens[2], 0) 185 | except: 186 | pass 187 | 188 | data = None 189 | 190 | if byte: 191 | data = [byte] 192 | else: 193 | try: 194 | with open(path, 'rb') as f: 195 | data = f.read() 196 | except Exception as e: 197 | print('Error opening file: {}'.format(e)) 198 | continue 199 | 200 | if addr + len(data) >= 2 ** 16: 201 | print('Too large to write') 202 | continue 203 | 204 | for base, last in rom_write_chunked(addr, data): 205 | sys.stdout.write('[0x{:04X}..0x{:04X}] ... OK\n'.format(base, last)) 206 | 207 | # verify 208 | if len(tokens) == 4 and tokens[3].lower() in ['true', '1', 't', 'y']: 209 | try: 210 | for base, last in rom_verify(addr, data): 211 | sys.stdout.write('[0x{:04X}..0x{:04X}] ... VERIFIED\n'.format(base, last)) 212 | except Exception as e: 213 | print(e) 214 | elif cmd == 'image': 215 | if len(tokens) != 3: 216 | print('\'image\' only accepts three arguments') 217 | continue 218 | 219 | try: 220 | addr = int(tokens[1], 0) 221 | except: 222 | print('Invalid address') 223 | continue 224 | 225 | im = Image.open(tokens[2]).convert('RGB') 226 | 227 | if im.size != (208, 240): 228 | print('Invalid image size, can only accept 208x240') 229 | continue 230 | 231 | pixels = list(im.getdata()) 232 | 233 | data = [] 234 | 235 | for j in range(0, 240): 236 | for i in range(0, 32): 237 | if i < 4 or i > 29: 238 | data.append(0x55) 239 | continue 240 | 241 | base = (j * 208) + ((i - 4) * 8) 242 | px = map(lambda p: p[0] != 0 or p[1] != 0 or p[2] != 0, pixels[base:base+8]) 243 | b = 0 244 | 245 | for k, x in enumerate(px): 246 | b |= x << k 247 | 248 | data.append(b) 249 | 250 | for base, last in rom_write_chunked(addr, data): 251 | sys.stdout.write('[0x{:04X}..0x{:04X}] ... OK\n'.format(base, last)) 252 | 253 | try: 254 | for base, last in rom_verify(addr, data): 255 | sys.stdout.write('[0x{:04X}..0x{:04X}] ... VERIFIED\n'.format(base, last)) 256 | except Exception as e: 257 | print(e) 258 | elif cmd == 'ping': 259 | se_write(AEPC_READY) 260 | se_write16(3) 261 | res = se_read() 262 | if res != EOC: 263 | print('Error, expected 0x{:02X} but got 0x{:02X}'.format(EOC, res)) 264 | exit(1) 265 | else: 266 | print('pong!') 267 | else: 268 | print('Unrecognized command {}, type \'help\' for help'.format(cmd)) 269 | -------------------------------------------------------------------------------- /misc/aep/old/Makefile: -------------------------------------------------------------------------------- 1 | CC=avr-gcc 2 | LD=avr-gcc 3 | 4 | CCFLAGS?=-O1 -DF_APU=16000000UL -mmcu=atmega328p 5 | LDFLAGS?=-mmcu=atmega328p 6 | 7 | ARDUINO_USB = /dev/cu.usbmodem141301 8 | 9 | all: out 10 | 11 | %.o: %.c 12 | $(CC) -o $@ -c $< $(CCFLAGS) 13 | 14 | out: main.o 15 | $(LD) $(CCFLAGS) $(LDFLAGS) -o $@ $^ 16 | 17 | deploy: out 18 | avrdude -F -V -c arduino -p ATMEGA328p -P $(ARDUINO_USB) -b 115200 -U flash:w:out 19 | 20 | clean: 21 | rm -f ./*.o out aep 22 | -------------------------------------------------------------------------------- /misc/aep/old/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #define F_CPU 16000000 12 | #define BAUD 9600 13 | 14 | #include 15 | #include 16 | 17 | struct Pin { 18 | volatile uint8_t *port, *ddr, *pin; 19 | uint8_t no; 20 | }; 21 | 22 | const struct Pin IO_PINS[] = { 23 | { &PORTD, &DDRD, &PIND, 2 }, 24 | { &PORTD, &DDRD, &PIND, 3 }, 25 | { &PORTD, &DDRD, &PIND, 4 }, 26 | { &PORTD, &DDRD, &PIND, 5 }, 27 | { &PORTD, &DDRD, &PIND, 6 }, 28 | { &PORTD, &DDRD, &PIND, 7 }, 29 | { &PORTB, &DDRB, &PINB, 0 }, 30 | { &PORTB, &DDRB, &PINB, 1 }, 31 | }; 32 | 33 | const struct Pin 34 | PIN_NWE = { &PORTB, &DDRB, &PINB, 2 }, 35 | PIN_SER = { &PORTB, &DDRB, &PINB, 3 }, 36 | PIN_RCLK = { &PORTB, &DDRB, &PINB, 4 }, 37 | PIN_SRCLK = { &PORTB, &DDRB, &PINB, 5 }, 38 | PIN_LED = { &PORTB, &DDRB, &PINB, 5 }; 39 | 40 | // clock cycle time 41 | #define T_CLK 100 42 | 43 | // write cycle time, max 44 | #define T_WC 10000 45 | 46 | enum RWMode { 47 | RW_READ, 48 | RW_WRITE 49 | }; 50 | 51 | enum PinMode { 52 | PM_INPUT, 53 | PM_OUTPUT 54 | }; 55 | 56 | static struct { 57 | char data[1024]; 58 | size_t i, len; 59 | } sbuf; 60 | 61 | static size_t se_cnt = 0; 62 | 63 | enum AEPCommand { 64 | // INPUT = { }, OUTPUT = { 0xFF } 65 | AEPC_READY = 1, 66 | 67 | // INPUT = { u16 addr, u16 len }, OUTPUT = { u8 data, 0xFF } 68 | AEPC_READ = 2, 69 | 70 | // INPUT = { u16 addr, u16 len, u8 data }, OUTPUT = { 0xFF } 71 | AEPC_WRITE = 3, 72 | }; 73 | 74 | static uint8_t write_buffer[64]; 75 | 76 | // shamelessly stolen from the arduino stdlib 77 | // github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/delay.c 78 | static inline void delay(size_t us) { 79 | _delay_us((double) us); 80 | } 81 | 82 | static inline void pin_mode(struct Pin pin, enum PinMode mode) { 83 | *pin.ddr = (*pin.ddr & ~(1 << pin.no)) | ((mode == PM_OUTPUT ? 1 : 0) << pin.no); 84 | } 85 | 86 | static inline uint8_t pin_read(struct Pin pin) { 87 | return (*pin.pin & (1 << pin.no)) >> pin.no; 88 | } 89 | 90 | static inline void pin_write(struct Pin pin, uint8_t data) { 91 | *pin.port = (*pin.port & ~(1 << pin.no)) | (!!data << pin.no); 92 | } 93 | 94 | void shift_out(struct Pin pin_data, struct Pin pin_clock, uint8_t data) { 95 | uint8_t mask = 0x80; 96 | 97 | #pragma GCC unroll 8 98 | for (uint8_t i = 0; i < 8; i++) { 99 | pin_write(pin_data, data & mask); 100 | pin_write(pin_clock, 1); 101 | pin_write(pin_clock, 0); 102 | mask >>= 1; 103 | } 104 | } 105 | 106 | static void se_init() { 107 | // values configured with F_CPU and BAUD 108 | UBRR0H = UBRRH_VALUE; 109 | UBRR0L = UBRRL_VALUE; 110 | UCSR0A &= ~_BV(U2X0); 111 | UCSR0B |= _BV(RXCIE0); // enable interrupt 112 | UCSR0B |= _BV(RXEN0) | _BV(TXEN0); // rx + tx 113 | UCSR0C |= _BV(UCSZ01) | _BV(UCSZ00); // set frame: 8 data, 1 stop 114 | 115 | // enable global interrupts 116 | sei(); 117 | } 118 | 119 | // UART ISR 120 | ISR(USART_RX_vect) { 121 | loop_until_bit_is_set(UCSR0A, UDRE0); 122 | char data = UDR0; 123 | 124 | if (sbuf.len == sizeof(sbuf.data)) { 125 | sbuf.i = (sbuf.i + 1) % sizeof(sbuf.data); 126 | sbuf.len--; 127 | } 128 | 129 | sbuf.data[(sbuf.i + sbuf.len) % sizeof(sbuf.data)] = data; 130 | sbuf.len++; 131 | 132 | se_cnt++; 133 | } 134 | 135 | static void se_writec(char c) { 136 | loop_until_bit_is_set(UCSR0A, UDRE0); 137 | UDR0 = c; 138 | } 139 | 140 | static int se_readc(char *out) { 141 | cli(); 142 | 143 | if (sbuf.len == 0) { 144 | sei(); 145 | return -1; 146 | } 147 | 148 | *out = sbuf.data[sbuf.i]; 149 | sbuf.i = (sbuf.i + 1) % sizeof(sbuf.data); 150 | sbuf.len--; 151 | 152 | sei(); 153 | return 0; 154 | } 155 | 156 | static void se_wait() { 157 | while (sbuf.len == 0) { 158 | delay(32768); 159 | } 160 | } 161 | 162 | static char se_wreadc() { 163 | se_wait(); 164 | char c; 165 | assert(!se_readc(&c)); 166 | return c; 167 | } 168 | 169 | static void se_writestr(const char *str) { 170 | while (*str) { 171 | se_writec(*str++); 172 | } 173 | } 174 | 175 | static void se_printf(const char *fmt, ...) { 176 | char buf[512]; 177 | va_list va_args; 178 | va_start(va_args, fmt); 179 | vsnprintf(buf, sizeof(buf), fmt, va_args); 180 | se_writestr(buf); 181 | va_end(va_args); 182 | } 183 | 184 | void e_set_rw(enum RWMode mode) { 185 | for (int i = 0; i < 8; i++) { 186 | pin_mode(IO_PINS[i], mode == RW_READ ? PM_INPUT : PM_OUTPUT); 187 | } 188 | } 189 | 190 | void e_io_write(uint8_t data) { 191 | for (int i = 0; i < 8; i++) { 192 | pin_write(IO_PINS[i], (data >> i) & 0x01); 193 | } 194 | } 195 | 196 | uint8_t e_io_read() { 197 | uint8_t res = 0; 198 | for (int i = 0; i < 8; i++) { 199 | res |= pin_read(IO_PINS[i]) << i; 200 | } 201 | return res; 202 | } 203 | 204 | void e_addr(uint16_t addr, bool oe) { 205 | shift_out(PIN_SER, PIN_SRCLK, (uint8_t) ((addr >> 8) | (((uint8_t) (!oe)) << 7))); 206 | shift_out(PIN_SER, PIN_SRCLK, (uint8_t) (addr & 0xFF)); 207 | 208 | pin_write(PIN_RCLK, 0); 209 | pin_write(PIN_RCLK, 1); 210 | pin_write(PIN_RCLK, 0); 211 | } 212 | 213 | void e_write(uint16_t addr, uint8_t data) { 214 | e_set_rw(RW_WRITE); 215 | e_addr(addr, false); 216 | e_io_write(data); 217 | pin_write(PIN_NWE, 0); 218 | delay(1); 219 | pin_write(PIN_NWE, 1); 220 | delay(T_WC); 221 | 222 | // spin while write completes 223 | uint32_t n = 0; 224 | do { 225 | e_set_rw(RW_READ); 226 | e_addr(addr, true); 227 | n++; 228 | } while (e_io_read() != data); 229 | } 230 | 231 | bool e_can_write_page(uint16_t addr, uint16_t len) { 232 | return (addr & 0xFFA0) == ((addr + len) & 0xFFA0); 233 | } 234 | 235 | void e_write_page(uint16_t addr, uint8_t *data, uint16_t len) { 236 | e_set_rw(RW_WRITE); 237 | 238 | uint16_t i = 0; 239 | while (i != len) { 240 | e_addr(addr, false); 241 | e_io_write(data[i]); 242 | pin_write(PIN_NWE, 0); 243 | delay(1); 244 | pin_write(PIN_NWE, 1); 245 | delay(1); 246 | 247 | addr++; 248 | i++; 249 | } 250 | 251 | delay(T_WC); 252 | } 253 | 254 | void e_write_block(uint16_t addr, uint8_t *data, uint16_t len) { 255 | if (e_can_write_page(addr, len)) { 256 | e_write_page(addr, data, len); 257 | } else { 258 | uint16_t i = 0; 259 | while (i != len) { 260 | e_write(addr, data[i]); 261 | addr++; 262 | i++; 263 | } 264 | } 265 | } 266 | 267 | uint8_t e_read(uint16_t addr) { 268 | e_set_rw(RW_READ); 269 | e_addr(addr, true); 270 | pin_write(PIN_NWE, 1); 271 | return e_io_read(); 272 | } 273 | 274 | void e_dump() { 275 | for (int i = 0; i < (1 << 12); i += 8) { 276 | uint8_t bytes[8]; 277 | for (int j = 0; j < 8; j++) { 278 | bytes[j] = e_read(i + j); 279 | } 280 | 281 | se_printf( 282 | "%" PRIx8 " %" PRIx8 " %" PRIx8 " %" PRIx8 283 | " %" PRIx8 " %" PRIx8 " %" PRIx8 " %" PRIx8 "\r\n", 284 | bytes[0], bytes[1], bytes[2], bytes[3], 285 | bytes[4], bytes[5], bytes[6], bytes[7] 286 | ); 287 | } 288 | } 289 | 290 | static void exec_command(enum AEPCommand cmd, uint16_t cmdlen) { 291 | uint16_t addr, len; 292 | 293 | switch (cmd) { 294 | case AEPC_READY: 295 | se_writec(0xFF); 296 | break; 297 | case AEPC_READ: 298 | case AEPC_WRITE: 299 | addr = ((uint16_t) se_wreadc()) | (((uint16_t) se_wreadc()) << 8); 300 | len = ((uint16_t) se_wreadc()) | (((uint16_t) se_wreadc()) << 8); 301 | 302 | if (cmd == AEPC_READ) { 303 | while (len != 0) { 304 | se_writec(e_read(addr++)); 305 | len--; 306 | } 307 | } else { 308 | uint16_t chunk = 0, total = 0; 309 | 310 | while (total != len) { 311 | while (chunk < sizeof(write_buffer) && (chunk + total != len)) { 312 | write_buffer[chunk++] = se_wreadc(); 313 | } 314 | 315 | e_write_block(addr + total, write_buffer, chunk); 316 | 317 | total += chunk; 318 | chunk = 0; 319 | } 320 | } 321 | 322 | se_writec(0xFF); 323 | break; 324 | default: 325 | se_writec(0xEE); 326 | } 327 | 328 | // check and reset serial read counter 329 | assert(se_cnt == cmdlen); 330 | se_cnt = 0; 331 | } 332 | 333 | void main() { 334 | se_init(); 335 | 336 | pin_mode(PIN_NWE, PM_OUTPUT); 337 | pin_mode(PIN_SRCLK, PM_OUTPUT); 338 | pin_mode(PIN_RCLK, PM_OUTPUT); 339 | pin_mode(PIN_SER, PM_OUTPUT); 340 | pin_write(PIN_NWE, 1); 341 | 342 | // write startup code 343 | se_writec(0xAA); 344 | 345 | while (true) { 346 | enum AEPCommand cmd = se_wreadc(); 347 | uint16_t len = ((uint16_t) se_wreadc()) | (((uint16_t) se_wreadc()) << 8); 348 | exec_command(cmd, len); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /misc/avt/Makefile: -------------------------------------------------------------------------------- 1 | CC=avr-gcc 2 | LD=avr-gcc 3 | 4 | CCFLAGS?=-Os -DF_APU=16000000UL -mmcu=atmega328p 5 | LDFLAGS?=-mmcu=atmega328p 6 | 7 | ARDUINO_USB = /dev/cu.usbmodem141201 8 | 9 | SRCS=$(wildcard ./*.c) 10 | OBJS=$(SRCS:.c=.o) 11 | 12 | all: out 13 | 14 | %.o: %.c 15 | $(CC) -o $@ -c $< $(CCFLAGS) 16 | 17 | out: $(OBJS) 18 | $(LD) $(LDFLAGS) -o $@ $^ 19 | 20 | deploy: out 21 | avrdude -F -V -c arduino -p ATMEGA328p -P $(ARDUINO_USB) -b 115200 -U flash:w:out 22 | 23 | clean: 24 | rm -f ./*.o out 25 | -------------------------------------------------------------------------------- /misc/avt/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define F_CPU 16000000 10 | #define BAUD 9600 11 | 12 | #include 13 | #include 14 | 15 | // pins 16 | #define PORT_VID PORTD 17 | #define DDR_VID DDRD 18 | #define PIN_VID 7 19 | 20 | #define PORT_SYN PORTB 21 | #define DDR_SYN DDRB 22 | #define PIN_SYN 1 23 | 24 | #define PORT_LED PORTB 25 | #define DDR_LED DDRB 26 | #define PIN_LED 5 27 | 28 | #define NS_PER_TICK 250 29 | #define NS_PER_US 1000 30 | #define TICKS_PER_US (NS_PER_US / NS_PER_TICK) 31 | 32 | #define CYCLES_PER_SECOND F_CPU 33 | #define CYCLES_PER_MS (CYCLES_PER_SECOND / 1000) 34 | #define CYCLES_PER_US (CYCLES_PER_MS / 1000) 35 | #define CYCLES_PER_TICK (CYCLES_PER_US / TICKS_PER_US) 36 | 37 | #define NOP() _NOP() 38 | 39 | #define DELAY_TCNT(n) do { \ 40 | cli(); \ 41 | uint16_t _dts = TCNT1; \ 42 | while ((TCNT1 - _dts) < (n - 4)) { } \ 43 | sei(); \ 44 | } while (0); 45 | 46 | #define DELAY_CYCLE() NOP() 47 | #define DELAY_125NS() DELAY_CYCLE(); DELAY_CYCLE() 48 | #define DELAY_250NS() DELAY_125NS(); DELAY_125NS() 49 | #define DELAY_500NS() DELAY_250NS(); DELAY_250NS() 50 | #define DELAY_750NS() DELAY_500NS(); DELAY_250NS() 51 | #define DELAY_1000NS() DELAY_500NS(); DELAY_500NS() 52 | #define DELAY_1US() DELAY_1000NS() 53 | #define DELAY_2US() DELAY_1US(); DELAY_1US() 54 | #define DELAY_5US() DELAY_2US(); DELAY_2US(); DELAY_1US() 55 | #define DELAY_10US() DELAY_5US(); DELAY_5US() 56 | #define DELAY_20US() DELAY_10US(); DELAY_10US() 57 | 58 | static void seinit() { 59 | UBRR0H = UBRRH_VALUE; 60 | UBRR0L = UBRRL_VALUE; 61 | UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); 62 | UCSR0B = _BV(TXEN0); 63 | } 64 | 65 | static void sewritec(char c) { 66 | loop_until_bit_is_set(UCSR0A, UDRE0); 67 | UDR0 = c; 68 | } 69 | 70 | static void sewritestr(const char *str) { 71 | while (*str) { 72 | sewritec(*str++); 73 | } 74 | } 75 | 76 | static void seprintf(const char *fmt, ...) { 77 | char buf[512]; 78 | va_list va_args; 79 | va_start(va_args, fmt); 80 | vsnprintf(buf, sizeof(buf), fmt, va_args); 81 | sewritestr(buf); 82 | va_end(va_args); 83 | } 84 | 85 | /* 86 | * NOTES: 87 | * - Takes 2 cycles to set a port 88 | * - One cycle @ 16 MHz = 62.5 ns (4 cycles = 250ns = 1 tick) 89 | */ 90 | 91 | 92 | int main(void) { 93 | seprintf("%s\r\n", "init again"); 94 | seinit(); 95 | 96 | // set timer1 to normal mode at F_CPU 97 | TCCR1A = 0; 98 | TCCR1B = 1; 99 | 100 | DDR_LED |= (1 << PIN_LED); 101 | DDR_VID |= (1 << PIN_VID); 102 | DDR_SYN |= (1 << PIN_SYN); 103 | 104 | volatile uint16_t hsc = 0; 105 | 106 | // 28 us @ 0.0v + 4 us @ 0.3v 107 | #define SYN_SHORT_MINUS_2US() \ 108 | PORT_SYN &= ~(1 << PIN_SYN); DELAY_CYCLE(); \ 109 | DELAY_20US(); \ 110 | DELAY_5US(); \ 111 | DELAY_2US(); \ 112 | DELAY_750NS(); \ 113 | DELAY_CYCLE(); \ 114 | PORT_SYN |= (1 << PIN_SYN); \ 115 | DELAY_1US(); \ 116 | DELAY_750NS(); \ 117 | DELAY_CYCLE(); 118 | 119 | #define SYN_SHORT() \ 120 | SYN_SHORT_MINUS_2US(); \ 121 | DELAY_2US(); 122 | 123 | // 2 us @ 0.0v + 30 us @ 0.3v 124 | #define SYN_BROAD_MINUS_2US() \ 125 | PORT_SYN &= ~(1 << PIN_SYN); DELAY_CYCLE(); \ 126 | DELAY_1US(); \ 127 | DELAY_750NS(); \ 128 | DELAY_CYCLE(); \ 129 | PORT_SYN |= (1 << PIN_SYN); \ 130 | DELAY_20US(); \ 131 | DELAY_5US(); \ 132 | DELAY_2US(); \ 133 | DELAY_750NS(); \ 134 | DELAY_CYCLE(); 135 | 136 | #define SYN_BROAD() \ 137 | SYN_BROAD_MINUS_2US(); \ 138 | DELAY_2US(); 139 | 140 | // 2 us = 32 cycles 141 | #define VID_FP() \ 142 | PORT_SYN |= (1 << PIN_SYN); 143 | 144 | // 4 us = 64 cycles 145 | #define VID_SYNC() \ 146 | PORT_SYN &= ~(1 << PIN_SYN);\ 147 | DELAY_125NS(); \ 148 | DELAY_750NS(); \ 149 | DELAY_1US(); \ 150 | DELAY_2US(); 151 | 152 | // 6 us = 96 cycles 153 | #define VID_BP() \ 154 | PORT_SYN |= (1 << PIN_SYN); \ 155 | DELAY_CYCLE(); \ 156 | DELAY_750NS(); \ 157 | DELAY_5US(); 158 | 159 | #define VID_LINE(_op0, _op1) \ 160 | VID_SYNC(); \ 161 | VID_BP(); \ 162 | _op0; \ 163 | DELAY_1US(); \ 164 | DELAY_750NS(); \ 165 | DELAY_20US(); \ 166 | DELAY_20US(); \ 167 | DELAY_10US(); \ 168 | _op1; \ 169 | VID_FP(); 170 | 171 | static const void *JMP_TABLE[640] = { 172 | [0 ... 4] = &&sig_eq, 173 | [5 ... 9] = &&sig_post, 174 | [10 ... 83] = &&sig_empty, 175 | [84 ... 563] = &&sig_data, 176 | [564 ... 617] = &&sig_empty, 177 | [618 ... 624] = &&sig_pre, 178 | [625 ... 639] = &&sig_reset 179 | }; 180 | 181 | uint8_t data[26] = { [0 ... 25] = 0xEA }; 182 | 183 | #define PIXEL(_d, _j) \ 184 | PORT_VID = (_d & (1 << _j)) == 0 ? \ 185 | ({ DELAY_CYCLE(); (PORT_VID & ~(1 << PIN_VID)); }) :\ 186 | (PORT_VID | (1 << PIN_VID)); 187 | 188 | // 1 us = 1000 ns = 16 cycles 189 | // total overhead from each jump is 32 cycles = 2 us 190 | sig_reset: 191 | hsc = 0; 192 | sig_eq: 193 | // 2560 cycles 194 | // 5 eq pulses 195 | SYN_SHORT(); 196 | SYN_SHORT(); 197 | SYN_SHORT(); 198 | SYN_SHORT(); 199 | SYN_SHORT_MINUS_2US(); 200 | hsc += 5; 201 | goto *JMP_TABLE[hsc]; 202 | sig_post: 203 | // 2560 cycles 204 | // 5 post eq pulses 205 | SYN_BROAD(); 206 | SYN_BROAD(); 207 | SYN_BROAD(); 208 | SYN_BROAD(); 209 | SYN_BROAD_MINUS_2US(); 210 | hsc += 5; 211 | goto *JMP_TABLE[hsc]; 212 | sig_empty: 213 | VID_LINE( 214 | PORT_VID &= ~(1 << PIN_VID); DELAY_CYCLE(), 215 | PORT_VID &= ~(1 << PIN_VID); DELAY_CYCLE() 216 | ); 217 | hsc += 2; 218 | goto *JMP_TABLE[hsc]; 219 | sig_data: 220 | // data 221 | VID_SYNC(); 222 | VID_BP(); 223 | 224 | DELAY_1US(); 225 | DELAY_2US(); 226 | 227 | #pragma GCC unroll 26 228 | for (uint8_t i = 0; i < 26; i++) { 229 | uint8_t d = data[i]; 230 | PORT_VID &= ~(1 << PIN_VID); 231 | 232 | #pragma GCC unroll 8 233 | for (uint8_t j = 0; j < 8; j++) { 234 | PIXEL(d, j); 235 | DELAY_CYCLE(); 236 | } 237 | } 238 | PORT_VID &= ~(1 << PIN_VID); DELAY_CYCLE(); 239 | DELAY_1US(); 240 | DELAY_750NS(); 241 | DELAY_CYCLE(); 242 | VID_FP(); 243 | hsc += 2; 244 | goto *JMP_TABLE[hsc]; 245 | sig_pre: 246 | // pre 247 | SYN_BROAD(); 248 | SYN_BROAD(); 249 | SYN_BROAD(); 250 | SYN_BROAD(); 251 | SYN_BROAD(); 252 | SYN_SHORT_MINUS_2US(); 253 | hsc += 6; 254 | goto *JMP_TABLE[hsc]; 255 | } 256 | -------------------------------------------------------------------------------- /misc/chk/gpuchk.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | INPUT_FILE = sys.argv[1] 4 | 5 | data = [] 6 | correct = [] 7 | 8 | # short pulse: 28us @ 0.0v; 4us @ 0.3v 9 | SHORT = [] 10 | SHORT.extend([(0, 0)] * (28 * 4)) 11 | SHORT.extend([(1, 0)] * (4 * 4)) 12 | 13 | # broad pulse: 2us @ 0.0v; 30us @ 0.3v 14 | BROAD = [] 15 | BROAD.extend([(0, 0)] * (2 * 4)) 16 | BROAD.extend([(1, 0)] * (30 * 4)) 17 | 18 | # scanline: 4us @ 0.0v (hsync); 6us @ 0.3v (back porch); 19 | # 40us data @ 0.3v or 1.0v; 2us @ 0.3v (front porch) 20 | LINE_BLACK, LINE_WHITE = [], [] 21 | 22 | for l in [LINE_BLACK, LINE_WHITE]: 23 | l.extend([(0, 0)] * (4 * 4)) 24 | l.extend([(1, 0)] * (6 * 4)) 25 | l.extend([(1, 1 if l is LINE_WHITE else 0)] * (52 * 4)) 26 | l.extend([(1, 0)] * (2 * 4)) 27 | 28 | CORRECT_DEF = [ 29 | (5, SHORT), 30 | (5, BROAD), 31 | (37, LINE_BLACK), 32 | (240, LINE_WHITE), 33 | (27, LINE_BLACK), 34 | (5, BROAD) 35 | ] 36 | 37 | for n, l in CORRECT_DEF: 38 | correct.extend(l * n) 39 | 40 | print(len(correct)) 41 | 42 | correct = list(map(lambda e: (e[0],) + e[1], enumerate(correct))) 43 | 44 | with open('correct.txt', 'wb+') as f: 45 | for (a, b, c) in correct: 46 | f.write('{} {} {}\n'.format(a, b, c).encode('utf-8')) 47 | 48 | with open(INPUT_FILE) as f: 49 | lines = list(enumerate(f)) 50 | 51 | while not lines[0][1].startswith('0 '): 52 | print('Skipping line \"{}\"'.format(lines[0][1].replace('\n', ''))) 53 | lines = lines[1:] 54 | 55 | j = 0 56 | 57 | for i, l in lines: 58 | assert int(l.split(' ')[0]) == j, 'Error on line {}'.format(i) 59 | data.append(tuple(map(lambda s: int(s.strip()), l.split(' ')))) 60 | j += 1 61 | 62 | # compare 63 | for i, (exp, act) in enumerate(zip(correct, data)): 64 | if exp != act: 65 | print('ERROR: {} did not match {}, got {}.\r'.format(i, exp, act)) 66 | 67 | for j in range(max(i - 5, 0), min(i + 5, min(len(correct), len(data)))): 68 | print(' {} {}{}\r'.format(correct[j], data[j], ' < HERE' if j == i else '')) 69 | 70 | exit(1) 71 | 72 | print('No errors!') 73 | exit(0) 74 | -------------------------------------------------------------------------------- /misc/images/gfx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/misc/images/gfx.png -------------------------------------------------------------------------------- /misc/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/misc/images/image.png -------------------------------------------------------------------------------- /misc/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/misc/images/image2.png -------------------------------------------------------------------------------- /misc/images/t4w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/misc/images/t4w.png -------------------------------------------------------------------------------- /misc/images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/misc/images/test.png -------------------------------------------------------------------------------- /misc/imc/imc.py: -------------------------------------------------------------------------------- 1 | # converts an image file into JDH-8 assembly syntax'd @db statements 2 | 3 | import sys 4 | from PIL import Image 5 | 6 | def convert(path, label): 7 | im = Image.open(path).convert('RGB') 8 | 9 | if im.size != (208, 240): 10 | raise Exception('Invalid image size, can only accept 208x240') 11 | 12 | pixels = list(im.getdata()) 13 | data = [] 14 | 15 | for j in range(0, 240): 16 | for i in range(0, 32): 17 | if i < 4 or i > 29: 18 | data.append(0) 19 | else: 20 | base = (j * 208) + ((i - 4) * 8) 21 | px = map(lambda p: p[0] != 0 or p[1] != 0 or p[2] != 0, 22 | pixels[base:base+8]) 23 | b = 0 24 | 25 | for k, x in enumerate(px): 26 | b |= x << k 27 | 28 | data.append(b) 29 | 30 | lines = [data[x:x+16] for x in range(0, len(data), 16)] 31 | lines = map(lambda l: map(lambda x: '0x%02x' % x, l), lines) 32 | lines = map(lambda l: '@db ' + ', '.join(l), lines) 33 | lines = '\n'.join(lines) 34 | 35 | return f'''; GENERATED BY JDH-8 IMAGE CONVERTER imc.py 36 | ; {path} 37 | {label}: 38 | {lines} 39 | 40 | ; END OF IMAGE 41 | ''' 42 | 43 | if __name__ == '__main__': 44 | path, label = '', 'image' 45 | 46 | if len(sys.argv) == 2: 47 | path = sys.argv[1] 48 | print('No label provided, using \'image\'', file=sys.stderr) 49 | elif len(sys.argv) == 3: 50 | path = sys.argv[2] 51 | label = sys.argv[1] 52 | else: 53 | print('usage: inc.py [label] [image file]') 54 | exit(1) 55 | 56 | try: 57 | print(convert(path, label), end='', file=sys.stdout) 58 | except Exception as e: 59 | print(f'Error: {str(e)}', file=sys.stderr) 60 | -------------------------------------------------------------------------------- /misc/tests/test.asm: -------------------------------------------------------------------------------- 1 | ; JDH-8 BASIC HARDWARE TEST 2 | ; NO OS DEPENDENCIES 3 | 4 | @include "os/arch.asm" 5 | 6 | @org 0x0000 7 | 8 | main: 9 | sw [ADDR_MB], 1 10 | lda a, b, [(ADDR_BANK + 8)] 11 | mw c, 0 12 | .loop: 13 | ; [hl] = [data + c] 14 | lda [data] 15 | add16 h, l, c 16 | 17 | ; d <- data 18 | lw d 19 | 20 | ; [hl] = [ab] <- d 21 | lda a, b 22 | sw d 23 | 24 | ; next scanline 25 | add16 a, b, SCANLINE_WIDTH_BYTES 26 | inc c 27 | jne c, 24, [.loop] 28 | .done: 29 | jmp [.done] 30 | 31 | data: 32 | @db 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00 33 | @db 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 34 | @db 0x00, 0x66, 0x66, 0x00, 0x81, 0xE7, 0x3C, 0x00 35 | 36 | -------------------------------------------------------------------------------- /misc/tests/test2.asm: -------------------------------------------------------------------------------- 1 | @org 0x0000 2 | 3 | @include "os/arch.asm" 4 | 5 | @define SEED 0x8000 6 | @define OFFSET 0x8001 7 | 8 | sw [ADDR_MB], 0x01 9 | lda a, b, [0x0000] 10 | 11 | loop: 12 | lda [image] 13 | add16 h, l, a, b 14 | lw c 15 | lda [ADDR_BANK] 16 | add16 h, l, a, b 17 | sw c 18 | add16 a, b, 0x01 19 | eq16 a, b, (SCANLINE_WIDTH_BYTES * SCREEN_HEIGHT) 20 | jne [loop] 21 | 22 | ; fib 23 | fib: 24 | mw a, 0 25 | mw b, 1 26 | mw c, 9 27 | .loop3: 28 | mw d, a 29 | add d, b 30 | mw a, b 31 | mw b, d 32 | clb 33 | sbb c, 1 34 | jnz c, [.loop3] 35 | 36 | ; result 37 | sw [(ADDR_BANK + (SCANLINE_WIDTH_BYTES * 16) + 16)], d 38 | 39 | spin: 40 | jmp [spin] 41 | 42 | @include "programs/holiday/image.asm" 43 | -------------------------------------------------------------------------------- /misc/tests/test3.asm: -------------------------------------------------------------------------------- 1 | @org 0x0000 2 | 3 | sw [0xFFFA], 0x01 4 | 5 | lda [data] 6 | 7 | lw f 8 | sw [(0x8008 + (32 * 0))], f 9 | add l, 1 10 | 11 | lw f 12 | sw [(0x8008 + (32 * 1))], f 13 | add l, 1 14 | 15 | lw f 16 | sw [(0x8008 + (32 * 2))], f 17 | add l, 1 18 | 19 | lw f 20 | sw [(0x8008 + (32 * 3))], f 21 | add l, 1 22 | 23 | lw f 24 | sw [(0x8008 + (32 * 4))], f 25 | add l, 1 26 | 27 | lw f 28 | sw [(0x8008 + (32 * 5))], f 29 | add l, 1 30 | 31 | lw f 32 | sw [(0x8008 + (32 * 6))], f 33 | add l, 1 34 | 35 | lw f 36 | sw [(0x8008 + (32 * 7))], f 37 | add l, 1 38 | 39 | lw f 40 | sw [(0x8008 + (32 * 8))], f 41 | add l, 1 42 | 43 | lw f 44 | sw [(0x8008 + (32 * 9))], f 45 | add l, 1 46 | 47 | lw f 48 | sw [(0x8008 + (32 * 10))], f 49 | add l, 1 50 | 51 | lw f 52 | sw [(0x8008 + (32 * 11))], f 53 | add l, 1 54 | 55 | lw f 56 | sw [(0x8008 + (32 * 12))], f 57 | add l, 1 58 | 59 | lw f 60 | sw [(0x8008 + (32 * 13))], f 61 | add l, 1 62 | 63 | lw f 64 | sw [(0x8008 + (32 * 14))], f 65 | add l, 1 66 | 67 | lw f 68 | sw [(0x8008 + (32 * 15))], f 69 | add l, 1 70 | 71 | lw f 72 | sw [(0x8008 + (32 * 16))], f 73 | add l, 1 74 | 75 | lw f 76 | sw [(0x8008 + (32 * 17))], f 77 | add l, 1 78 | 79 | lw f 80 | sw [(0x8008 + (32 * 18))], f 81 | add l, 1 82 | 83 | lw f 84 | sw [(0x8008 + (32 * 19))], f 85 | add l, 1 86 | 87 | lw f 88 | sw [(0x8008 + (32 * 20))], f 89 | add l, 1 90 | 91 | lw f 92 | sw [(0x8008 + (32 * 21))], f 93 | add l, 1 94 | 95 | lw f 96 | sw [(0x8008 + (32 * 22))], f 97 | add l, 1 98 | 99 | lw f 100 | sw [(0x8008 + (32 * 23))], f 101 | add l, 1 102 | 103 | lw f 104 | sw [(0x8008 + (32 * 24))], f 105 | add l, 1 106 | 107 | done: 108 | jmp [done] 109 | 110 | data: 111 | @db 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00 112 | @db 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 113 | @db 0x00, 0x66, 0x66, 0x00, 0x81, 0xE7, 0x3C, 0x00 114 | -------------------------------------------------------------------------------- /misc/tests/test4.asm: -------------------------------------------------------------------------------- 1 | @org 0x0000 2 | 3 | @include "os/arch.asm" 4 | @include "os/ostest.asm" 5 | 6 | jmp [ostest] 7 | 8 | osmain: 9 | jmp [osmain] 10 | -------------------------------------------------------------------------------- /misc/tests/test5.asm: -------------------------------------------------------------------------------- 1 | @include "os/arch.asm" 2 | 3 | @org 0x0000 4 | @define Q0 0xA000 5 | 6 | sw16 [ADDR_SPL], 0xFEFF 7 | sw [ADDR_MB], 1 8 | sw [(ADDR_BANK + 7)], 0xEE 9 | 10 | lda a, b, [ADDR_BANK] 11 | lda c, d, [image] 12 | lda [(SCANLINE_WIDTH_BYTES * SCREEN_HEIGHT)] 13 | sw16 [Q0], h, l 14 | call [memcpy16] 15 | 16 | loop2: 17 | jmp [loop2] 18 | 19 | ; copies memory 20 | ; a, b: dst 21 | ; c, d: src 22 | ; z: len 23 | memcpy: 24 | pusha 25 | jz z, [.done] 26 | .loop: 27 | lw f, c, d 28 | sw a, b, f 29 | add16 a, b, 1 30 | add16 c, d, 1 31 | dec z 32 | jnz z, [.loop] 33 | .done: 34 | popa 35 | ret 36 | 37 | ; copies memory (16-bit size) 38 | ; a, b: dst 39 | ; c, d: src 40 | ; Q0: len 41 | memcpy16: 42 | pusha 43 | .loop: 44 | push a 45 | lw a, [(Q0 + 1)] 46 | jz a, [.less_than_256] 47 | push b 48 | lw b, [(Q0 + 0)] 49 | mw z, 0xFF 50 | clb 51 | sub16 a, b, 0xFF 52 | sw16 [Q0], a, b 53 | pop b, a 54 | call [memcpy] 55 | add16 a, b, 0xFF 56 | add16 c, d, 0xFF 57 | jmp [.loop] 58 | .less_than_256: 59 | pop a 60 | lw z, [(Q0 + 0)] 61 | call [memcpy] 62 | popa 63 | ret 64 | 65 | @include "programs/holiday/image.asm" 66 | -------------------------------------------------------------------------------- /os/arch.asm: -------------------------------------------------------------------------------- 1 | @ifndef ARCH_ASM 2 | @define ARCH_ASM 3 | 4 | ; ARCHITECTURE CONSTANTS 5 | 6 | ; SPECIAL MEMORY ADDRESSES 7 | @define ADDR_ROM 0x0000 8 | @define ADDR_BANK 0x8000 9 | @define ADDR_RAM 0xC000 10 | @define ADDR_STACK 0xFEFF 11 | 12 | ; SPECIAL MEMORY-MAPPED REGISTERS 13 | @define ADDR_MB 0xFFFA 14 | 15 | @define ADDR_SP 0xFFFC 16 | @define ADDR_SPL 0xFFFC 17 | @define ADDR_SPH 0xFFFD 18 | 19 | @define ADDR_PC 0xFFFC 20 | @define ADDR_PCL 0xFFFC 21 | @define ADDR_PCH 0xFFFD 22 | 23 | ; FLAG INDICES 24 | @define FLAG_L 0x01 25 | @define FLAG_E 0x02 26 | @define FLAG_C 0x03 27 | @define FLAG_B 0x04 28 | 29 | @define FLAG_LESS FLAG_L 30 | @define FLAG_EQUAL FLAG_E 31 | @define FLAG_CARRY FLAG_C 32 | @define FLAG_BORROW FLAG_B 33 | 34 | ; GFX CONSTANTS 35 | @define SCANLINE_OFFSET 32 36 | @define SCANLINE_WIDTH 256 37 | @define SCREEN_WIDTH 208 38 | @define SCREEN_HEIGHT 240 39 | @define SCANLINE_OFFSET_BYTES (SCANLINE_OFFSET / 8) 40 | @define SCANLINE_WIDTH_BYTES (SCANLINE_WIDTH / 8) 41 | @define SCREEN_WIDTH_BYTES (SCREEN_WIDTH / 8) 42 | @define SCREEN_HEIGHT_BYTES (SCREEN_HEIGHT / 8) 43 | @define SCREEN_SIZE_BYTES ((256 / 8) * SCREEN_HEIGHT) 44 | 45 | ; OS DATA 46 | @define OSDATA_OFFSET 0xFA00 47 | 48 | ; TEMPORARY DATA CONSTANTS 49 | @define TEMP_OFFSET 0xFD00 50 | 51 | @define T0 (TEMP_OFFSET + 0) 52 | @define T1 (TEMP_OFFSET + 1) 53 | @define T2 (TEMP_OFFSET + 2) 54 | @define T3 (TEMP_OFFSET + 3) 55 | @define T4 (TEMP_OFFSET + 4) 56 | @define T5 (TEMP_OFFSET + 5) 57 | @define T6 (TEMP_OFFSET + 6) 58 | @define T7 (TEMP_OFFSET + 7) 59 | 60 | @define D0 (TEMP_OFFSET + 0) 61 | @define D1 (TEMP_OFFSET + 2) 62 | @define D2 (TEMP_OFFSET + 4) 63 | @define D3 (TEMP_OFFSET + 6) 64 | 65 | @endif 66 | -------------------------------------------------------------------------------- /os/lib/font.asm: -------------------------------------------------------------------------------- 1 | ; OS-BUNDLED BITMAP FONT 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | ; font glyphs, ascii order, starting at space 5 | font: 6 | @db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 7 | @db 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00 8 | @db 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 9 | @db 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00 10 | @db 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00 11 | @db 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00 12 | @db 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00 13 | @db 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 14 | @db 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00 15 | @db 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00 16 | @db 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00 17 | @db 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00 18 | @db 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06 19 | @db 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00 20 | @db 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 21 | @db 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 22 | @db 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 23 | @db 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 24 | @db 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 25 | @db 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 26 | @db 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 27 | @db 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 28 | @db 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 29 | @db 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 30 | @db 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 31 | @db 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 32 | @db 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00 33 | @db 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06 34 | @db 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00 35 | @db 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00 36 | @db 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00 37 | @db 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00 38 | @db 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00 39 | @db 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00 40 | @db 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00 41 | @db 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00 42 | @db 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00 43 | @db 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00 44 | @db 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00 45 | @db 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00 46 | @db 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00 47 | @db 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 48 | @db 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00 49 | @db 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00 50 | @db 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00 51 | @db 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00 52 | @db 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00 53 | @db 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00 54 | @db 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00 55 | @db 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00 56 | @db 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00 57 | @db 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00 58 | @db 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 59 | @db 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00 60 | @db 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 61 | @db 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00 62 | @db 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00 63 | @db 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00 64 | @db 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00 65 | @db 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00 66 | @db 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00 67 | @db 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00 68 | @db 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00 69 | @db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF 70 | @db 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00 71 | @db 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00 72 | @db 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00 73 | @db 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00 74 | @db 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00 75 | @db 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00 76 | @db 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00 77 | @db 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F 78 | @db 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00 79 | @db 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 80 | @db 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E 81 | @db 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00 82 | @db 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 83 | @db 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00 84 | @db 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00 85 | @db 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00 86 | @db 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F 87 | @db 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78 88 | @db 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00 89 | @db 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00 90 | @db 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00 91 | @db 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00 92 | @db 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 93 | @db 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00 94 | @db 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00 95 | @db 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F 96 | @db 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00 97 | @db 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00 98 | @db 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00 99 | @db 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00 100 | @db 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 101 | @db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 102 | 103 | -------------------------------------------------------------------------------- /os/lib/gfx.asm: -------------------------------------------------------------------------------- 1 | ; OS-BUNDLED GRAPHICS FUNCTIONS 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | ; sets pixel 5 | ; a: x 6 | ; b: y 7 | ; c: value 8 | set_pixel: 9 | pusha 10 | 11 | mw d, a 12 | mw z, c 13 | 14 | ; b already contains y 15 | ; [ab] -> scanline start 16 | mw a, 0 17 | mw c, SCANLINE_WIDTH_BYTES 18 | call [mul16_8] 19 | 20 | ; offset into VRAM 21 | add16 a, b, ADDR_BANK 22 | 23 | ; x >> 3 for byte 24 | push a, b 25 | mw a, d 26 | mw b, 3 27 | call [shr] 28 | mw c, a 29 | pop b, a 30 | 31 | ; add byte offset to scanline pointer 32 | add16 a, b, c 33 | 34 | ; add scanline offset 35 | add16 a, b, SCANLINE_OFFSET_BYTES 36 | 37 | ; 1 << (d & 0x7) is pixel mask 38 | push a, b 39 | and d, 0x7 40 | mw a, 1 41 | mw b, d 42 | call [shl] 43 | mw d, a 44 | pop b, a 45 | 46 | lw c, a, b 47 | jnz z, [.fill] 48 | not d 49 | and c, d 50 | jmp [.store] 51 | .fill: 52 | or c, d 53 | .store: 54 | sw a, b, c 55 | popa 56 | ret 57 | 58 | ; draws a font glyph 59 | ; a: glyph to draw 60 | ; c, d: x, y (x is in byte indices) 61 | draw_char: 62 | pusha 63 | 64 | ; move x from screen space -> vram space 65 | add c, SCANLINE_OFFSET_BYTES 66 | 67 | ; [ab] -> first byte 68 | sub a, ' ' 69 | mw b, a 70 | mw a, 0 71 | push c 72 | mw c, 8 73 | call [mul16_8] 74 | pop c 75 | add16 a, b, [font] 76 | 77 | ; [cd] -> first screen byte 78 | push a, b, c 79 | mw c, SCANLINE_WIDTH_BYTES 80 | mw a, 0 81 | mw b, d 82 | call [mul16_8] 83 | add16 a, b, ADDR_BANK 84 | pop c 85 | add16 a, b, c 86 | mw c, a 87 | mw d, b 88 | pop b, a 89 | 90 | mw z, 7 91 | .loop: 92 | lw f, a, b 93 | sw c, d, f 94 | add16 a, b, 1 95 | add16 c, d, SCANLINE_WIDTH_BYTES 96 | dec z 97 | jnz z, [.loop] 98 | .done: 99 | popa 100 | ret 101 | 102 | ; draws a string 103 | ; a, b: string 104 | ; c, d: x, y (x is in byte indices) 105 | draw_str: 106 | pusha 107 | .loop: 108 | lw z, a, b ; load character 109 | jz z, [.done] ; stop if null terminator 110 | push a 111 | mw a, z 112 | call [draw_char] 113 | pop a 114 | inc16 a, b 115 | inc c 116 | jmp [.loop] 117 | .done: 118 | popa 119 | ret 120 | -------------------------------------------------------------------------------- /os/lib/math.asm: -------------------------------------------------------------------------------- 1 | ; OS-BUNDLED MATH FUNCTIONS 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | ; multiply 5 | ; a: first operand 6 | ; b: second operand 7 | mul: 8 | push b, c 9 | mw c, b 10 | mw b, a 11 | mw a, 0 12 | jz c, [.done] 13 | .loop: 14 | add a, b 15 | dec c 16 | jnz c, [.loop] 17 | .done: 18 | pop c, b 19 | ret 20 | 21 | ; multiply (16-bit x 8-bit) 22 | ; a, b: first operand 23 | ; c: second operand 24 | mul16_8: 25 | push c, d, z 26 | mw d, a 27 | mw z, b 28 | mw a, 0 29 | mw b, 0 30 | jz c, [.done] 31 | .loop: 32 | add16 a, b, d, z 33 | dec c 34 | jnz c, [.loop] 35 | .done: 36 | pop z, d, c 37 | ret 38 | 39 | ; multiply (16-bit x 16-bit) 40 | ; a, b: first operand 41 | ; c, d: second operand 42 | ; D0: temporary variable storage 43 | mul16: 44 | push c, d, z 45 | eq16 c, d, 0 46 | jeq [.done] 47 | sw16 [D0], a, b 48 | mw16 a, b, 0 49 | .loop: 50 | lw16 z, f, [D0] 51 | add16 a, b, z, f 52 | dec16 c, d 53 | eq16 c, d, 0 54 | jne [.loop] 55 | .done: 56 | pop z, d, c 57 | ret 58 | 59 | ; compares two 16-bit numbers 60 | ; NOTE: DOES NOT SET CARRY/BORROW BITS 61 | ; a, b: first operand 62 | ; c, d: second operand 63 | cmp16: 64 | push a, b 65 | cmp a, c 66 | mw h, f 67 | cmp b, d 68 | mw l, f 69 | 70 | ; less: high less or (high equal and low less) 71 | mw a, h 72 | mw b, a 73 | push h, l 74 | call [shr] 75 | pop l, h 76 | and a, l 77 | or a, h 78 | and a, 0x01 79 | 80 | ; equal: both equal 81 | mw b, h 82 | and b, l 83 | and b, 0x02 84 | or a, b 85 | mw f, a 86 | 87 | pop b, a 88 | ret 89 | -------------------------------------------------------------------------------- /os/lib/shift.asm: -------------------------------------------------------------------------------- 1 | ; OS-BUNDLED SHIFT (LEFT/RIGHT) OPS 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | ; SHIFT LEFT 5 | ; a: number to shift 6 | ; b: number of times to shift 7 | shl: 8 | push b 9 | jz b, [.done] 10 | clb 11 | .shift: 12 | ; a + a equivalent to left shift 13 | add a, a 14 | dec b 15 | jnz b, [.shift] 16 | .done: 17 | pop b 18 | ret 19 | 20 | ; SHIFT LEFT (16-bit) 21 | ; a, b: number to shift 22 | ; c: times to shift 23 | shl16: 24 | push c 25 | jz c, [.done] 26 | clb 27 | .shift: 28 | ; ab + ab equivalent to left shift 29 | add16 a, b, a, b 30 | dec c 31 | jnz c, [.shift] 32 | .done: 33 | pop c 34 | ret 35 | 36 | ; SHIFT RIGHT 37 | ; a: number to shift 38 | ; b: number of times to shift 39 | shr: 40 | push b, c 41 | jz b, [.done] 42 | mw c, a 43 | mw a, 0 44 | .shift: 45 | jmn c, 0x80, [.try_64] 46 | add a, 64 47 | sub c, 128 48 | .try_64: 49 | jmn c, 0x40, [.try_32] 50 | add a, 32 51 | sub c, 64 52 | .try_32: 53 | jmn c, 0x20, [.try_16] 54 | add a, 16 55 | sub c, 32 56 | .try_16: 57 | jmn c, 0x10, [.try_08] 58 | add a, 8 59 | sub c, 16 60 | .try_08: 61 | jmn c, 0x08, [.try_04] 62 | add a, 4 63 | sub c, 8 64 | .try_04: 65 | jmn c, 0x04, [.try_02] 66 | add a, 2 67 | sub c, 4 68 | .try_02: 69 | jmn c, 0x02, [.next] 70 | add a, 1 71 | sub c, 2 72 | .next: 73 | dec b 74 | jnz b, [.shift] 75 | .done: 76 | pop c, b 77 | ret 78 | -------------------------------------------------------------------------------- /os/lib/term.asm: -------------------------------------------------------------------------------- 1 | ; OS-BUNDLED TERMINAL FUNCTIONS 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | @define TERM_WIDTH (192 / 8) 5 | @define TERM_HEIGHT (240 / 8) 6 | @define DATA (OSDATA_OFFSET + 0) 7 | @define DATA_SIZE (TERM_WIDTH * TERM_HEIGHT) 8 | @define CURSOR (DATA + DATA_SIZE + 0) 9 | @define CURSOR_X (CURSOR + 0) 10 | @define CURSOR_Y (CURSOR + 1) 11 | 12 | ; gets a character 13 | ; a: x 14 | ; b: y 15 | ; z: result 16 | term_lookup: 17 | push c, d 18 | 19 | ; c, d <- offset 20 | push a, b 21 | mw a, 0 22 | mw c, TERM_WIDTH 23 | call [mul16_8] 24 | mw16 c, d, a, b 25 | pop b, a 26 | 27 | lda [DATA] 28 | add16 h, l, c, d 29 | lw z 30 | pop d, c 31 | ret 32 | 33 | ; sets a character 34 | ; a: x 35 | ; b: y 36 | ; c: char 37 | term_set: 38 | push d, c 39 | 40 | ; c, d <- offset 41 | push a, b 42 | mw a, 0 43 | mw c, TERM_WIDTH 44 | call [mul16_8] 45 | mw16 c, d, a, b 46 | pop b, a 47 | 48 | lda [DATA] 49 | add16 h, l, c, d 50 | pop c, d 51 | sw c 52 | ret 53 | 54 | ; draw character 55 | ; a: x 56 | ; b: y 57 | term_draw_char: 58 | push a, c, d, z 59 | 60 | ; z <- character 61 | call [term_lookup] 62 | 63 | ; c <- x = a + 1 64 | mw c, a 65 | inc c 66 | 67 | ; d <- y = (b * 8) 68 | mw a, 8 69 | call [mul] 70 | mw d, a 71 | 72 | mw a, z 73 | call [draw_char] 74 | 75 | pop z, d, c, a 76 | ret 77 | 78 | ; draw terminal line 79 | ; a: line 80 | term_draw_line: 81 | push a, b 82 | mw b, a ; b <- y 83 | mw a, (TERM_WIDTH - 1) ; a <- x 84 | .loop: 85 | call [term_draw_char] 86 | dec a 87 | jnz a, [.loop] 88 | .done: 89 | pop b, a 90 | ret 91 | 92 | ; clears terminal 93 | term_clear: 94 | pusha 95 | mw z, 0 96 | 97 | ; clear data 98 | lda a, b, [DATA] 99 | mw16 c, d, DATA_SIZE 100 | call [memset] 101 | 102 | ; clear screen 103 | lda a, b, [ADDR_BANK] 104 | mw16 c, d, SCREEN_SIZE_BYTES 105 | call [memset] 106 | 107 | ; reset cursor 108 | sw [CURSOR_X], 0 109 | sw [CURSOR_Y], 0 110 | 111 | popa 112 | ret 113 | 114 | ; scrolls the terminal up one line 115 | term_scroll: 116 | pusha 117 | 118 | mw z, 0 119 | 120 | ; scroll data 121 | lda a, b, [DATA] 122 | lda c, d, [(DATA + TERM_WIDTH)] 123 | sw16 [D0], (DATA_SIZE - TERM_WIDTH) 124 | call [memcpy16] 125 | 126 | ; clear last line 127 | lda a, b, [(DATA + DATA_SIZE - TERM_WIDTH)] 128 | mw16 c, d, (TERM_WIDTH) 129 | call [memset] 130 | 131 | ; scroll graphics 132 | lda a, b, [ADDR_BANK] 133 | lda c, d, [(ADDR_BANK + (SCANLINE_WIDTH_BYTES * 8))] 134 | sw16 [D0], (SCREEN_SIZE_BYTES - (SCANLINE_WIDTH_BYTES * 8)) 135 | call [memcpy16] 136 | 137 | ; clear last line 138 | lda a, b, [(ADDR_BANK + SCREEN_SIZE_BYTES - (SCANLINE_WIDTH_BYTES * 8))] 139 | mw16 c, d, (SCANLINE_WIDTH_BYTES * 8) 140 | call [memset] 141 | 142 | popa 143 | ret 144 | 145 | ; sets cursor to selected byte 146 | ; a: byte 147 | term_set_cursor: 148 | pusha 149 | 150 | ; [SP] <- cursor byte 151 | push a 152 | 153 | ; a, b <- line offset 154 | mw a, 0 155 | lw b, [CURSOR_Y] 156 | mw16 c, d, (SCANLINE_WIDTH_BYTES * 8) 157 | call [mul16] 158 | 159 | ; a, b <- byte offset 160 | lw c, [CURSOR_X] 161 | add c, (SCANLINE_OFFSET_BYTES + 1) 162 | add16 a, b, c 163 | 164 | ; a, b <- starting byte 165 | lda [ADDR_BANK] 166 | add16 a, b, h, l 167 | 168 | ; c <- cursor byte 169 | pop c 170 | 171 | ; draw block 172 | mw z, 7 173 | .loop: 174 | sw a, b, c 175 | add16 a, b, SCANLINE_WIDTH_BYTES 176 | dec z 177 | jnz z, [.loop] 178 | 179 | popa 180 | ret 181 | 182 | ; prints a character at the current cursor position 183 | ; a: character 184 | term_print: 185 | push a, b, c 186 | 187 | mw c, a 188 | lw a, [CURSOR_X] 189 | lw b, [CURSOR_Y] 190 | 191 | ; is the character a newline? 192 | jne c, '\n', [.no_newline] 193 | 194 | ; clear cursor 195 | push a 196 | mw a, 0x00 197 | call [term_set_cursor] 198 | pop a 199 | 200 | jmp [.inc_y] 201 | .no_newline: 202 | 203 | ; set/draw character 204 | call [term_set] 205 | call [term_draw_char] 206 | 207 | ; increment cursor 208 | inc a 209 | jne a, TERM_WIDTH, [.cont] 210 | .inc_y: 211 | mw a, 0 212 | inc b 213 | .cont: 214 | ; check if terminal needs to scroll 215 | jne b, TERM_HEIGHT, [.no_scroll] 216 | call [term_scroll] 217 | mw a, 0 218 | mw b, (TERM_HEIGHT - 1) 219 | .no_scroll: 220 | sw [CURSOR_X], a 221 | sw [CURSOR_Y], b 222 | pop c, b 223 | 224 | ; redraw cursor 225 | mw a, 0xFF 226 | call [term_set_cursor] 227 | 228 | pop a 229 | ret 230 | 231 | ; prints a string to the terminal 232 | ; a, b: string 233 | term_print_str: 234 | push a, b, c 235 | .loop: 236 | lda a, b 237 | lw c 238 | jz c, [.done] 239 | push a 240 | mw a, c 241 | call [term_print] 242 | pop a 243 | inc16 a, b 244 | jmp [.loop] 245 | .done: 246 | pop c, b, a 247 | ret 248 | -------------------------------------------------------------------------------- /os/lib/util.asm: -------------------------------------------------------------------------------- 1 | ; OS-BUNDLED UTILITY FUNCTIONS 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | ; computes the length of a string 5 | ; a, b: string 6 | ; z: result 7 | strlen: 8 | push a, b, c 9 | mw z, 0 10 | .loop: 11 | lw c, a, b 12 | jeq c, 0, [.done] 13 | add16 a, b, 1 14 | add z, 1 15 | jmp [.loop] 16 | .done: 17 | pop c, b, a 18 | ret 19 | 20 | ; copies memory 21 | ; a, b: dst 22 | ; c, d: src 23 | ; z: len 24 | memcpy: 25 | pusha 26 | jz z, [.done] 27 | .loop: 28 | lw f, c, d 29 | sw a, b, f 30 | add16 a, b, 1 31 | add16 c, d, 1 32 | dec z 33 | jnz z, [.loop] 34 | .done: 35 | popa 36 | ret 37 | 38 | ; copies memory (16-bit size) 39 | ; a, b: dst 40 | ; c, d: src 41 | ; D0: len 42 | memcpy16: 43 | pusha 44 | .loop: 45 | push a 46 | lw a, [(D0 + 1)] 47 | jz a, [.less_than_256] 48 | push b 49 | lw b, [(D0 + 0)] 50 | mw z, 0xFF 51 | clb 52 | sub16 a, b, 0xFF 53 | sw16 [D0], a, b 54 | pop b, a 55 | call [memcpy] 56 | add16 a, b, 0xFF 57 | add16 c, d, 0xFF 58 | jmp [.loop] 59 | .less_than_256: 60 | pop a 61 | lw z, [(D0 + 0)] 62 | call [memcpy] 63 | popa 64 | ret 65 | 66 | ; sets memory 67 | ; a, b: dst 68 | ; c, d: len 69 | ; z: value 70 | memset: 71 | pusha 72 | add16 c, d, a, b 73 | .loop: 74 | eq16 a, b, c, d 75 | jnz f, [.done] 76 | sw a, b, z 77 | add16 a, b, 1 78 | jmp [.loop] 79 | .done: 80 | popa 81 | ret 82 | -------------------------------------------------------------------------------- /os/os.asm: -------------------------------------------------------------------------------- 1 | ; JDH-8 OPERATING SYSTEM 2 | ; INCLUDED IN OSTEXT.ASM 3 | 4 | osmain: 5 | sw [ADDR_MB], 1 6 | sw16 [ADDR_SP], 0xFEFF 7 | call [ADDR_RAM] 8 | .loop: 9 | jmp [.loop] 10 | -------------------------------------------------------------------------------- /os/oscall.asm: -------------------------------------------------------------------------------- 1 | @ifndef OSCALL_ASM 2 | @define OSCALL_ASM 3 | 4 | ; OS HEADER 5 | ; NOT TO BE INCLUDED IN OS 6 | 7 | @define OSCALL_OFFSET 5 8 | @define OSCALL_SIZE 5 9 | 10 | ; === MATH.ASM === 11 | @define mul ((0x00 * OSCALL_SIZE) + OSCALL_OFFSET) 12 | @define mul16_8 ((0x01 * OSCALL_SIZE) + OSCALL_OFFSET) 13 | @define mul16 ((0x02 * OSCALL_SIZE) + OSCALL_OFFSET) 14 | @define cmp16 ((0x03 * OSCALL_SIZE) + OSCALL_OFFSET) 15 | ; 0x04 16 | ; 0x05 17 | ; 0x06 18 | ; 0x07 19 | 20 | ; === UTIl.ASM === 21 | @define strlen ((0x08 * OSCALL_SIZE) + OSCALL_OFFSET) 22 | @define memcpy ((0x09 * OSCALL_SIZE) + OSCALL_OFFSET) 23 | @define memcpy16 ((0x0A * OSCALL_SIZE) + OSCALL_OFFSET) 24 | @define memset ((0x0B * OSCALL_SIZE) + OSCALL_OFFSET) 25 | ; 0x0C 26 | ; 0x0D 27 | ; 0x0E 28 | ; 0x0F 29 | 30 | ; === SHIFT.ASM === 31 | @define shl ((0x10 * OSCALL_SIZE) + OSCALL_OFFSET) 32 | @define shr ((0x11 * OSCALL_SIZE) + OSCALL_OFFSET) 33 | ; 0x12 34 | ; 0x13 35 | 36 | ; === GFX.ASM === 37 | @define set_pixel ((0x14 * OSCALL_SIZE) + OSCALL_OFFSET) 38 | @define draw_char ((0x15 * OSCALL_SIZE) + OSCALL_OFFSET) 39 | @define draw_str ((0x16 * OSCALL_SIZE) + OSCALL_OFFSET) 40 | ; 0x17 41 | ; 0x18 42 | ; 0x19 43 | ; 0x1A 44 | ; 0x1B 45 | 46 | ; === TERM.ASM === 47 | @define term_print ((0x1C * OSCALL_SIZE) + OSCALL_OFFSET) 48 | @define term_print_str ((0x1D * OSCALL_SIZE) + OSCALL_OFFSET) 49 | @define term_clear ((0x1E * OSCALL_SIZE) + OSCALL_OFFSET) 50 | ; 0x1F 51 | ; 0x20 52 | ; 0x21 53 | ; 0x22 54 | ; 0x23 55 | 56 | @endif 57 | -------------------------------------------------------------------------------- /os/ostest.asm: -------------------------------------------------------------------------------- 1 | ; COMPLETE FUNCTIONALITY TEST 2 | ; to be run on boot 3 | 4 | ; remove if test should not try to write to screen on failure/success 5 | @define VISUAL_OUTPUT 6 | 7 | @define test_changes 0x80C0 8 | @define test_data 0x80C1 9 | 10 | ostest: 11 | 12 | mw a, 0x55 13 | mw a, a 14 | 15 | mw b, 0x66 16 | mw a, b 17 | 18 | mw c, 0x77 19 | mw a, c 20 | 21 | mw d, 0x88 22 | mw a, d 23 | 24 | mw c, 0x55 25 | 26 | @ifdef VISUAL_OUTPUT 27 | sw [ADDR_MB], 1 28 | sw [(ADDR_BANK + 6)], 0x00 29 | @endif 30 | 31 | mw a, 0x01 32 | sw [test_changes], a 33 | 34 | mw a, 0x66 35 | sw [test_data], a 36 | 37 | ; clear some bytes so we can see the failure over the garbage 38 | mw z, 0x00 39 | sw [(ADDR_BANK + 4)], z 40 | sw [(ADDR_BANK + 5)], z 41 | sw [(ADDR_BANK + 6)], z 42 | sw [(ADDR_BANK + 7)], z 43 | sw [(ADDR_BANK + 8)], z 44 | sw [(ADDR_BANK + SCANLINE_WIDTH_BYTES + 4)], z 45 | sw [(ADDR_BANK + SCANLINE_WIDTH_BYTES + 5)], z 46 | sw [(ADDR_BANK + SCANLINE_WIDTH_BYTES + 6)], z 47 | sw [(ADDR_BANK + SCANLINE_WIDTH_BYTES + 7)], z 48 | sw [(ADDR_BANK + SCANLINE_WIDTH_BYTES + 8)], z 49 | 50 | jmp [.skip_to_me] 51 | 52 | jmp [.test_start] 53 | 54 | ; fail condition is in z, goes to h 55 | ; fail indicator goes to l 56 | .test_fail: 57 | @ifdef VISUAL_OUTPUT 58 | sw [ADDR_MB], 1 59 | sw [(ADDR_BANK + 6)], z 60 | @endif 61 | mw f, z 62 | .test_fail_loop: 63 | jmp [.test_fail_loop] 64 | 65 | @macro 66 | failne %i0, %r1, %i2: 67 | mw z, %i0 68 | jne %r1, %i2, [.test_fail] 69 | 70 | @macro 71 | faileq %i0, %r1, %i2: 72 | mw z, %i0 73 | jeq %r1, %i2, [.test_fail] 74 | 75 | .test_start: 76 | 77 | ; mw 78 | @macro 79 | mw_test %r0: 80 | mw a, 0x55 81 | mw %r0, 0xAB 82 | failne 0x01, %r0, 0xAB 83 | mw a, %r0 84 | failne 0x02, a, 0xAB 85 | mw a, 0x12 86 | mw %r0, a 87 | failne 0x03, %r0, 0x12 88 | 89 | mw_test a 90 | mw_test b 91 | mw_test c 92 | mw_test d 93 | 94 | ; test z manually 95 | mw z, 0xAB 96 | jne z, 0xAB, [test_fail] 97 | mw a, z 98 | jne a, 0xAB, [test_fail] 99 | mw a, 0x12 100 | mw z, a 101 | jne z, 0x12, [.test_fail] 102 | 103 | .skip_to_me: 104 | 105 | ; lw 106 | lda [0xFEFE] 107 | lda [test_data] 108 | lw d 109 | failne 0x04, d, 0x66 110 | 111 | lw c, [test_data] 112 | failne 0x05, c, 0x66 113 | 114 | ; sw 115 | lda [0xFEFE] 116 | lda [test_data] 117 | mw a, 0x77 118 | sw a 119 | lw b 120 | failne 0x06, b, 0x77 121 | ; jne b, 0x77, [test_fail] 122 | lw c, [test_data] 123 | failne 0x07, c, 0x77 124 | ; jne c, 0x77, [test_fail] 125 | 126 | ; push/pop 127 | lda [0xFEFF] 128 | sw [0xFFFC], l 129 | sw [0xFFFD], h 130 | 131 | ; TODO: might be a bug with macro placement? 132 | ; this might codegen even though it should be skipped 133 | @macro 134 | stack_test: 135 | mw a, 0x01 136 | mw b, 0x02 137 | push a 138 | push b 139 | push a 140 | push b 141 | pop a 142 | pop b 143 | pop c 144 | pop d 145 | failne 0x08, a, 0x02 ; jne a, 0x02, [test_fail] 146 | failne 0x09, b, 0x01 ; jne b, 0x01, [test_fail] 147 | failne 0x0A, c, 0x02 ; jne c, 0x02, [test_fail] 148 | failne 0x0B, d, 0x01 ; jne d, 0x01, [test_fail] 149 | push 0x66 150 | pop a 151 | failne 0x0D, a, 0x66 ; jne a, 0x66, [test_fail] 152 | push a 153 | pop z 154 | jne z, 0x66, [.test_fail] 155 | 156 | stack_test 157 | 158 | ; lda 159 | lda [0xEEEE] 160 | failne 0x0E, h, 0xEE ; jne h, 0xEE, [test_fail] 161 | 162 | lda [0xEEEE] 163 | failne 0x0F, l, 0xEE ; jne l, 0xEE, [test_fail] 164 | 165 | ; jnz 166 | 167 | ; TODO: fix? infinite loop. 168 | ; imm 169 | ; lda [.after_skip_me] 170 | ; jnz 1 171 | ; 172 | ; .skip_me: 173 | ; mw z, 0x10 174 | ; jz b, [.test_fail] 175 | ; mw a, 0x02 176 | ; sw [test_changes], a 177 | ; .after_skip_me: 178 | ; 179 | ; ; jumps second time 180 | ; lda [test_changes] 181 | ; lw c 182 | ; jne c, 0x01, [.move_on] 183 | ; 184 | ; ; reg 185 | ; mw b, 0x00 186 | ; lda [.after_skip_me] 187 | ; jnz b 188 | ; 189 | ; mw b, 0x01 190 | ; jnz b 191 | 192 | .move_on: 193 | 194 | ; arithmetic ops 195 | 196 | ; ADD 197 | mw a, 0x01 198 | mw b, 0x02 199 | add a, b 200 | failne 0x11, a, 0x03 ; jne a, 0x03, [test_fail] 201 | failne 0x12, b, 0x02 ; jne b, 0x02, [test_fail] 202 | 203 | ; less and borrow 204 | mw a, 0x01 205 | mw b, 0x02 206 | add a, b 207 | failne 0x12, f, 0x09 ; jne f, 0x09, [test_fail] 208 | 209 | ; ADC 210 | stc 211 | mw a, 0x01 212 | mw b, 0x02 213 | adc a, b 214 | mw c, f 215 | failne 0x13, a, 0x04 ; jne a, 0x04, [test_fail] 216 | failne 0x14, b, 0x02 ; jne b, 0x02, [test_fail] 217 | failne 0x15, c, 0x09 ; jne c, 0x09, [test_fail] 218 | 219 | ; AND 220 | mw f, 0x55 221 | mw c, 0x75 222 | mw d, 0x11 223 | and c, d 224 | failne 0x16, f, 0x55 ; jne f, 0x55, [test_fail] 225 | failne 0x17, c, 0x11 ; jne c, 0x11, [test_fail] 226 | 227 | ; OR 228 | mw f, 0x66 229 | mw c, 0x75 230 | mw d, 0x1F 231 | or c, d 232 | failne 0x18, f, 0x66 ; jne f, 0x66, [test_fail] 233 | failne 0x19, c, 0x7f ; jne c, 0x7F, [test_fail] 234 | 235 | ; NOR 236 | mw f, 0x77 237 | mw c, 0x75 238 | mw d, 0x1F 239 | nor c, d 240 | failne 0x1A, f, 0x77 ; jne f, 0x77, [test_fail] 241 | failne 0x1B, c, 0x80 ; jne c, 0x80, [test_fail] 242 | 243 | ; CMP is tested implicitly by previous jumps 244 | 245 | ; SBB 246 | clb 247 | mw c, 0x0F 248 | mw d, 0x04 249 | sbb c, d 250 | failne 0x1C, f, 0x00 ; jne f, 0x00, [test_fail] 251 | failne 0x1D, c, 0x0B ; jne c, 0x0B, [test_fail] 252 | 253 | stb 254 | mw c, 0x0F 255 | mw d, 0x04 256 | sbb c, d 257 | failne 0x1E, f, 0x00 ; jne f, 0x00, [test_fail] 258 | failne 0x1F, c, 0x0A ; jne c, 0x0A, [test_fail] 259 | 260 | ; const arithmetic 261 | clc 262 | mw a, 0x01 263 | add a, 0xFF 264 | push f 265 | failne 0x20, a, 0x00 ; jne a, 0x00, [test_fail] 266 | pop f 267 | failne 0x21, f, 0x0D ; jne f, 0x0D, [test_fail] 268 | 269 | sw [ADDR_MB], 0 270 | 271 | ; no writes to ROM 272 | mw a, 0xEE 273 | sw [0x0000], a 274 | mw b, 0x03 275 | lw b, [0x0000] 276 | faileq 0x22, b, 0xEE ; jeq b, 0xEE, [test_fail] 277 | 278 | ; general purpose RAM 279 | sw [0xF000], a 280 | lw b, [0xF000] 281 | failne 0x23, b, 0xEE ; jne b, 0xEE, [test_fail] 282 | 283 | mw a, 0xDD 284 | lda [0xF000] 285 | sw a 286 | lw b 287 | failne 0x24, b, 0xDD ; jne b, 0xDD, [test_fail] 288 | 289 | ; banked memory (VRAM) 290 | mw a, 0x66 291 | sw [0x8080], a 292 | lw b, [0x8080] 293 | failne 0x25, b, 0x66 ; jne b, 0x66, [test_fail] 294 | 295 | ; switch bank 296 | mw d, 0x01 297 | sw [0xFFFA], d 298 | lw b, [0x8080] 299 | faileq 0x26, b, 0x66 ; jeq b, 0x66, [test_fail] 300 | 301 | mw a, 0x77 302 | sw [0x8080], a 303 | lw b, [0x8080] 304 | failne 0x27, b, 0x77 ; jne b, 0x77, [test_fail] 305 | 306 | ; general purpose RAM, again, with banked RAM enabled 307 | ; 0xF000 should still have 0xDD in it! 308 | 309 | lw b, [0xF000] 310 | failne 0x28, b, 0xDD ; jne b, 0xDD, [test_fail] 311 | 312 | ; try overwriting again 313 | mw a, 0x55 314 | lda [0xF000] 315 | sw a 316 | lw b 317 | failne 0x29, b, 0x55 ; jne b, 0x55, [test_fail] 318 | 319 | ; repeat stack test in banked RAM 320 | lda [0x8FFF] 321 | sw [0xFFFC], l 322 | sw [0xFFFD], h 323 | 324 | stack_test 325 | 326 | ; proc test 327 | lda a, b, [.test_string] 328 | call [.test_strlen] 329 | mw a, z 330 | failne 0x30, a, 4 331 | jmp [.after_strlen] 332 | 333 | .test_strlen: 334 | mw z, 0 335 | .loop: 336 | lw d, a, b 337 | jeq d, 0, [.done] 338 | add16 a, b, 1 339 | add z, 1 340 | jmp [.loop] 341 | .done: 342 | ret 343 | .after_strlen: 344 | 345 | @ifdef TEST_VISUAL_OUTPUT 346 | sw [(ADDR_BANK + 6)], 0xFF 347 | @endif 348 | 349 | ; GO TO OS/OS.ASM 350 | jmp [osmain] 351 | 352 | .test_string: 353 | @ds "a bt" 354 | -------------------------------------------------------------------------------- /os/ostext.asm: -------------------------------------------------------------------------------- 1 | ; OS TEXT 2 | ; THIS IS THE MAIN ASSEMBLY FILE FOR THE JDH-8 ROM 3 | 4 | ; corresponds to OSCALL_OFFSET in oscall.asm 5 | ; TODO: jmp [ostest] 6 | jmp [osmain] 7 | 8 | ; 5 blank bytes for empty space in jump table 9 | ; a jump to one of these will fault/restart the OS 10 | @macro 11 | BLANK: 12 | jmp [0x0000] 13 | 14 | ; DO NOT MOVE THESE CALLS WITHOUT ADJUSTING HEADER OSCALL.ASM 15 | 16 | ; === MATH.ASM === 17 | jmp [mul] 18 | jmp [mul16_8] 19 | jmp [mul16] 20 | jmp [cmp16] 21 | blank 22 | blank 23 | blank 24 | blank 25 | 26 | ; === UTIL.ASM === 27 | jmp [strlen] 28 | jmp [memcpy] 29 | jmp [memcpy16] 30 | jmp [memset] 31 | blank 32 | blank 33 | blank 34 | blank 35 | 36 | ; === SHIFT.ASM === 37 | jmp [shl] 38 | jmp [shr] 39 | blank 40 | blank 41 | 42 | ; === GFX.ASM === 43 | jmp [set_pixel] 44 | jmp [draw_char] 45 | jmp [draw_str] 46 | blank 47 | blank 48 | blank 49 | blank 50 | blank 51 | 52 | ; === TERM.ASM === 53 | jmp [term_print] 54 | jmp [term_print_str] 55 | jmp [term_clear] 56 | blank 57 | blank 58 | blank 59 | blank 60 | blank 61 | 62 | ; ALL LIBRARIES 63 | @include "os/arch.asm" 64 | @include "os/lib/math.asm" 65 | @include "os/lib/util.asm" 66 | @include "os/lib/shift.asm" 67 | @include "os/lib/font.asm" 68 | @include "os/lib/gfx.asm" 69 | @include "os/lib/term.asm" 70 | 71 | @include "os/ostest.asm" 72 | @include "os/os.asm" 73 | -------------------------------------------------------------------------------- /programs/demo.asm: -------------------------------------------------------------------------------- 1 | @include "os/arch.asm" 2 | @include "os/oscall.asm" 3 | 4 | @org ADDR_RAM 5 | 6 | sw [ADDR_MB], 1 7 | jmp [main] 8 | 9 | main: 10 | call [term_clear] 11 | 12 | mw z, 24 13 | .loop0: 14 | lda a, b, [string] 15 | call [term_print_str] 16 | dec z 17 | jnz z, [.loop0] 18 | .loop: 19 | jmp [.loop] 20 | 21 | string: 22 | @ds "ABCDEFGHIJKLMNOPQRSTUVWXYZ\nZLKD\n" 23 | -------------------------------------------------------------------------------- /programs/holiday/holiday.asm: -------------------------------------------------------------------------------- 1 | @include "os/arch.asm" 2 | @include "os/oscall.asm" 3 | 4 | @org ADDR_RAM 5 | 6 | jmp [main] 7 | 8 | @include "programs/holiday/image.asm" 9 | 10 | counter: 11 | @db 0 12 | 13 | main: 14 | sw [ADDR_MB], 1 15 | lda a, b, [ADDR_BANK] 16 | lda c, d, [image] 17 | lda [(SCANLINE_WIDTH_BYTES * SCREEN_HEIGHT)] 18 | sw16 [D0], h, l 19 | call [memcpy16] 20 | .loop: 21 | jmp [.loop] 22 | -------------------------------------------------------------------------------- /programs/holiday/holiday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/jdh-8/e4fad7cf43c8a23066d4a230baaa130354fbefbf/programs/holiday/holiday.png -------------------------------------------------------------------------------- /programs/pong.asm: -------------------------------------------------------------------------------- 1 | ; ======== 2 | ; = PONG = 3 | ; ======== 4 | 5 | @include "os/arch.asm" 6 | @include "os/oscall.asm" 7 | 8 | @org ADDR_RAM 9 | 10 | ; sizes 11 | @define PADDLE_HEIGHT 16 12 | @define BALL_SIZE 1 13 | 14 | ; paddles 15 | @define PLEFT 0 16 | @define PRIGHT 1 17 | 18 | ; run out of characters after this 19 | @define MAX_SCORE 9 20 | 21 | ; keyboard port 22 | @define KBPRT 2 23 | 24 | jmp [main] 25 | 26 | ; dirty flags 27 | @define dirty_scores 0x01 28 | @define dirty_BORDER 0x02 29 | @define dirty_PLEFT 0x04 30 | @define dirty_PRIGHT 0x08 31 | 32 | ; key scancodes 33 | @define SCAN_LU 0x1A 34 | @define SCAN_LD 0x16 35 | @define SCAN_RU 0x52 36 | @define SCAN_RD 0x51 37 | 38 | ; key flags 39 | @define KEY_LU 0x01 40 | @define KEY_LD 0x02 41 | @define KEY_RU 0x04 42 | @define KEY_RD 0x08 43 | 44 | ; variables 45 | paddles: 46 | @dd 0x0000 47 | pball: 48 | @dd 0x0000 49 | vball: 50 | @dd 0x0000 51 | scores: 52 | @dd 0x0000 53 | randn: 54 | @db 0x00 55 | dirty: 56 | @dd 0x00 57 | plball: 58 | @dd 0x0000 59 | keys: 60 | @db 0x00 61 | 62 | ; TEXT 63 | 64 | ; places random number in z 65 | rand: 66 | push a 67 | lw z, [randn] 68 | mw a, 113 69 | add a, z 70 | sw [randn], a 71 | pop a 72 | ret 73 | 74 | ; resets the game 75 | ; a: last to score 76 | reset: 77 | pusha 78 | 79 | ; any score > 9? reset scores 80 | lw a, [(scores + 0)] 81 | lw b, [(scores + 1)] 82 | cmp a, MAX_SCORE 83 | mw h, f 84 | cmp b, MAX_SCORE 85 | and f, h 86 | jle [.no_score_reset] 87 | sw [(scores + 0)], 0 88 | sw [(scores + 1)], 0 89 | .no_score_reset: 90 | ; reset paddles 91 | @define reset_PADDLE_HEIGHT ((SCREEN_HEIGHT - PADDLE_HEIGHT) / 2) 92 | sw [(paddles + 0)], reset_PADDLE_HEIGHT 93 | sw [(paddles + 1)], reset_PADDLE_HEIGHT 94 | 95 | ; reset ball 96 | sw [(pball + 0)], ((SCREEN_WIDTH - BALL_SIZE) / 2) 97 | 98 | call [rand] 99 | and z, 0x7F 100 | add z, ((SCREEN_HEIGHT - 0x7F) / 2) 101 | sw [(pball + 1)], z 102 | 103 | lw a, [(pball + 0)] 104 | lw b, [(pball + 1)] 105 | sw [(plball + 0)], a 106 | sw [(plball + 1)], b 107 | 108 | ; ball velocity -> towards last to lose 109 | jeq a, PRIGHT, [.neg_x] 110 | sw [(vball + 0)], 1 111 | jmp [.y] 112 | .neg_x: 113 | sw [(vball + 0)], (-1) 114 | .y: 115 | call [rand] 116 | jms z, 0x1, [.neg_y] 117 | sw [(vball + 1)], 1 118 | jmp [.done] 119 | .neg_y: 120 | sw [(vball + 1)], (-1) 121 | .done: 122 | ; reset screen 123 | lda a, b, [ADDR_BANK] 124 | lda c, d, [(SCANLINE_WIDTH_BYTES * SCREEN_HEIGHT)] 125 | mw z, 0 126 | call [memset] 127 | 128 | sw [dirty], 0xFF 129 | call [draw] 130 | sw [dirty], 0x00 131 | 132 | popa 133 | ret 134 | 135 | ; draws a paddle 136 | ; a: which paddle to draw 137 | ; b: which byte to draw 138 | draw_paddle: 139 | pusha 140 | mw d, a 141 | mw z, b 142 | 143 | ; c <- paddle y 144 | lda [paddles] 145 | add16 h, l, a 146 | lw c 147 | 148 | ; [ab] -> first byte 149 | mw a, 0 150 | mw b, SCANLINE_WIDTH_BYTES 151 | call [mul16_8] 152 | 153 | ; add paddle offset 154 | jeq d, PRIGHT, [.right] 155 | mw d, (SCANLINE_OFFSET_BYTES + 1) 156 | jmp [.cont] 157 | .right: 158 | mw d, ((SCANLINE_OFFSET_BYTES + SCREEN_WIDTH_BYTES) - 1) 159 | .cont: 160 | add16 a, b, d 161 | 162 | ; point into video RAM 163 | add16 a, b, ADDR_BANK 164 | 165 | ; [cd] -> last byte 166 | mw c, a 167 | mw d, b 168 | 169 | ; add (stride * PADDLE_HEIGHT) 170 | clb 171 | push z 172 | mw z, PADDLE_HEIGHT 173 | .add: 174 | add16 c, d, SCANLINE_WIDTH_BYTES 175 | dec z 176 | jnz z, [.add] 177 | pop z 178 | .loop: 179 | sw a, b, z 180 | add16 a, b, SCANLINE_WIDTH_BYTES 181 | eq16 a, b, c, d 182 | jz f, [.loop] 183 | .done: 184 | popa 185 | ret 186 | 187 | ; draws the border 188 | draw_border: 189 | pusha 190 | lda a, b, (ADDR_BANK + (SCANLINE_WIDTH_BYTES / 2)) 191 | mw c, 0 192 | .loop: 193 | mw z, c 194 | and z, 0x01 195 | jnz z, [.next] 196 | lw d, a, b 197 | or d, 0x80 198 | sw a, b, d 199 | inc16 a, b 200 | lw d, a, b 201 | or d, 0x01 202 | sw a, b, d 203 | dec16 a, b 204 | .next: 205 | add16 a, b, (SCANLINE_WIDTH_BYTES * 3) 206 | add c, 3 207 | jlt c, SCREEN_HEIGHT, [.loop] 208 | .done: 209 | popa 210 | ret 211 | 212 | ; draws scores 213 | draw_scores: 214 | push a, c, d 215 | mw d, 4 216 | 217 | lw a, [(scores + 0)] 218 | add a, '0' 219 | mw c, ((SCREEN_WIDTH_BYTES / 2) - 2) 220 | call [draw_char] 221 | 222 | lw a, [(scores + 1)] 223 | add a, '0' 224 | mw c, ((SCREEN_WIDTH_BYTES / 2) + 1) 225 | call [draw_char] 226 | 227 | pop d, c, a 228 | ret 229 | 230 | ; clears scores 231 | clear_scores: 232 | push a, c, d 233 | mw a, ' ' 234 | mw d, 4 235 | 236 | mw c, ((SCREEN_WIDTH_BYTES / 2) - 2) 237 | call [draw_char] 238 | 239 | mw c, ((SCREEN_WIDTH_BYTES / 2) + 1) 240 | call [draw_char] 241 | 242 | pop d, c, a 243 | ret 244 | 245 | ; draws ball 246 | ; a: fill (>0) or clear (0) 247 | draw_ball: 248 | pusha 249 | mw z, a 250 | mw c, 0 251 | .loop_c: 252 | mw d, 0 253 | .loop_d: 254 | lw a, [(pball + 0)] 255 | lw b, [(pball + 1)] 256 | 257 | add a, c 258 | add b, d 259 | push c 260 | mw c, z 261 | call [set_pixel] 262 | pop c 263 | inc d 264 | jne d, 2, [.loop_d] 265 | inc c 266 | jne c, 2, [.loop_c] 267 | 268 | popa 269 | ret 270 | 271 | ; dirty things according to ball's location 272 | dirty_ball: 273 | push a, b, c 274 | lw a, [(pball + 0)] 275 | lw b, [(pball + 1)] 276 | lw c, [dirty] 277 | 278 | ; ball in middle? redraw border 279 | jlt a, ((SCREEN_WIDTH / 2) - 2), [.dscores] 280 | jgt a, ((SCREEN_WIDTH / 2) + 2), [.dscores] 281 | or c, dirty_BORDER 282 | .dscores: 283 | jgt b, 16, [.dleft] 284 | or c, dirty_scores 285 | .dleft: 286 | jgt a, 16, [.dright] 287 | or c, dirty_PLEFT 288 | .dright: 289 | jlt a, (SCREEN_WIDTH - 16), [.done] 290 | or c, dirty_PRIGHT 291 | .done: 292 | sw [dirty], c 293 | pop c, b, a 294 | ret 295 | 296 | ; renders everything which is dirty 297 | draw: 298 | pusha 299 | 300 | ; clear ball 301 | lw a, [(pball + 0)] 302 | lw b, [(pball + 1)] 303 | lw c, [(plball + 0)] 304 | lw d, [(plball + 1)] 305 | sw [(pball + 0)], c 306 | sw [(pball + 1)], d 307 | 308 | push a, b 309 | mw a, 0 310 | call [draw_ball] 311 | pop b, a 312 | 313 | sw [(pball + 0)], a 314 | sw [(pball + 1)], b 315 | 316 | ; draw dirty screen elements 317 | lw c, [dirty] 318 | jmn c, dirty_BORDER, [.dscores] 319 | call [draw_border] 320 | .dscores: 321 | jmn c, dirty_scores, [.dleft] 322 | call [draw_scores] 323 | .dleft: 324 | jmn c, dirty_PLEFT, [.dright] 325 | mw a, PLEFT 326 | mw b, 0xF0 327 | call [draw_paddle] 328 | .dright: 329 | jmn c, dirty_PRIGHT, [.done] 330 | mw a, PRIGHT 331 | mw b, 0x0F 332 | call [draw_paddle] 333 | .done: 334 | sw [dirty], 0 335 | mw a, 1 336 | call [draw_ball] 337 | popa 338 | ret 339 | 340 | ; updates keys with current keyboard data 341 | check_keys: 342 | push a, b, c 343 | 344 | lw c, [keys] 345 | inb a, KBPRT 346 | 347 | ; b <- pure scancode 348 | mw b, a 349 | and b, 0x7F 350 | 351 | ; determine key from scancode 352 | .left_up: 353 | jne b, SCAN_LU, [.left_down] 354 | mw b, KEY_LU 355 | jmp [.cont] 356 | .left_down: 357 | jne b, SCAN_LD, [.right_up] 358 | mw b, KEY_LD 359 | jmp [.cont] 360 | .right_up: 361 | jne b, SCAN_RU, [.right_down] 362 | mw b, KEY_RU 363 | jmp [.cont] 364 | .right_down: 365 | jne b, SCAN_RD, [.done] 366 | mw b, KEY_RD 367 | 368 | .cont: 369 | jmn a, 0x80, [.down] 370 | ; clear bit (key up) 371 | not b 372 | and c, b 373 | jmp [.done] 374 | .down: 375 | ; set bit (key down) 376 | or c, b 377 | .done: 378 | sw [keys], c 379 | pop c, b, a 380 | ret 381 | 382 | ; move a paddle 383 | ; a: which paddle 384 | ; b: direction (-1 or 1) 385 | move_paddle: 386 | pusha 387 | 388 | ; z <- ([cd] -> paddle byte) 389 | lda c, d, [paddles] 390 | add16 c, d, a 391 | lw z, c, d 392 | 393 | ; check that movement is OK 394 | jeq b, 1, [.down] 395 | jeq z, 0, [.done] 396 | jmp [.clear] 397 | .down: 398 | jgt z, (SCREEN_HEIGHT - PADDLE_HEIGHT), [.done] 399 | 400 | .clear: 401 | ; clear paddle 402 | push b 403 | mw b, 0x00 404 | call [draw_paddle] 405 | pop b 406 | 407 | ; add and store 408 | add z, b 409 | sw c, d, z 410 | 411 | ; mark dirty 412 | lw c, [dirty] 413 | jeq a, PRIGHT, [.right] 414 | or c, dirty_PLEFT 415 | jmp [.mark] 416 | .right: 417 | or c, dirty_PRIGHT 418 | .mark: 419 | sw [dirty], c 420 | .done: 421 | popa 422 | ret 423 | 424 | ; move_ball z flags 425 | @define MB_INV_X 0x01 426 | @define MB_INV_Y 0x02 427 | @define MB_RESET_L 0x04 428 | @define MB_RESET_R 0x08 429 | 430 | ; moves the ball 431 | ; returns z, combination of above flags according to move 432 | move_ball: 433 | push a, b, c, d 434 | 435 | mw z, 0 436 | 437 | ; try movement by velocity 438 | lw a, [(pball + 0)] 439 | lw b, [(pball + 1)] 440 | lw c, [(vball + 0)] 441 | lw d, [(vball + 1)] 442 | sw [(plball + 0)], a 443 | sw [(plball + 1)], b 444 | add a, c 445 | add b, d 446 | 447 | .left_paddle: 448 | jgt a, (12 + BALL_SIZE), [.right_paddle] 449 | jlt a, (12 + BALL_SIZE - 4), [.right_paddle] 450 | lw c, [(paddles + 0)] 451 | mw d, c 452 | add d, PADDLE_HEIGHT 453 | jlt b, c, [.right_paddle] 454 | jgt b, d, [.right_paddle] 455 | mw z, MB_INV_X 456 | lw c, [keys] 457 | and c, (KEY_LU | KEY_LD) 458 | jz c, [.left_noinv] 459 | jeq c, KEY_LD, [.left_down] 460 | mw c, (-1) 461 | jmp [.left_check] 462 | .left_down: 463 | mw c, 1 464 | .left_check: 465 | lw d, [(vball + 1)] 466 | jeq c, d, [.left_noinv] 467 | or z, MB_INV_Y 468 | .left_noinv: 469 | jmp [.apply] 470 | .right_paddle: 471 | jlt a, (SCREEN_WIDTH - 8 - BALL_SIZE), [.top_bound] 472 | jgt a, (SCREEN_WIDTH - 8 - BALL_SIZE + 4), [.top_bound] 473 | lw c, [(paddles + 1)] 474 | mw d, c 475 | add d, PADDLE_HEIGHT 476 | jlt b, c, [.top_bound] 477 | jgt b, d, [.top_bound] 478 | mw z, MB_INV_X 479 | lw c, [keys] 480 | and c, (KEY_RU | KEY_RD) 481 | jz c, [.right_noinv] 482 | jeq c, KEY_RD, [.right_down] 483 | mw c, (-1) 484 | jmp [.right_check] 485 | .right_down: 486 | mw c, 1 487 | .right_check: 488 | lw d, [(vball + 1)] 489 | jeq c, d, [.right_noinv] 490 | or z, MB_INV_Y 491 | .right_noinv: 492 | jmp [.apply] 493 | .top_bound: 494 | jne b, 0, [.bottom_bound] 495 | mw z, MB_INV_Y 496 | jmp [.apply] 497 | .bottom_bound: 498 | jne b, (SCREEN_HEIGHT - BALL_SIZE), [.left_bound] 499 | mw z, MB_INV_Y 500 | jmp [.apply] 501 | .left_bound: 502 | jne a, 0, [.right_bound] 503 | lw a, [(scores + 1)] 504 | inc a 505 | sw [(scores + 1)], a 506 | mw z, MB_RESET_L 507 | jmp [.apply] 508 | .right_bound: 509 | jne a, (SCREEN_WIDTH - BALL_SIZE), [.apply] 510 | lw a, [(scores + 0)] 511 | inc a 512 | sw [(scores + 0)], a 513 | mw z, MB_RESET_R 514 | .apply: 515 | jz z, [.store] 516 | mw f, z 517 | and f, (MB_RESET_L | MB_RESET_R) 518 | jz f, [.invert] 519 | jeq z, MB_RESET_R, [.right_score] 520 | mw a, PLEFT 521 | jmp [.do_reset] 522 | .right_score: 523 | mw a, PRIGHT 524 | .do_reset: 525 | call [reset] 526 | jmp [.done] 527 | .invert: 528 | ; invert according to z 529 | jmn z, MB_INV_X, [.invert_y] 530 | lw a, [(vball + 0)] 531 | mw b, 0 532 | sub b, a 533 | sw [(vball + 0)], b 534 | .invert_y: 535 | jmn z, MB_INV_Y, [.done] 536 | lw a, [(vball + 1)] 537 | mw b, 0 538 | sub b, a 539 | sw [(vball + 1)], b 540 | jmp [.done] 541 | .store: 542 | sw [(pball + 0)], a 543 | sw [(pball + 1)], b 544 | .done: 545 | pop d, c, b, a 546 | ret 547 | 548 | main: 549 | ; memory bank -> screen 550 | sw [ADDR_MB], 1 551 | 552 | ; seed random number generator 553 | sw [randn], 13 554 | 555 | call [reset] 556 | 557 | mw z, 0 558 | .loop: 559 | call [check_keys] 560 | 561 | ; busy-wait, expects 1 MHz clock 562 | mw c, 16 563 | call [mul16_8] 564 | inc z 565 | jne z, 255, [.loop] 566 | mw z, 0 567 | 568 | ; paddle movement 569 | lw c, [keys] 570 | .move_lu: 571 | mw a, PLEFT 572 | jmn c, KEY_LU, [.move_ld] 573 | mw b, (-1) 574 | call [move_paddle] 575 | .move_ld: 576 | jmn c, KEY_LD, [.move_ru] 577 | mw b, 1 578 | call [move_paddle] 579 | .move_ru: 580 | mw a, PRIGHT 581 | jmn c, KEY_RU, [.move_rd] 582 | mw b, (-1) 583 | call [move_paddle] 584 | .move_rd: 585 | jmn c, KEY_RD, [.move_done] 586 | mw b, 1 587 | call [move_paddle] 588 | .move_done: 589 | 590 | ; update 591 | call [move_ball] 592 | 593 | ; loop again, do not render, if there was a reset 594 | jms z, (MB_RESET_L | MB_RESET_R), [.loop] 595 | 596 | ; render 597 | call [dirty_ball] 598 | call [draw] 599 | jmp [.loop] 600 | ret 601 | -------------------------------------------------------------------------------- /syntax/jdh8.vim: -------------------------------------------------------------------------------- 1 | " JDH-8 ASSEMBLER SYNTAX FILE 2 | 3 | if exists("b:current_syntax") 4 | finish 5 | endif 6 | 7 | syn keyword jdhSpecial \$ 8 | syn keyword jdhReg a b c d f h l z 9 | syn keyword jdhTodo contained TODO 10 | syn match jdhComment ";.*$" contains=jdhTodo 11 | syn match jdhDirective "^\s*[@][a-zA-Z]\+" 12 | syn match jdhMacroArg "%[a-zA-Z0-9_]\+" 13 | syn match jdhNumber "0x[0-9a-fA-F]\+" 14 | syn match jdhNumber "\<[0-9]\+D\=\>" 15 | syn match jdhOp "^\s*[a-zA-Z0-9_]\+\s" 16 | syn match jdhOp "^\s*[a-zA-Z0-9_]\+$" 17 | syn match jdhMicroOp "^\s*[~]\=[a-zA-Z0-9_]\+[,]\s*\([~]\=[a-zA-Z0-9_]\+[,]\=\s*\)\+" 18 | 19 | " syn region jdhOp start='^' end='$' 20 | syn region jdhLabel start="^\s*[a-zA-Z0-9_.]" end=":" oneline contains=jdhLabelName,jdhMacroArg,jdhAddr 21 | syn region jdhString start='"' end='"' 22 | syn region jdhAddr start='\[' end='\]' contains=jdhMacroArg,jdhAddrLabel 23 | 24 | syn match jdhLabelName "^[a-zA-Z0-9_\.]\+:\=" contained 25 | syn match jdhAddrLabel '[a-zA-Z0-9_\.]\+' contained 26 | 27 | let b:current_syntax = "jdh8" 28 | hi def link jdhSpecial Special 29 | hi def link jdhTodo Todo 30 | hi def link jdhComment Comment 31 | hi def link jdhLabelName Label 32 | hi def link jdhAddrLabel Label 33 | hi def link jdhDirective Macro 34 | hi def link jdhOp Function 35 | hi def link jdhMicroOp Function 36 | hi def link jdhMacroArg Special 37 | hi def link jdhReg Identifier 38 | hi def link jdhNumber Number 39 | hi def link jdhString String 40 | -------------------------------------------------------------------------------- /test/char.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0x0C 3 | ; b = 0x5C 4 | ; c = 0x27 5 | 6 | jmp [test] 7 | 8 | chars: 9 | @db '\f' 10 | @db '\\', '\'' 11 | 12 | test: 13 | lw a, [(chars + 0)] 14 | lw b, [(chars + 1)] 15 | lw c, [(chars + 2)] 16 | halt 17 | -------------------------------------------------------------------------------- /test/complete.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; z = 0x00 3 | ; a = 0xFF 4 | 5 | @define changes 0x80C0 6 | @define data 0x80C1 7 | 8 | mw a, 0x01 9 | sw [changes], a 10 | 11 | mw a, 0x66 12 | sw [data], a 13 | 14 | jmp [start] 15 | 16 | ; fail condition is in z 17 | fail: 18 | mw a, 0xEE 19 | halt 20 | 21 | @macro 22 | failne %i0, %r1, %i2: 23 | mw z, %i0 24 | jne %r1, %i2, [fail] 25 | 26 | @macro 27 | faileq %i0, %r1, %i2: 28 | mw z, %i0 29 | jeq %r1, %i2, [fail] 30 | 31 | start: 32 | 33 | ; mw 34 | @macro 35 | mw_test %r0: 36 | mw a, 0x55 37 | mw %r0, 0xAB 38 | failne 0x01, %r0, 0xAB 39 | mw a, %r0 40 | failne 0x02, a, 0xAB 41 | mw a, 0x12 42 | mw %r0, a 43 | failne 0x03, %r0, 0x12 44 | 45 | mw_test a 46 | mw_test b 47 | mw_test c 48 | mw_test d 49 | 50 | ; test z manually 51 | mw z, 0xAB 52 | jne z, 0xAB, [fail] 53 | mw a, z 54 | jne a, 0xAB, [fail] 55 | mw a, 0x12 56 | mw z, a 57 | jne z, 0x12, [fail] 58 | 59 | ; lw 60 | lda [0xFEFE] 61 | lda [data] 62 | lw d 63 | failne 0x04, d, 0x66 64 | 65 | lw c, [data] 66 | failne 0x05, c, 0x66 67 | 68 | ; sw 69 | lda [0xFEFE] 70 | lda [data] 71 | mw a, 0x77 72 | sw a 73 | lw b 74 | failne 0x06, b, 0x77 75 | ; jne b, 0x77, [fail] 76 | lw c, [data] 77 | failne 0x07, c, 0x77 78 | ; jne c, 0x77, [fail] 79 | 80 | ; push/pop 81 | lda [0xFEFF] 82 | sw [0xFFFC], l 83 | sw [0xFFFD], h 84 | 85 | ; TODO: might be a bug with macro placement? 86 | ; this might codegen even though it should be skipped 87 | @macro 88 | stack_test: 89 | mw a, 0x01 90 | mw b, 0x02 91 | push a 92 | push b 93 | push a 94 | push b 95 | pop a 96 | pop b 97 | pop c 98 | pop d 99 | failne 0x08, a, 0x02 ; jne a, 0x02, [fail] 100 | failne 0x09, b, 0x01 ; jne b, 0x01, [fail] 101 | failne 0x0A, c, 0x02 ; jne c, 0x02, [fail] 102 | failne 0x0B, d, 0x01 ; jne d, 0x01, [fail] 103 | push 0x66 104 | pop a 105 | failne 0x0D, a, 0x66 ; jne a, 0x66, [fail] 106 | push a 107 | pop z 108 | jne z, 0x66, [fail] 109 | 110 | stack_test 111 | 112 | ; lda 113 | lda [0xEEEE] 114 | failne 0x0E, h, 0xEE ; jne h, 0xEE, [fail] 115 | 116 | lda [0xEEEE] 117 | failne 0x0F, l, 0xEE ; jne l, 0xEE, [fail] 118 | 119 | ; jnz 120 | 121 | ; imm 122 | lda [after] 123 | jnz 1 124 | 125 | skip_me: 126 | mw z, 0x10 127 | jz b, [fail] 128 | mw a, 0x02 129 | sw [changes], a 130 | after: 131 | 132 | ; jumps second time 133 | lda [changes] 134 | lw c 135 | jne c, 0x01, [move_on] 136 | 137 | ; reg 138 | mw b, 0x00 139 | lda [skip_me] 140 | jnz b 141 | 142 | mw b, 0x01 143 | jnz b 144 | 145 | move_on: 146 | 147 | ; arithmetic ops 148 | 149 | ; ADD 150 | mw a, 0x01 151 | mw b, 0x02 152 | add a, b 153 | failne 0x11, a, 0x03 ; jne a, 0x03, [fail] 154 | failne 0x12, b, 0x02 ; jne b, 0x02, [fail] 155 | 156 | ; less and borrow 157 | mw a, 0x01 158 | mw b, 0x02 159 | add a, b 160 | failne 0x12, f, 0x09 ; jne f, 0x09, [fail] 161 | 162 | ; ADC 163 | stc 164 | mw a, 0x01 165 | mw b, 0x02 166 | adc a, b 167 | mw c, f 168 | failne 0x13, a, 0x04 ; jne a, 0x04, [fail] 169 | failne 0x14, b, 0x02 ; jne b, 0x02, [fail] 170 | failne 0x15, c, 0x09 ; jne c, 0x09, [fail] 171 | 172 | ; AND 173 | mw f, 0x55 174 | mw c, 0x75 175 | mw d, 0x11 176 | and c, d 177 | failne 0x16, f, 0x55 ; jne f, 0x55, [fail] 178 | failne 0x17, c, 0x11 ; jne c, 0x11, [fail] 179 | 180 | ; OR 181 | mw f, 0x66 182 | mw c, 0x75 183 | mw d, 0x1F 184 | or c, d 185 | failne 0x18, f, 0x66 ; jne f, 0x66, [fail] 186 | failne 0x19, c, 0x7f ; jne c, 0x7F, [fail] 187 | 188 | ; NOR 189 | mw f, 0x77 190 | mw c, 0x75 191 | mw d, 0x1F 192 | nor c, d 193 | failne 0x1A, f, 0x77 ; jne f, 0x77, [fail] 194 | failne 0x1B, c, 0x80 ; jne c, 0x80, [fail] 195 | 196 | ; CMP is tested implicitly by previous jumps 197 | 198 | ; SBB 199 | clb 200 | mw c, 0x0F 201 | mw d, 0x04 202 | sbb c, d 203 | failne 0x1C, f, 0x00 ; jne f, 0x00, [fail] 204 | failne 0x1D, c, 0x0B ; jne c, 0x0B, [fail] 205 | 206 | stb 207 | mw c, 0x0F 208 | mw d, 0x04 209 | sbb c, d 210 | failne 0x1E, f, 0x00 ; jne f, 0x00, [fail] 211 | failne 0x1F, c, 0x0A ; jne c, 0x0A, [fail] 212 | 213 | ; const arithmetic 214 | clc 215 | mw a, 0x01 216 | add a, 0xFF 217 | push f 218 | failne 0x20, a, 0x00 ; jne a, 0x00, [fail] 219 | pop f 220 | failne 0x21, f, 0x0D ; jne f, 0x0D, [fail] 221 | 222 | ; no writes to ROM 223 | mw a, 0xEE 224 | sw [0x0000], a 225 | lw b, [0x0000] 226 | faileq 0x22, b, 0xEE ; jeq b, 0xEE, [fail] 227 | 228 | ; general purpose RAM 229 | sw [0xF000], a 230 | lw b, [0xF000] 231 | failne 0x23, b, 0xEE ; jne b, 0xEE, [fail] 232 | 233 | mw a, 0xDD 234 | lda [0xF000] 235 | sw a 236 | lw b 237 | failne 0x24, b, 0xDD ; jne b, 0xDD, [fail] 238 | 239 | ; banked memory (VRAM) 240 | mw a, 0x66 241 | sw [0x8080], a 242 | lw b, [0x8080] 243 | failne 0x25, b, 0x66 ; jne b, 0x66, [fail] 244 | 245 | ; switch bank 246 | mw d, 0x01 247 | sw [0xFFFA], d 248 | lw b, [0x8080] 249 | faileq 0x26, b, 0x66 ; jeq b, 0x66, [fail] 250 | 251 | mw a, 0x77 252 | sw [0x8080], a 253 | lw b, [0x8080] 254 | failne 0x27, b, 0x77 ; jne b, 0x77, [fail] 255 | 256 | ; general purpose RAM, again, with banked RAM enabled 257 | ; 0xF000 should still have 0xDD in it! 258 | 259 | lw b, [0xF000] 260 | failne 0x28, b, 0xDD ; jne b, 0xDD, [fail] 261 | 262 | ; try overwriting again 263 | mw a, 0x55 264 | lda [0xF000] 265 | sw a 266 | lw b 267 | failne 0x29, b, 0x55 ; jne b, 0x55, [fail] 268 | 269 | ; repeat stack test in banked RAM 270 | lda [0x8FFF] 271 | sw [0xFFFC], l 272 | sw [0xFFFD], h 273 | 274 | stack_test 275 | 276 | ; proc test 277 | lda a, b, [string] 278 | call [strlen] 279 | mw a, z 280 | failne 0x30, a, 4 281 | jmp [after_strlen] 282 | 283 | strlen: 284 | mw z, 0 285 | .loop: 286 | lw d, a, b 287 | jeq d, 0, [.done] 288 | add16 a, b, 1 289 | add z, 1 290 | jmp [.loop] 291 | .done: 292 | ret 293 | after_strlen: 294 | 295 | mw z, 0x00 296 | mw a, 0xFF 297 | halt 298 | 299 | string: 300 | @ds "a bt" 301 | -------------------------------------------------------------------------------- /test/data.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; [0x8000] = 0 3 | 4 | ; set up stack 5 | lda [0xFEFF] 6 | sw [0xFFFC], l 7 | sw [0xFFFD], h 8 | 9 | mw a, 0x01 10 | sw [0x8000], a 11 | jmp [main] 12 | 13 | fail: 14 | mw a, 0x01 15 | sw [0x8000], a 16 | mw d, 0xEE 17 | halt 18 | 19 | main: 20 | lda a, b, [string] 21 | call [strlen] 22 | jne z, 13, [fail] 23 | 24 | lda a, b, [bytes] 25 | lw d, a, b 26 | jne d, 0xFF, [fail] 27 | add16 a, b, 1 28 | 29 | lw d, a, b 30 | jne d, 45, [fail] 31 | add16 a, b, 1 32 | 33 | lw d, a, b 34 | jne d, (-1), [fail] 35 | 36 | ; call set_z_to_one through address stored in data 37 | mw z, 0 38 | lda a, b, [double_words] 39 | add16 a, b, 4 40 | lw c, a, b 41 | add16 a, b, 1 42 | lw d, a, b 43 | call d, c 44 | jne z, 1, [fail] 45 | 46 | call [sets_a_to_ff] 47 | jne a, 0xFF, [fail] 48 | 49 | sw [0x8000], 0x00 50 | halt 51 | 52 | strlen: 53 | mw z, 0 54 | .loop: 55 | lw d, a, b 56 | jeq d, 0, [.done] 57 | add16 a, b, 1 58 | add z, 1 59 | jmp [.loop] 60 | .done: 61 | ret 62 | 63 | set_z_to_one: 64 | mw z, 1 65 | ret 66 | 67 | bytes: 68 | @db 0xFF, 45, (-1), 0 69 | 70 | double_words: 71 | @dd 0xFF00, 0xFFFF, [set_z_to_one] 72 | 73 | string: 74 | @ds "Hello, world!" 75 | 76 | ; should be able to just @db our own instructions if we want 77 | sets_a_to_ff: 78 | @db 0x00 0xFF ; mw a, 0xFF 79 | ret 80 | -------------------------------------------------------------------------------- /test/esc.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 97 3 | ; b = 0x0a 4 | ; c = 98 5 | ; d = 0x09 6 | ; z = 0x5C 7 | 8 | lda [0xFEFF] 9 | sw [0xFFFC], l 10 | sw [0xFFFD], h 11 | 12 | jmp [test] 13 | 14 | strings: 15 | @ds "a\nb\t\\" 16 | 17 | fail: 18 | mw a, 0xFF 19 | halt 20 | 21 | test: 22 | lda a, b, [strings] 23 | call [strlen] 24 | jne z, 5, [fail] 25 | 26 | lw a, [(strings + 0)] 27 | lw b, [(strings + 1)] 28 | lw c, [(strings + 2)] 29 | lw d, [(strings + 3)] 30 | lw z, [(strings + 4)] 31 | halt 32 | 33 | strlen: 34 | mw z, 0 35 | .loop: 36 | lw d, a, b 37 | jeq d, 0, [.done] 38 | add16 a, b, 1 39 | add z, 1 40 | jmp [.loop] 41 | .done: 42 | ret 43 | -------------------------------------------------------------------------------- /test/eval.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0x00 3 | 4 | mw a, (5 + 3) 5 | jne a, 8, [fail] 6 | 7 | mw a, (6 - 3) 8 | jne a, 3, [fail] 9 | 10 | mw a, ((5 + 6) + (3 - 2)) 11 | jne a, 12, [fail] 12 | 13 | mw a, (1 < 3) 14 | jne a, 0x8, [fail] 15 | 16 | mw a, (1 < 3 + 5) 17 | jne a, 13, [fail] 18 | 19 | mw a, (~(1 < 3)) 20 | jne a, 0xF7, [fail] 21 | 22 | mw a, (0x8 > 1) 23 | jne a, 0x4, [fail] 24 | 25 | mw a, (0x1 | (0xF0 & 0xF7)) 26 | jne a, 0xF1, [fail] 27 | 28 | mw a, (0x1F ^ 0xF0) 29 | jne a, 0xEF, [fail] 30 | 31 | mw a, (10 / 5) 32 | jne a, 2, [fail] 33 | 34 | mw a, (100 / 30) 35 | jne a, 3, [fail] 36 | 37 | mw a, (-1) 38 | jne a, 0xFF, [fail] 39 | 40 | mw a, (~(((50 + (60 - 10)) / 100) < 3)) 41 | jne a, 0xF7, [fail] 42 | 43 | mw a, 0x00 44 | halt 45 | 46 | fail: 47 | mw a, 0x01 48 | halt 49 | 50 | @ds "aaaah" 51 | -------------------------------------------------------------------------------- /test/extras.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0x00 3 | ; [0x8000] = 0xAA 4 | 5 | jmp [main] 6 | 7 | fail: 8 | mw a, 0x01 9 | halt 10 | 11 | main: 12 | ; CHECK CMP F 13 | mw f, 0x08 14 | cmp f, 0x08 15 | and f, 0x02 16 | jz f, [fail] 17 | 18 | ; CHECK EQ16 19 | lda a, b, [fail] 20 | eq16 a, b, [fail] 21 | jz f, [fail] 22 | 23 | lda a, b, 0xFF04 24 | lda c, d, 0xFE03 25 | 26 | eq16 a, b, c, d 27 | jnz f, [fail] 28 | 29 | inc c 30 | inc d 31 | eq16 a, b, c, d 32 | jz f, [fail] 33 | 34 | ; CHECK INC16 35 | inc16 a, b 36 | eq16 a, b, [fail] 37 | jnz f, [fail] 38 | 39 | ; CHECK ADD16 40 | lda a, b, 0x00FF 41 | add16 a, b, 0x8001 42 | eq16 a, b, 0x8100 43 | jz f, [fail] 44 | 45 | ; CHECK SW AT REGISTER 46 | lda a, b, 0x8000 47 | sw a, b, 0xAA 48 | 49 | ; CHECK JMS 50 | mw a, 0xA2 51 | jms a, 0x02, [.continue_0] 52 | jmp [fail] 53 | .continue_0: 54 | ; CHECK JMN 55 | mw a, 0xA2 56 | jmn a, 0x01, [.continue_1] 57 | jmp [fail] 58 | .continue_1: 59 | ; CHECK JGT 60 | mw a, 0x3 61 | jgt a, 0x01, [.continue_2] 62 | jmp [fail] 63 | .continue_2: 64 | mw a, 0x01 65 | jgt a, 0x02, [fail] 66 | 67 | mw a, 0x00 68 | halt 69 | -------------------------------------------------------------------------------- /test/fib.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; d = 21 3 | ; a = 13 4 | ; b = 21 5 | ; c = 0 6 | ; [0x0000] = 0 7 | 8 | ; calculates the 8th fibonacci number leaves the result in d 9 | 10 | @org 0x0000 11 | @define COUNT 7 12 | 13 | fib: 14 | mw a, 0 15 | mw b, 1 16 | mw c, COUNT 17 | .loop: 18 | mw d, a 19 | add d, b 20 | mw a, b 21 | mw b, d 22 | clb 23 | sbb c, 1 24 | jnz c, [.loop] 25 | .done: 26 | halt 27 | 28 | @dn 1024 29 | -------------------------------------------------------------------------------- /test/jump.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0x03 3 | ; b = 0x04 4 | ; d = 0xFF 5 | ; z = 0x00 6 | 7 | jmp [_c] 8 | 9 | _a: 10 | mw a, 0x02 11 | _b: 12 | mw a, 0x03 13 | jmp [_e] 14 | _c: 15 | mw b, 0x04 16 | jmp [_a] 17 | _d: 18 | mw c, 0x05 19 | _e: 20 | mw d, 0xFF 21 | lda [_g] 22 | jnz d 23 | _f: 24 | mw d, 0xEE 25 | _g: 26 | mw z, 0x00 27 | lda [_f] 28 | jnz z 29 | halt 30 | -------------------------------------------------------------------------------- /test/mem.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0xF0 3 | ; b = 0x0F 4 | ; c = 0xF0 5 | ; d = 0x0F 6 | ; [0x8F00] = 0xF0 7 | ; [0x8F01] = 0x0F 8 | ; [0x8FF0] = 0xF0 9 | ; [0x8FF1] = 0x0F 10 | 11 | mw a, 0xF0 12 | mw b, 0x0F 13 | 14 | sw [0x8F00], a 15 | sw [0x8F01], b 16 | sw [0x8FF0], a 17 | sw [0x8FF1], b 18 | 19 | lw a, [0x8F00] 20 | lw b, [0x8F01] 21 | lw c, [0x8FF0] 22 | lw d, [0x8FF1] 23 | 24 | lda [0x8FF1] 25 | lw d 26 | 27 | halt 28 | -------------------------------------------------------------------------------- /test/mw.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 1 3 | ; b = 2 4 | ; c = 3 5 | ; d = 1 6 | 7 | mw a, 1 8 | mw b, 2 9 | mw c, 3 10 | mw d, a 11 | mw a, d 12 | halt 13 | -------------------------------------------------------------------------------- /test/ops.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0x00 3 | 4 | @define F_LESS 0x01 5 | @define F_EQUAL 0x02 6 | @define F_CARRY 0x03 7 | @define F_BORROW 0x04 8 | 9 | ; cmp 10 | mw a, 0x06 11 | cmp a, 0x04 12 | and f, (F_EQUAL | F_LESS) 13 | jnz f, [fail] 14 | 15 | ; add 16 | mw a, 0x05 17 | mw b, 0x05 18 | add a, b 19 | cmp a, 0x0A 20 | not f 21 | and f, F_EQUAL 22 | jnz f, [fail] 23 | 24 | add a, a 25 | cmp a, 0x14 26 | not f 27 | and f, F_EQUAL 28 | jnz f, [fail] 29 | 30 | ; adc 31 | mw a, 0xFF 32 | mw b, 0x00 33 | add a, b 34 | jc [fail] 35 | 36 | mw b, 0x01 37 | add a, b 38 | jnc [fail] 39 | 40 | or f, 0x04 41 | mw a, 0x00 42 | mw b, 0x80 43 | adc a, b 44 | jne a, 0x81, [fail] 45 | 46 | ; and 47 | mw a, 0xF0 48 | mw b, 0x0F 49 | and a, b 50 | jnz a, [fail] 51 | 52 | mw a, 0x80 53 | mw b, 0x88 54 | and a, b 55 | jne a, 0x80, [fail] 56 | 57 | ; or 58 | mw a, 0xF0 59 | mw b, 0x0F 60 | or a, b 61 | jne a, 0xFF, [fail] 62 | 63 | ; nor 64 | mw a, 0x80 65 | mw b, 0x18 66 | nor a, b 67 | jne a, 0x67, [fail] 68 | 69 | ; sbb 70 | mw a, 0x01 71 | mw b, 0x01 72 | sbb a, b 73 | jb [fail] 74 | 75 | sbb a, b 76 | jnb [fail] 77 | 78 | or f, 0x8 79 | mw a, 0x05 80 | mw b, 0x01 81 | sbb a, b 82 | jne a, 0x03, [fail] 83 | 84 | ; arithmetic macros 85 | ; not 86 | mw a, 0x0F 87 | not a 88 | jne a, 0xF0, [fail] 89 | 90 | ; nand 91 | mw a, 0x0F 92 | mw b, 0xF0 93 | nand a, b 94 | jne a, 0xFF, [fail] 95 | 96 | ; xnor 97 | mw a, 0xF0 98 | mw b, 0x88 99 | xnor a, b 100 | jne a, 0x87, [fail] 101 | 102 | ; xor 103 | mw a, 0xF0 104 | mw b, 0x88 105 | xor a, b 106 | jne a, 0x78, [fail] 107 | 108 | mw a, 0x00 109 | halt 110 | 111 | fail: 112 | mw a, 0x01 113 | halt 114 | -------------------------------------------------------------------------------- /test/proc.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 11 3 | ; z = 1 4 | ; c = 0 5 | ; d = 0xFF 6 | 7 | ; set up stack so calls work as expected 8 | lda [0xFEFF] 9 | sw [0xFFFC], l 10 | sw [0xFFFD], h 11 | 12 | call [start] 13 | halt 14 | 15 | return_from_me: 16 | mw z, c 17 | ret 18 | 19 | start: 20 | mw c, 0x10 21 | mw z, 0xFF 22 | call [return_from_me] 23 | 24 | mw c, 0xFF 25 | lda [return_from_me] 26 | mw a, h 27 | mw b, l 28 | call a, b 29 | mw d, z 30 | 31 | mw a, 1 32 | mw c, 10 33 | mw z, 10 34 | 35 | call [add_one] 36 | ret 37 | 38 | add_one: 39 | add a, 1 40 | call [return_from_me] 41 | sbb c, 1 42 | jz c, [.done] 43 | call [add_one] 44 | .done: 45 | ret 46 | -------------------------------------------------------------------------------- /test/stack.asm: -------------------------------------------------------------------------------- 1 | ; TEST 2 | ; a = 0x06 3 | ; b = 0x02 4 | ; c = 0x01 5 | ; d = 0x05 6 | ; l = 0xFF 7 | ; h = 0xFE 8 | ; [0xFEFF] = 0x03 9 | ; [0xFEFE] = 0x04 10 | 11 | lda [0xFEFF] 12 | sw [0xFFFC], l 13 | sw [0xFFFD], h 14 | 15 | push 0x06 16 | pop a 17 | 18 | push 0x01 19 | push 0x02 20 | pop b 21 | pop c 22 | 23 | push 0x03 24 | push 0x04 25 | push 0x05 26 | pop d 27 | 28 | halt 29 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../common/util.h" 6 | #include "../common/jdh8.h" 7 | #include "../emu/emu.h" 8 | 9 | static const char *asmpath, *testpath; 10 | 11 | static bool verbose = false; 12 | 13 | struct Assertion { 14 | bool reg; 15 | u8 value; 16 | 17 | union { 18 | char name; 19 | u16 addr; 20 | }; 21 | }; 22 | 23 | static void dbg(const char *fmt, ...) { 24 | if (!verbose) { 25 | return; 26 | } 27 | 28 | va_list args; 29 | va_start(args, fmt); 30 | printf("%s", "[DEBUG] "); 31 | vfprintf(stdout, fmt, args); 32 | va_end(args); 33 | } 34 | 35 | static bool test(const char *filename) { 36 | printf("Testing file %s ... ", filename); 37 | 38 | // assertions stated in test file 39 | usize num_assertions = 0; 40 | struct Assertion assertions[32]; 41 | 42 | // read file contents 43 | FILE *f = fopen(filename, "rb"); 44 | assert(f); 45 | 46 | fseek(f, 0, SEEK_END); 47 | usize len = ftell(f); 48 | rewind(f); 49 | 50 | char *buf = malloc(len + 1); 51 | assert(buf); 52 | assert(fread((void *) buf, len, 1, f) == 1); 53 | fclose(f); 54 | buf[len] = '\0'; 55 | 56 | dbg("Read %d bytes from file %s\n", len, filename); 57 | 58 | // parse first lines, verify formatting 59 | bool test_found = false; 60 | 61 | char *str = buf; 62 | while (*str != '\0' && 63 | *(str + 1) != '\0' && 64 | !(str != buf && *(str - 1) == '\n' && *str == '\n')) { 65 | char *eol = strchr(str, '\n'); 66 | assert(eol); 67 | 68 | // seek to first non-space 69 | while (str != eol && isspace(*str)) { 70 | str++; 71 | } 72 | 73 | // must be comment 74 | assert(*str == ';'); 75 | 76 | if (!test_found) { 77 | // first line must be "; TEST" 78 | assert(strstr(str, "; TEST\n") == str); 79 | test_found = true; 80 | } else { 81 | // skip ; 82 | str++; 83 | 84 | // other lines must assert about assembler state 85 | char *eq = strchr(str, '='); 86 | assert(eq && eq < eol); 87 | 88 | while (isspace(*str)) { 89 | str++; 90 | } 91 | 92 | if (*str == '[') { 93 | // memory address 94 | char *rb = strchr(str, ']'); 95 | assert(rb && rb < eq); 96 | 97 | char nstr[16]; 98 | assert((usize) (rb - str) <= sizeof(nstr)); 99 | memcpy(nstr, str + 1, rb - str - 1); 100 | nstr[rb - str - 1] = '\0'; 101 | 102 | assert(!strtou16(nstr, 0, &assertions[num_assertions].addr)); 103 | assertions[num_assertions++].reg = false; 104 | str = rb + 1; 105 | } else { 106 | // register 107 | assertions[num_assertions++] = (struct Assertion) { 108 | .reg = true, 109 | .name = *str 110 | }; 111 | 112 | str++; 113 | } 114 | 115 | while (str != eq) { 116 | assert(isspace(*str)); 117 | str++; 118 | } 119 | 120 | str++; 121 | 122 | char value[256]; 123 | memcpy(value, str, eol - str); 124 | value[eol - str] = '\0'; 125 | strstrip(value); 126 | 127 | i64 v; 128 | assert(!strtoi64(value, 0, &v)); 129 | assertions[num_assertions - 1].value = v; 130 | } 131 | 132 | str = eol + 1; 133 | } 134 | 135 | char command[256]; 136 | 137 | // generate temporary filename 138 | // TODO: better way to do this without deleting? 139 | char tmpfile[] = "/tmp/jdh8-asmtest-XXXXXXXX"; 140 | assert(mkstemp(tmpfile) != -1); 141 | dbg("tmpfile is %s\n", tmpfile); 142 | 143 | strncpy(command, "rm -f ", sizeof(command)); 144 | strncat(command, tmpfile, sizeof(command) - strlen(command) - 1); 145 | assert(!system(command)); 146 | dbg("Running \"%s\"\n", command); 147 | 148 | // assemble the file 149 | strncpy(command, asmpath, sizeof(command)); 150 | strncat(command, " -o ", sizeof(command) - strlen(command) - 1); 151 | strncat(command, tmpfile, sizeof(command) - strlen(command) - 1); 152 | strncat(command, " ", sizeof(command) - strlen(command) - 1); 153 | strncat(command, filename, sizeof(command) - strlen(command) - 1); 154 | assert(!system(command)); 155 | dbg("Running \"%s\"\n", command); 156 | 157 | // emulate until halt 158 | struct JDH8 state; 159 | memset(&state, 0, sizeof(struct JDH8)); 160 | add_bank(&state, 1); 161 | 162 | dbg("Emulating...\n"); 163 | load(&state, tmpfile, 0); 164 | state.write_protect = true; 165 | 166 | usize count = 0; 167 | while (!halted(&state)) { 168 | step(&state); 169 | 170 | if (++count >= 65536) { 171 | printf( 172 | ANSI_MAGENTA "%s\n" ANSI_RESET, 173 | "aborted" 174 | ); 175 | return false; 176 | } 177 | } 178 | dbg("Emulation complete, CPU has halted\n"); 179 | 180 | // check assertions 181 | for (usize i = 0; i < num_assertions; i++) { 182 | struct Assertion *a = &assertions[i]; 183 | 184 | char src[256]; 185 | snprintf( 186 | src, sizeof(src), 187 | a->reg ? "%c" : "[0x%04X]", 188 | a->reg ? a->name : a->addr 189 | ); 190 | 191 | u8 value; 192 | 193 | if (a->reg) { 194 | char name[2]; 195 | name[0] = a->name; 196 | name[1] = '\0'; 197 | 198 | isize i = -1; 199 | assert((i = strcasetab(R_NAMES, R_COUNT, name)) != -1); 200 | value = state.registers.raw[i]; 201 | } else { 202 | value = peek(&state, a->addr); 203 | } 204 | 205 | if (value != a->value) { 206 | printf( 207 | ANSI_RED "expected %s = 0x%02X, got 0x%02X\n" ANSI_RESET, 208 | src, a->value, value 209 | ); 210 | return false; 211 | } 212 | 213 | dbg("Assertion %s = 0x%02X passed", src, a->value); 214 | } 215 | 216 | printf(ANSI_GREEN "%s" ANSI_RESET "\n", "OK"); 217 | return true; 218 | } 219 | 220 | static void usage() { 221 | printf( 222 | "usage: test [-a/--asmpath path] [-v]" 223 | " [--verbose] [test file or directory]\n"); 224 | } 225 | 226 | int main(int argc, const char *argv[]) { 227 | asmpath = "./bin/asm"; 228 | testpath = "./test"; 229 | 230 | for (int i = 1; i < argc; i++) { 231 | if (!strcmp(argv[i], "-h") || 232 | !strcmp(argv[i], "--help")) { 233 | usage(); 234 | exit(0); 235 | } else if (!strcmp(argv[i], "-a") || 236 | !strcmp(argv[i], "--asmpath")) { 237 | assert(i != argc - 1); 238 | asmpath = argv[++i]; 239 | } else if (!strcmp(argv[i], "-v") || 240 | !strcmp(argv[i], "--verbose")) { 241 | verbose = true; 242 | } else { 243 | testpath = argv[i]; 244 | } 245 | } 246 | 247 | assert(!access(asmpath, F_OK)); 248 | assert(!access(testpath, F_OK | R_OK)); 249 | 250 | struct stat sb; 251 | assert(!stat(testpath, &sb)); 252 | if (S_ISDIR(sb.st_mode)) { 253 | // find all files with .asm extension 254 | DIR *d; 255 | assert(d = opendir(testpath)); 256 | 257 | struct dirent *e; 258 | while ((e = readdir(d))) { 259 | // construct relative path 260 | char path[256]; 261 | strncpy(path, testpath, sizeof(path)); 262 | strncat(path, "/", sizeof(path) - strlen(path) - 1); 263 | strncat(path, e->d_name, sizeof(path) - strlen(path) - 1); 264 | 265 | assert(!stat(path, &sb)); 266 | 267 | if (e->d_name[0] == '.') { 268 | // hidden file 269 | } else if (S_ISDIR(sb.st_mode)) { 270 | printf("Skipping directory %s\n", path); 271 | } else if (strsuf(".asm", e->d_name)) { 272 | test(path); 273 | } else { 274 | printf("Skipping file %s\n", path); 275 | } 276 | } 277 | 278 | closedir(d); 279 | } else { 280 | test(testpath); 281 | } 282 | } 283 | --------------------------------------------------------------------------------