├── software ├── kernel │ ├── ramfs.s │ ├── Makefile │ ├── system.cfg │ ├── main.s │ ├── interrupt.s │ ├── wdccc_test.s │ ├── device │ │ └── uart.s │ └── panic.s ├── requirements.txt ├── ramfs │ ├── test.txt │ └── etc │ │ └── passwd ├── .gitignore ├── kernel_tmp │ ├── kernel_tmp.cfg │ ├── Makefile │ └── kernel_tmp.s ├── hexdump │ ├── README.md │ ├── hexdump.cfg │ └── Makefile ├── snake │ ├── snake.cfg │ ├── test.cfg │ ├── Makefile │ ├── random.s │ └── random_test.s ├── bootloader │ ├── Makefile │ ├── boot.cfg │ └── boot.s ├── build_tools │ └── test_ramfs.py └── Makefile ├── rom ├── .gitignore ├── address.s ├── system.cfg ├── vectors.s ├── Makefile ├── post.s ├── xmodem.s ├── main.s ├── uart.s └── interrupt.s ├── emulator ├── .gitignore ├── test │ ├── resources │ │ └── testing_example │ │ │ ├── test.lbl │ │ │ ├── test.bin │ │ │ ├── example.o │ │ │ ├── example.s │ │ │ ├── README.md │ │ │ ├── Makefile │ │ │ ├── system.cfg │ │ │ └── test.dbg │ ├── DebugSymbolsFuzz.cpp │ └── DebugSymbolsTest.cpp ├── src │ ├── devices │ │ ├── InterruptStatus.cpp │ │ ├── InterruptStatus.h │ │ ├── TerminalWrapper.h │ │ ├── Rom.h │ │ ├── Ram.h │ │ ├── Rom.cpp │ │ ├── Ram.cpp │ │ ├── Uart.h │ │ ├── Via.h │ │ ├── TerminalWrapper.cpp │ │ ├── Uart.cpp │ │ └── Via.cpp │ ├── cpu │ │ ├── Interrupt.h │ │ ├── SpeedLimit.h │ │ ├── DebugSymbols.h │ │ ├── BuildConfig.h │ │ ├── SpeedLimit.cpp │ │ ├── Stack.h │ │ ├── SystemBus.h │ │ ├── Addressing.h │ │ ├── Binary.h │ │ ├── Cpu65816Debugger.h │ │ ├── OpCode.h │ │ ├── Stack.cpp │ │ ├── opcodes │ │ │ ├── OpCode_STX.cpp │ │ │ ├── OpCode_STY.cpp │ │ │ ├── OpCode_STZ.cpp │ │ │ ├── OpCode_LDX.cpp │ │ │ ├── OpCode_LDY.cpp │ │ │ ├── OpCode_BIT.cpp │ │ │ ├── OpCode_JumpReturn.cpp │ │ │ ├── OpCode_Misc.cpp │ │ │ ├── OpCode_Interrupt.cpp │ │ │ ├── OpCode_Branch.cpp │ │ │ ├── OpCode_StatusReg.cpp │ │ │ ├── OpCode_TSB_TRB.cpp │ │ │ ├── OpCode_ASL.cpp │ │ │ ├── OpCode_LSR.cpp │ │ │ ├── OpCode_STA.cpp │ │ │ ├── OpCode_CPX_CPY.cpp │ │ │ ├── OpCode_ROL.cpp │ │ │ ├── OpCode_ROR.cpp │ │ │ └── OpCode_CMP.cpp │ │ ├── SystemBusDevice.cpp │ │ ├── CpuStatus.h │ │ ├── DebugSymbols.cpp │ │ ├── SystemBusDevice.h │ │ └── SystemBus.cpp │ └── logging │ │ ├── Log.h │ │ └── Log.cpp ├── README.md └── CMakeLists.txt ├── hardware ├── kicad │ ├── .gitignore │ ├── i2c-test-board │ │ ├── fp-lib-table │ │ ├── sym-lib-table │ │ ├── gerber │ │ │ └── i2c-test-board-rev-1.0.zip │ │ └── i2c-test-board.kicad_prl │ └── 65c816-test-board │ │ ├── gerber │ │ └── 65816-test-board-rev-1.0.zip │ │ ├── sym-lib-table │ │ ├── generated_parts.lib │ │ └── 65816-computer.kicad_prl └── programmable_logic │ ├── .gitignore │ ├── address_decode │ ├── Makefile │ └── address_decode.pld │ ├── README.md │ └── pin_to_csv.py ├── .github ├── Dockerfile └── workflows │ └── build.yml └── README.md /software/kernel/ramfs.s: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /software/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest -------------------------------------------------------------------------------- /software/ramfs/test.txt: -------------------------------------------------------------------------------- 1 | Hello world 2 | -------------------------------------------------------------------------------- /rom/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.o 3 | *.lbl 4 | 5 | -------------------------------------------------------------------------------- /software/ramfs/etc/passwd: -------------------------------------------------------------------------------- 1 | root:x:0:0:root:/root:/bin/sh 2 | -------------------------------------------------------------------------------- /software/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.o 3 | *.img 4 | *.lbl 5 | *.dbg 6 | -------------------------------------------------------------------------------- /emulator/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cmake-build-debug/ 3 | .idea 4 | Makefile 5 | -------------------------------------------------------------------------------- /hardware/kicad/.gitignore: -------------------------------------------------------------------------------- 1 | *.sch-bak 2 | *.bck 3 | *.bak 4 | *-backups 5 | fp-info-cache 6 | -------------------------------------------------------------------------------- /hardware/programmable_logic/.gitignore: -------------------------------------------------------------------------------- 1 | *.chp 2 | *.fus 3 | *.pin 4 | *.csv 5 | *.lib 6 | *.jed 7 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/test.lbl: -------------------------------------------------------------------------------- 1 | al 00E004 .test_this_will_fail 2 | al 00E001 .test_this_will_pass 3 | al 00E000 .test_setup 4 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/65816-computer/HEAD/emulator/test/resources/testing_example/test.bin -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/example.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/65816-computer/HEAD/emulator/test/resources/testing_example/example.o -------------------------------------------------------------------------------- /hardware/kicad/i2c-test-board/fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (lib (name "project")(type "KiCad")(uri "${KIPRJMOD}/project.pretty")(options "")(descr "")) 3 | ) 4 | -------------------------------------------------------------------------------- /hardware/kicad/i2c-test-board/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (lib (name "project")(type "KiCad")(uri "${KIPRJMOD}/project.kicad_sym")(options "")(descr "")) 3 | ) 4 | -------------------------------------------------------------------------------- /hardware/kicad/i2c-test-board/gerber/i2c-test-board-rev-1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/65816-computer/HEAD/hardware/kicad/i2c-test-board/gerber/i2c-test-board-rev-1.0.zip -------------------------------------------------------------------------------- /hardware/kicad/65c816-test-board/gerber/65816-test-board-rev-1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/65816-computer/HEAD/hardware/kicad/65c816-test-board/gerber/65816-test-board-rev-1.0.zip -------------------------------------------------------------------------------- /rom/address.s: -------------------------------------------------------------------------------- 1 | ; address.s: Base addresses for I/O devices etc. 2 | .export VIA_BASE 3 | .export UART_BASE 4 | .export BOOTLOADER_BASE 5 | 6 | BOOTLOADER_BASE = $7c00 7 | VIA_BASE = $c000 8 | UART_BASE = $c200 9 | -------------------------------------------------------------------------------- /software/kernel_tmp/kernel_tmp.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | PRG: start = $010000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 3 | } 4 | 5 | SEGMENTS { 6 | CODE: load = PRG, type = ro, start = $010000; 7 | } 8 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/example.s: -------------------------------------------------------------------------------- 1 | .segment "CODE" 2 | 3 | test_setup: ; do nothing 4 | rts 5 | 6 | test_this_will_pass: 7 | lda #0 8 | rts 9 | 10 | test_this_will_fail: 11 | lda #1 12 | rts 13 | -------------------------------------------------------------------------------- /hardware/kicad/65c816-test-board/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (lib (name "project")(type "KiCad")(uri "${KIPRJMOD}/project.kicad_sym")(options "")(descr "")) 3 | (lib (name "generated_parts")(type "Legacy")(uri "${KIPRJMOD}/generated_parts.lib")(options "")(descr "")) 4 | ) 5 | -------------------------------------------------------------------------------- /emulator/src/devices/InterruptStatus.cpp: -------------------------------------------------------------------------------- 1 | #include "InterruptStatus.h" 2 | 3 | InterruptStatus::InterruptStatus() { 4 | active = false; 5 | } 6 | 7 | bool InterruptStatus::get() { 8 | return active; 9 | } 10 | 11 | void InterruptStatus::set(bool val) { 12 | active = val; 13 | } 14 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/README.md: -------------------------------------------------------------------------------- 1 | # Example test project 2 | 3 | This is an assembled test project: the emulator is able to run tests in the linker binary output (test.bin), discovered by parsing the label file (test.lbl). 4 | 5 | Source and build script are provided for reference. 6 | 7 | -------------------------------------------------------------------------------- /emulator/test/DebugSymbolsFuzz.cpp: -------------------------------------------------------------------------------- 1 | #include "DebugSymbols.h" 2 | 3 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 4 | auto symbols = DebugSymbols(); 5 | auto dataStr = std::string(reinterpret_cast(data), size); 6 | symbols.loadLabelFile(dataStr); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /software/hexdump/README.md: -------------------------------------------------------------------------------- 1 | # Hexdump 2 | 3 | This is a test program which outputs a hexdump of itself, for checking that data is loading correctly from SD card. 4 | 5 | The actual program code is 214 bytes, so it will run correctly as long as the first 512-byte block loads from disk. The remainder of the file is plain ASCII. 6 | 7 | -------------------------------------------------------------------------------- /.github/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update && \ 4 | DEBIAN_FRONTEND=noninteractive apt-get install -y \ 5 | build-essential \ 6 | cmake \ 7 | clang \ 8 | cc65 \ 9 | libgtest-dev \ 10 | libboost-dev \ 11 | libboost-program-options-dev \ 12 | libboost-filesystem-dev && \ 13 | rm -rf /var/lib/apt/lists/* 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: push 3 | jobs: 4 | build: 5 | runs-on: ubuntu-22.04 6 | steps: 7 | - name: Check out code 8 | uses: actions/checkout@v3 9 | - name: Build docker image 10 | run: docker build -t local - < .github/Dockerfile 11 | - name: Run build 12 | run: docker run -i -v $PWD:/build -w/build local ./build.sh 13 | 14 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run clean 2 | 3 | CA65=/usr/bin/ca65 4 | LD65=/usr/bin/ld65 5 | 6 | test.bin: example.s system.cfg 7 | $(CA65) --cpu 65816 --debug-info example.s 8 | $(LD65) -o test.bin \ 9 | -C system.cfg \ 10 | --dbgfile test.dbg \ 11 | -Ln test.lbl \ 12 | example.o 13 | 14 | clean: 15 | rm -f test.bin example.o test.dbg test.lbl 16 | 17 | -------------------------------------------------------------------------------- /software/snake/snake.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | PRG: start = $010000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 3 | RAM: start = $012000, size = $2000, type = rw, file = ""; 4 | } 5 | 6 | SEGMENTS { 7 | START: load = PRG, type = ro, start = $010000; 8 | CODE: load = PRG, type = ro, start = $010003; 9 | BSS: load = RAM, type = bss; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /software/hexdump/hexdump.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | PRG: start = $010000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 3 | RAM: start = $012000, size = $2000, type = rw, file = ""; 4 | } 5 | 6 | SEGMENTS { 7 | CODE: load = PRG, type = ro, start = $010000; 8 | FILL: load = PRG, type = ro, start = $010100; 9 | BSS: load = RAM, type = bss; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /software/bootloader/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run clean 2 | 3 | CA65=/usr/bin/ca65 4 | LD65=/usr/bin/ld65 5 | 6 | SOURCES = $(wildcard *.s) 7 | OBJECTS = $(SOURCES:.s=.o) 8 | 9 | boot.bin: $(OBJECTS) boot.cfg 10 | $(LD65) -o boot.bin \ 11 | -C boot.cfg \ 12 | $(OBJECTS) 13 | 14 | %.o: %.s 15 | $(CA65) --feature string_escapes --cpu 65816 --debug-info $< 16 | 17 | clean: 18 | rm -f boot.bin $(OBJECTS) 19 | 20 | -------------------------------------------------------------------------------- /emulator/src/devices/InterruptStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_INTERRUPTSTATUS_H 2 | #define EMULATOR_INTERRUPTSTATUS_H 3 | 4 | /* 5 | * Wrap active/inactive status of an interrupt, to allow CPU to poll. 6 | */ 7 | class InterruptStatus { 8 | public: 9 | InterruptStatus(); 10 | 11 | bool get(); 12 | 13 | void set(bool val); 14 | 15 | private: 16 | bool active; 17 | }; 18 | 19 | 20 | #endif //EMULATOR_INTERRUPTSTATUS_H 21 | -------------------------------------------------------------------------------- /software/hexdump/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run clean 2 | 3 | CA65=/usr/bin/ca65 4 | LD65=/usr/bin/ld65 5 | 6 | SOURCES = $(wildcard *.s) 7 | OBJECTS = $(SOURCES:.s=.o) 8 | 9 | hexdump.bin: $(OBJECTS) hexdump.cfg 10 | $(LD65) -o hexdump.bin \ 11 | -C hexdump.cfg \ 12 | -Ln hexdump.lbl \ 13 | $(OBJECTS) 14 | 15 | %.o: %.s 16 | $(CA65) --cpu 65816 --debug-info $< 17 | 18 | clean: 19 | rm -f hexdump.bin hexdump.lbl $(OBJECTS) 20 | 21 | -------------------------------------------------------------------------------- /software/kernel/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run clean 2 | 3 | CA65=/usr/bin/ca65 4 | LD65=/usr/bin/ld65 5 | 6 | SOURCES = $(wildcard *.s) $(wildcard device/*.s) 7 | OBJECTS = $(SOURCES:.s=.o) 8 | 9 | kernel.bin: $(OBJECTS) system.cfg 10 | $(LD65) -o kernel.bin \ 11 | -C system.cfg \ 12 | $(OBJECTS) 13 | 14 | %.o: %.s 15 | $(CA65) --feature string_escapes --cpu 65816 --debug-info $< 16 | 17 | clean: 18 | rm -f kernel.bin $(OBJECTS) 19 | -------------------------------------------------------------------------------- /software/snake/test.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $00, size = $0100, type = rw, file = ""; 3 | RAM: start = $0200, size = $7e00, type = rw, file = ""; 4 | PRG: start = $e000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp; 9 | BSS: load = RAM, type = bss; 10 | CODE: load = PRG, type = ro, start = $e000; 11 | } 12 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/system.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $00, size = $0100, type = rw, file = ""; 3 | RAM: start = $0100, size = $7e00, type = rw, file = ""; 4 | PRG: start = $e000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp; 9 | BSS: load = RAM, type = bss; 10 | CODE: load = PRG, type = ro, start = $e000; 11 | } 12 | -------------------------------------------------------------------------------- /emulator/src/devices/TerminalWrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_TERMINALWRAPPER_H 2 | #define EMULATOR_TERMINALWRAPPER_H 3 | 4 | 5 | class TerminalWrapper { 6 | public: 7 | TerminalWrapper(bool skip); 8 | 9 | void writeChar(unsigned char charToWrite) const; 10 | 11 | bool readChar(unsigned char &res); 12 | 13 | ~TerminalWrapper(); 14 | 15 | private: 16 | int sockfd; 17 | int connfd; 18 | }; 19 | 20 | 21 | #endif //EMULATOR_TERMINALWRAPPER_H 22 | -------------------------------------------------------------------------------- /software/kernel_tmp/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run clean 2 | 3 | CA65=/usr/bin/ca65 4 | LD65=/usr/bin/ld65 5 | 6 | SOURCES = $(wildcard *.s) 7 | OBJECTS = $(SOURCES:.s=.o) 8 | 9 | kernel_tmp.bin: $(OBJECTS) kernel_tmp.cfg 10 | $(LD65) -o kernel_tmp.bin \ 11 | -Ln kernel_tmp.lbl \ 12 | -C kernel_tmp.cfg \ 13 | $(OBJECTS) 14 | 15 | %.o: %.s 16 | $(CA65) --feature string_escapes --cpu 65816 --debug-info $< 17 | 18 | clean: 19 | rm -f kernel_tmp.bin $(OBJECTS) 20 | 21 | -------------------------------------------------------------------------------- /rom/system.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $00, size = $0100, type = rw, file = ""; 3 | RAM: start = $0200, size = $7d00, type = rw, file = ""; 4 | PRG: start = $e000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp; 9 | BSS: load = RAM, type = bss; 10 | CODE: load = PRG, type = ro, start = $e000; 11 | VECTORS: load = PRG, type = ro, start = $ffe0; 12 | } 13 | -------------------------------------------------------------------------------- /software/kernel/system.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $00, size = $0100, type = rw, file = ""; 3 | RAM: start = $0100, size = $7e00, type = rw, file = ""; 4 | PRG: start = $e000, size = $2000, type = ro, file = %O, fill = yes, fillval = $00; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp; 9 | BSS: load = RAM, type = bss; 10 | CODE: load = PRG, type = ro, start = $e000; 11 | VECTORS: load = PRG, type = ro, start = $ffe0; 12 | } 13 | -------------------------------------------------------------------------------- /software/bootloader/boot.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $00, size = $0100, type = rw, file = ""; 3 | RAM: start = $7e00, size = $0200, type = rw, file = ""; 4 | PRG: start = $7c00, size = $0200, type = rw, file = %O, fill = yes, fillval = $00; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp; 9 | BSS: load = RAM, type = bss; 10 | CODE: load = PRG, type = rw, start = $7c00; 11 | SIGNATURE: load = PRG, type = rw, start = $7dfe; 12 | } 13 | -------------------------------------------------------------------------------- /software/kernel_tmp/kernel_tmp.s: -------------------------------------------------------------------------------- 1 | ; kernel_tmp.s: A temporary placeholder program to test the boot process. 2 | 3 | ROM_PRINT_STRING := $02 4 | 5 | .segment "CODE" 6 | .a16 7 | .i16 8 | jmp __main 9 | 10 | __main: 11 | ; set data bank register to equal program bank register 12 | phk 13 | plb 14 | 15 | ; Print test string 16 | ldx #(test_string & $ffff) 17 | cop ROM_PRINT_STRING 18 | 19 | ; Halt 20 | stp 21 | 22 | test_string: .asciiz "Hello, world!\r\n" 23 | -------------------------------------------------------------------------------- /hardware/programmable_logic/address_decode/Makefile: -------------------------------------------------------------------------------- 1 | GALETTE=galette 2 | MINIPRO=minipro 3 | 4 | all: address_decode.jed 5 | 6 | address_decode.jed: address_decode.pld 7 | $(GALETTE) $^ 8 | ../pin_to_csv.py address_decode.pin address_decode.csv --name Address_Decoder 9 | kipart --overwrite -r generic --output address_decode.lib address_decode.csv 10 | 11 | clean: 12 | rm -f *.jed *.fus *.chp *.pin *.csv *.lib 13 | 14 | # Write JED to address decode PLD 15 | write_address_decode: address_decode.jed 16 | $(MINIPRO) -p ATF22V10CQZ -w address_decode.jed 17 | 18 | -------------------------------------------------------------------------------- /software/kernel/main.s: -------------------------------------------------------------------------------- 1 | ; main.s: entry point for kernel 2 | .import uart_init, uart_print_char, uart_recv_char, uart_printz, panic, __forty_two, shell_main 3 | .export __main 4 | 5 | .segment "CODE" 6 | main: 7 | clc ; switch to native mode 8 | xce 9 | .a16 ; use 16-bit accumulator and index registers 10 | .i16 11 | rep #%00110000 12 | lda #$3000 ; set up stack, direct page at $3000 13 | tcs 14 | tcd 15 | 16 | ; kick off the shell 17 | jsr shell_main 18 | jsr panic 19 | 20 | test_string: .asciiz "Hello, world!" 21 | -------------------------------------------------------------------------------- /emulator/src/cpu/Interrupt.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_INTERRUPT_H 2 | #define EMULATOR_INTERRUPT_H 3 | 4 | #define INTERRUPT_VECTOR_NATIVE_COP 0xFFE4 5 | #define INTERRUPT_VECTOR_NATIVE_BRK 0xFFE6 6 | #define INTERRUPT_VECTOR_NATIVE_ABORT 0xFFE8 7 | #define INTERRUPT_VECTOR_NATIVE_NMI 0xFFEA 8 | #define INTERRUPT_VECTOR_NATIVE_IRQ 0xFFEE 9 | 10 | #define INTERRUPT_VECTOR_EMULATION_COP 0xFFF4 11 | #define INTERRUPT_VECTOR_EMULATION_ABORT 0xFFF8 12 | #define INTERRUPT_VECTOR_EMULATION_NMI 0xFFFA 13 | #define INTERRUPT_VECTOR_EMULATION_RESET 0xFFFC 14 | #define INTERRUPT_VECTOR_EMULATION_BRK_IRQ 0xFFFE 15 | 16 | #endif //EMULATOR_INTERRUPT_H 17 | -------------------------------------------------------------------------------- /emulator/src/cpu/SpeedLimit.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_SPEEDLIMIT_H 2 | #define EMULATOR_SPEEDLIMIT_H 3 | 4 | 5 | #include 6 | #include 7 | 8 | /** 9 | * Rate-limit CPU execution to match speed of actual hardware. 10 | */ 11 | class SpeedLimit { 12 | 13 | public: 14 | explicit SpeedLimit(uint64_t cyclesPerSecond); 15 | 16 | void apply(uint64_t cyclesExecuted); 17 | 18 | private: 19 | 20 | std::chrono::time_point lastTime; 21 | uint64_t cycleCount; 22 | std::chrono::nanoseconds cycleDuration; 23 | }; 24 | 25 | 26 | #endif //EMULATOR_SPEEDLIMIT_H 27 | -------------------------------------------------------------------------------- /emulator/src/devices/Rom.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_ROM_H 2 | #define EMULATOR_ROM_H 3 | 4 | #include 5 | #include "SystemBusDevice.h" 6 | 7 | #define ROM_SIZE 8192 8 | /** 9 | * Emulate ROM, backed by a file 10 | */ 11 | class Rom : public SystemBusDevice { 12 | private: 13 | uint8_t rom[ROM_SIZE]; 14 | 15 | public: 16 | explicit Rom(const std::string& filename); 17 | ~Rom() override; 18 | 19 | void storeByte(const Address &, uint8_t) override; 20 | uint8_t readByte(const Address &) override; 21 | bool decodeAddress(const Address &, Address &) override; 22 | private: 23 | uint16_t vector_at(uint16_t address); 24 | }; 25 | #endif //EMULATOR_ROM_H 26 | -------------------------------------------------------------------------------- /emulator/src/devices/Ram.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_RAM_H 2 | #define EMULATOR_RAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * Emulate RAM 10 | */ 11 | class Ram : public SystemBusDevice { 12 | private: 13 | uint8_t *mRam; 14 | 15 | public: 16 | 17 | explicit Ram(uint8_t); 18 | ~Ram() override; 19 | 20 | void storeByte(const Address &, uint8_t) override; 21 | uint8_t readByte(const Address &) override; 22 | bool decodeAddress(const Address &, Address &) override; 23 | void loadFromFile(const std::string& fileName, const Address& start, size_t size); 24 | 25 | size_t mRamSize; 26 | }; 27 | 28 | #endif //EMULATOR_RAM_H 29 | -------------------------------------------------------------------------------- /software/build_tools/test_ramfs.py: -------------------------------------------------------------------------------- 1 | from build_tools.ramfs import RamFs 2 | 3 | 4 | def test_empty_filesystem(): 5 | filesystem = RamFs() 6 | files = filesystem.ls() 7 | assert len(files) == 0 8 | 9 | 10 | def test_mkdir(): 11 | filesystem = RamFs() 12 | filesystem.mkdir("foo") 13 | filesystem.mkdir("bar") 14 | filesystem.mkdir("baz") 15 | filesystem.mkdir("baz/quux") 16 | files = filesystem.ls() 17 | assert len(files) == 3 18 | files = filesystem.ls("baz/") 19 | assert len(files) == 1 20 | 21 | 22 | def test_add_file(): 23 | filesystem = RamFs() 24 | filesystem.mkdir("etc") 25 | content = b"Hello world " * 45 26 | filesystem.add_file("etc/foobar", content) 27 | inode = filesystem.stat("etc/foobar") 28 | assert inode.size_bytes == 540 29 | retrieved_content = filesystem.get_file("etc/foobar") 30 | assert retrieved_content == content 31 | -------------------------------------------------------------------------------- /software/snake/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run clean test 2 | 3 | CA65=/usr/bin/ca65 4 | LD65=/usr/bin/ld65 5 | 6 | # Source/obj files excluding those used for tests 7 | SRC_FILES = $(filter-out $(wildcard *_test.s), $(wildcard *.s)) 8 | OBJ_FILES = $(SRC_FILES:.s=.o) 9 | 10 | default: snake.bin 11 | 12 | run: snake.bin 13 | (cd ../../ &&./emulator/build/release/emulator --kernel software/snake/snake.bin --rom rom/rom_emulator.bin --trace) 14 | 15 | test: random_test.bin 16 | ../../emulator/build/release/emulator --test random_test.bin 17 | 18 | %_test.bin: %_test.o %.o 19 | $(LD65) -o $@ \ 20 | -C test.cfg \ 21 | -Ln $(@:.bin=.lbl) \ 22 | $^ 23 | 24 | snake.bin: $(OBJ_FILES) snake.cfg 25 | $(LD65) -o snake.bin \ 26 | -C snake.cfg \ 27 | -Ln snake.lbl \ 28 | $(OBJ_FILES) 29 | 30 | %.o: %.s 31 | $(CA65) --feature string_escapes --cpu 65816 --debug-info $< 32 | 33 | clean: 34 | rm -f *.bin *.o *.lbl 35 | -------------------------------------------------------------------------------- /emulator/src/cpu/DebugSymbols.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_DEBUGSYMBOLS_H 2 | #define EMULATOR_DEBUGSYMBOLS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "SystemBusDevice.h" 8 | 9 | class DebugSymbols { 10 | 11 | public: 12 | DebugSymbols(); 13 | 14 | /** 15 | * Locate and parse debug info for an image (eg. .dbg or .lbl files). 16 | * 17 | * @param imagePath Path to binary being added to memory. 18 | */ 19 | void loadSymbolsForBinary(const boost::filesystem::path &imagePath); 20 | 21 | /** 22 | * Parse string containing labels - VICE format 23 | **/ 24 | void loadLabelFile(const std::string &content); 25 | 26 | std::map labels; 27 | 28 | std::map labelsReverse; 29 | private: 30 | void loadLabelLine(const std::string &line); 31 | }; 32 | 33 | 34 | #endif //EMULATOR_DEBUGSYMBOLS_H 35 | -------------------------------------------------------------------------------- /emulator/test/DebugSymbolsTest.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "DebugSymbols.h" 4 | 5 | TEST(DebugSymbols, CanParseFile) { 6 | auto symbols = DebugSymbols(); 7 | auto testRes = boost::filesystem::path(TEST_RESOURCES_PATH); 8 | auto testImage = testRes.append("testing_example/test.bin"); 9 | symbols.loadSymbolsForBinary(testImage); 10 | ASSERT_EQ(3, symbols.labels.size()); 11 | ASSERT_TRUE(symbols.labels.contains("test_this_will_pass")); 12 | ASSERT_EQ(0xe001, symbols.labels["test_this_will_pass"].getOffset()); 13 | ASSERT_TRUE(symbols.labels.contains("test_this_will_fail")); 14 | ASSERT_EQ(0xe004, symbols.labels["test_this_will_fail"].getOffset()); 15 | ASSERT_EQ(0x00, symbols.labels["test_this_will_fail"].getBank()); 16 | } 17 | 18 | TEST(DebugSymbols, CanNameAddress) { 19 | auto symbols = DebugSymbols(); 20 | symbols.loadLabelFile("al 00E000 .method1\nal 00E0FF .method2"); 21 | ASSERT_EQ("method2", symbols.labelsReverse[0xe0ff]); 22 | ASSERT_EQ(2, symbols.labels.size()); 23 | } 24 | -------------------------------------------------------------------------------- /emulator/src/devices/Rom.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Rom.h" 3 | 4 | #define LOG_TAG "Rom" 5 | 6 | #include "Log.h" 7 | 8 | Rom::~Rom() = default; 9 | 10 | Rom::Rom(const std::string &file_name) { 11 | FILE *fp = fopen(file_name.c_str(), "rb"); 12 | assert(fp != nullptr); 13 | size_t bytes_read = fread(this->rom, 1, ROM_SIZE, fp); 14 | fclose(fp); 15 | assert(bytes_read == ROM_SIZE); 16 | Log::err(LOG_TAG).str("Rom initialised from ").str(file_name.c_str()).show(); 17 | } 18 | 19 | void Rom::storeByte(const Address &address, uint8_t value) { 20 | // Do absolutely nothing 21 | } 22 | 23 | uint8_t Rom::readByte(const Address &address) { 24 | return this->rom[address.getOffset() % ROM_SIZE]; 25 | } 26 | 27 | bool Rom::decodeAddress(const Address &in, Address &out) { 28 | out = in; 29 | return in.getBank() == 0 && (in.getOffset() & 0xE000) == 0xE000; 30 | } 31 | 32 | uint16_t Rom::vector_at(uint16_t offset) { 33 | return this->readByte(Address(0x00, offset)) + (this->readByte(Address(0x00, offset + 1)) << 8); 34 | } 35 | -------------------------------------------------------------------------------- /rom/vectors.s: -------------------------------------------------------------------------------- 1 | ; vectors.s: Interrupt vectors for ROM 2 | .import reset, cop_handler 3 | 4 | .segment "CODE" 5 | unused_interrupt: ; Probably make this into a crash. 6 | rti 7 | 8 | .segment "VECTORS" 9 | ; native mode interrupt vectors 10 | .word unused_interrupt ; Reserved 11 | .word unused_interrupt ; Reserved 12 | .word cop_handler ; COP 13 | .word unused_interrupt ; BRK 14 | .word unused_interrupt ; Abort 15 | .word unused_interrupt ; NMI 16 | .word unused_interrupt ; Reserved 17 | .word unused_interrupt ; IRQ 18 | 19 | ; emulation mode interrupt vectors 20 | .word unused_interrupt ; Reserved 21 | .word unused_interrupt ; Reserved 22 | .word unused_interrupt ; COP 23 | .word unused_interrupt ; Reserved 24 | .word unused_interrupt ; Abort 25 | .word unused_interrupt ; NMI 26 | .word reset ; Reset 27 | .word unused_interrupt ; IRQ/BRK 28 | -------------------------------------------------------------------------------- /software/Makefile: -------------------------------------------------------------------------------- 1 | EMULATOR=../emulator/cmake-build-debug/emulator 2 | 3 | .PHONY: bootloader kernel kernel_tmp snake hexdump clean all 4 | 5 | all: kernel disk.img ramfs.bin snake 6 | 7 | bootloader: 8 | make -C bootloader boot.bin 9 | 10 | kernel_tmp: 11 | make -C kernel_tmp kernel_tmp.bin 12 | 13 | kernel: 14 | make -C kernel kernel.bin 15 | 16 | snake: 17 | make -C snake snake.bin 18 | 19 | hexdump: 20 | make -C hexdump hexdump.bin 21 | 22 | ramfs.bin: 23 | (cd ramfs && mkdir -p bin/ dev/ && python3 ../build_tools/ramfs.py --create --file ../ramfs.bin *) 24 | 25 | disk.img: bootloader kernel_tmp 26 | cat bootloader/boot.bin kernel_tmp/kernel_tmp.bin > disk.img 27 | 28 | snake.img: bootloader snake 29 | cat bootloader/boot.bin snake/snake.bin > snake.img 30 | 31 | hexdump.img: bootloader hexdump 32 | cat bootloader/boot.bin hexdump/hexdump.bin > hexdump.img 33 | 34 | clean: 35 | make -C bootloader clean 36 | make -C kernel clean 37 | make -C kernel_tmp clean 38 | make -C hexdump clean 39 | rm -f ramfs.bin disk.img 40 | 41 | run: kernel ramfs.bin 42 | $(EMULATOR) kernel/kernel.bin ramfs.bin 43 | 44 | -------------------------------------------------------------------------------- /hardware/programmable_logic/README.md: -------------------------------------------------------------------------------- 1 | # Programmable logic for 65816 computer 2 | 3 | This project PLD's to implement some functionality. 4 | 5 | ## PLD definitions 6 | 7 | - `address_decode` - Provides chip select, read and write signals based on a memory address. 8 | 9 | ## Required tools 10 | 11 | You will need quite a few tools to work with these files. Take a look at the `Makefile` in each directory to see how they are used. 12 | 13 | ### galette 14 | 15 | Galette compiles the logic functions to a binary file which can be written to the PLD. See [simon-frankau/galette](https://github.com/simon-frankau/galette). 16 | 17 | ### pin_to_csv 18 | 19 | This converts the output of galette to an input for KiPart. It was written for this project, and is included in the repo. 20 | 21 | ### KiPart 22 | 23 | KiPart generates KiCAD symbols for the programmed PLD's. See [devbisme/KiPart](https://github.com/devbisme/KiPart) 24 | 25 | ``` 26 | pip3 install kipart 27 | ``` 28 | 29 | ### minipro 30 | 31 | This is a command-line utility which I use to program these chips. See [DavidGriffith/minipro/](https://gitlab.com/DavidGriffith/minipro/) 32 | 33 | -------------------------------------------------------------------------------- /hardware/programmable_logic/address_decode/address_decode.pld: -------------------------------------------------------------------------------- 1 | GAL22V10 2 | AddrDecode 3 | 4 | Clock RW VPA VDA A19 A18 A17 A16 A15 A14 A13 GND 5 | S0 /RAM0 /RAM1 /ROM /IO /RD /WR S1 S2 S3 S4 VCC 6 | 7 | ; ROM is the top 8 KiB of bank 0. 8 | ; prefix is 0000 111xxxxx xxxxxxxx 9 | ROM = /A19*/A18*/A17*/A16*A15*A14*A13 10 | 11 | ; IO addresses are mapped below ROM in bank 0. 12 | ; prefix is 0000 110xxxxx xxxxxxxx 13 | IO = /A19*/A18*/A17*/A16*A15*A14*/A13 14 | 15 | ; RAM0 is selected for any address that starts with a 0, but does *not* match 0000 11xxxxxx xxxxxxxx 16 | RAM0 = /A19*A18 + /A19*A17 + /A19*A16 + /A19*/A15 + /A19*/A14 17 | 18 | ; RAM1 is selected for any address that matches 1xxx xxxxxxxx xxxxxxxx 19 | RAM1 = A19 20 | 21 | ; Qualify reads/writes with clock 22 | RD = Clock*RW 23 | WR = Clock*/RW 24 | 25 | ; S0-S4 are inputs and outputs with no defined use. 26 | S1 = S0 27 | S2 = S0 28 | S3 = S0 29 | S4 = S0 30 | 31 | DESCRIPTION 32 | 33 | Address decode logic for 65C816 computer. 34 | 35 | ROM is top 8K of bank 0, I/O is 8k below that, everything else is RAM (two chips). 36 | 37 | -------------------------------------------------------------------------------- /emulator/src/cpu/BuildConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | /** 21 | * The following define enables building the Cpu65816 for 65C02 emulation. 22 | * Note that this will enable 65C02 emulation but NOT R65C02 emulation. 23 | * The R65C02 cpu has several different opcodes and is not compatible 24 | * with the 65C02. 25 | * 26 | * This define enables some minor differences in cycles counting 27 | * for some opcodes. 28 | * */ 29 | //#define EMU_65C02 -------------------------------------------------------------------------------- /hardware/kicad/65c816-test-board/generated_parts.lib: -------------------------------------------------------------------------------- 1 | EESchema-LIBRARY Version 2.3 2 | DEF Address_Decoder U 0 40 Y Y 1 L N 3 | F0 "U" 150 250 60 H V R CNN 4 | F1 "Address_Decoder" 150 150 60 H V R CNN 5 | F2 "" 150 50 60 H I R CNN 6 | F3 "" 150 -150 60 H I R CNN 7 | DRAW 8 | X Clock 1 0 0 200 R 50 50 1 1 I 9 | X RW 2 0 -100 200 R 50 50 1 1 I 10 | X VPA 3 0 -200 200 R 50 50 1 1 I 11 | X VDA 4 0 -300 200 R 50 50 1 1 I 12 | X A19 5 0 -400 200 R 50 50 1 1 I 13 | X A18 6 0 -500 200 R 50 50 1 1 I 14 | X A17 7 0 -600 200 R 50 50 1 1 I 15 | X A16 8 0 -700 200 R 50 50 1 1 I 16 | X A15 9 0 -800 200 R 50 50 1 1 I 17 | X A14 10 0 -900 200 R 50 50 1 1 I 18 | X A13 11 0 -1000 200 R 50 50 1 1 I 19 | X S0 13 0 -1100 200 R 50 50 1 1 I 20 | X GND 12 700 -1700 200 U 50 50 1 1 W 21 | X S4 23 1400 -1000 200 L 50 50 1 1 O 22 | X S3 22 1400 -900 200 L 50 50 1 1 O 23 | X S2 21 1400 -800 200 L 50 50 1 1 O 24 | X S1 20 1400 -700 200 L 50 50 1 1 O 25 | X ~WR 19 1400 -600 200 L 50 50 1 1 O 26 | X ~RD 18 1400 -500 200 L 50 50 1 1 O 27 | X ~IO 17 1400 -400 200 L 50 50 1 1 O 28 | X ~ROM 16 1400 -300 200 L 50 50 1 1 O 29 | X ~RAM1 15 1400 -200 200 L 50 50 1 1 O 30 | X ~RAM0 14 1400 -100 200 L 50 50 1 1 O 31 | X VCC 24 700 600 200 D 50 50 1 1 W 32 | S 200 400 1200 -1500 1 1 0 f 33 | ENDDRAW 34 | ENDDEF 35 | -------------------------------------------------------------------------------- /software/bootloader/boot.s: -------------------------------------------------------------------------------- 1 | ; boot.s: 512-byte bootloader. This utilizes services defined in ROM. 2 | ROM_PRINT_STRING := $02 3 | ROM_READ_DISK := $03 4 | 5 | .segment "CODE" 6 | .a16 7 | .i16 8 | jmp code_start 9 | 10 | code_start: 11 | ; load kernel 12 | ldx #loading_kernel ; Print loading message (kernel) 13 | cop ROM_PRINT_STRING 14 | phb ; Save current data bank register 15 | .a8 ; Set data bank register to 1 16 | php 17 | sep #%00100000 18 | lda #$01 19 | pha 20 | plb 21 | plp 22 | .a16 23 | ldx #$0000 ; lower 2 bytes of destination address 24 | lda #$01 ; block number 25 | ldy #$10 ; number of blocks to read - 8 KiB 26 | cop ROM_READ_DISK ; read kernel to RAM 27 | plb ; Restore previous data bank register 28 | 29 | ldx #boot ; Print boot message 30 | cop ROM_PRINT_STRING 31 | jml $010000 32 | 33 | loading_kernel: .asciiz "Loading kernel\r\n" 34 | boot: .asciiz "Booting\r\n" 35 | 36 | .segment "SIGNATURE" 37 | wdm $42 ; Ensure x86 systems don't recognise this as bootable. 38 | -------------------------------------------------------------------------------- /software/kernel/interrupt.s: -------------------------------------------------------------------------------- 1 | .import __main 2 | 3 | .segment "CODE" 4 | reset: 5 | clc ; switch to native mode 6 | xce 7 | jmp __main ; go to main method 8 | 9 | nmi_interrupt: 10 | rti 11 | 12 | irq_interrrupt: 13 | rti 14 | 15 | cop_interrupt: 16 | rti 17 | 18 | unused_interrupt: ; Probably make this into a crash. 19 | rti 20 | 21 | .segment "VECTORS" 22 | ; native mode interrupt vectors 23 | .word unused_interrupt ; Reserved 24 | .word unused_interrupt ; Reserved 25 | .word cop_interrupt ; COP 26 | .word unused_interrupt ; BRK 27 | .word unused_interrupt ; Abort 28 | .word nmi_interrupt ; NMI 29 | .word unused_interrupt ; Reserved 30 | .word irq_interrrupt ; IRQ 31 | 32 | ; emulation mode interrupt vectors 33 | .word unused_interrupt ; Reserved 34 | .word unused_interrupt ; Reserved 35 | .word unused_interrupt ; COP 36 | .word unused_interrupt ; Reserved 37 | .word unused_interrupt ; Abort 38 | .word unused_interrupt ; NMI 39 | .word reset ; Reset 40 | .word unused_interrupt ; IRQ/BRK 41 | -------------------------------------------------------------------------------- /emulator/src/devices/Ram.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Ram.h" 4 | #include "Log.h" 5 | 6 | #define LOG_TAG "Ram" 7 | 8 | Ram::Ram(uint8_t banks) { 9 | mRam = new uint8_t[banks * BANK_SIZE_BYTES]; 10 | mRamSize = banks * BANK_SIZE_BYTES; 11 | } 12 | 13 | Ram::~Ram() { 14 | delete[] mRam; 15 | } 16 | 17 | void Ram::storeByte(const Address &address, uint8_t value) { 18 | // Note this wraps if you go outside usable RAM 19 | size_t offset = address.getAbsolute() % mRamSize; 20 | mRam[offset] = value; 21 | } 22 | 23 | uint8_t Ram::readByte(const Address &address) { 24 | size_t offset = address.getAbsolute() % mRamSize; 25 | return mRam[offset]; 26 | } 27 | 28 | bool Ram::decodeAddress(const Address &in, Address &out) { 29 | out = in; 30 | return true; 31 | } 32 | 33 | void Ram::loadFromFile(const std::string& fileName, const Address &start, const size_t size) { 34 | FILE *fp = fopen(fileName.c_str(), "rb"); 35 | assert(fp != nullptr); 36 | assert(start.getAbsolute() + size <= mRamSize); // Hopefully keep us within the boundary. 37 | size_t bytesRead = fread(mRam + start.getAbsolute(), 1, size, fp); 38 | fclose(fp); 39 | assert(bytesRead == size); 40 | Log::err(LOG_TAG).str("Ram initialised from ").str(fileName.c_str()).show(); 41 | } 42 | -------------------------------------------------------------------------------- /emulator/src/cpu/SpeedLimit.cpp: -------------------------------------------------------------------------------- 1 | #include "SpeedLimit.h" 2 | #include 3 | #include 4 | 5 | SpeedLimit::SpeedLimit(uint64_t cyclesPerSecond) { 6 | lastTime = std::chrono::high_resolution_clock::now(); 7 | std::chrono::nanoseconds oneSecond = std::chrono::duration_cast(std::chrono::duration(1)); 8 | cycleDuration = oneSecond / cyclesPerSecond; 9 | } 10 | 11 | void SpeedLimit::apply(uint64_t ticks) { 12 | if(ticks <= cycleCount) { 13 | // No CPU progress recorded, or ticks was reset 14 | cycleCount = ticks; 15 | return; 16 | } 17 | if(ticks - cycleCount < 100) { 18 | // Wait until some serious progress. 19 | return; 20 | } 21 | auto newTime = std::chrono::high_resolution_clock::now(); 22 | // Record elapsedTime and elapsedTicks 23 | uint64_t elapsedTicks = ticks - cycleCount; 24 | std::chrono::nanoseconds elapsedTime = std::chrono::duration_cast(newTime - lastTime); 25 | // Determine expected elapsed time 26 | std::chrono::nanoseconds expectedElapsedTime = elapsedTicks * cycleDuration; 27 | if(expectedElapsedTime > elapsedTime) { 28 | // Sleep if we are running ahead of schedule 29 | std::this_thread::sleep_for(expectedElapsedTime - elapsedTime); 30 | } 31 | lastTime = newTime; 32 | cycleCount = ticks; 33 | } 34 | -------------------------------------------------------------------------------- /emulator/src/cpu/Stack.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_STACK_H 21 | #define EMULATOR_STACK_H 22 | 23 | #include 24 | #include "SystemBus.h" 25 | 26 | #define STACK_POINTER_DEFAULT 0x1FF 27 | 28 | class Stack { 29 | public: 30 | 31 | explicit Stack(SystemBus *); 32 | Stack(SystemBus *, uint16_t); 33 | 34 | void push8Bit(uint8_t); 35 | void push16Bit(uint16_t); 36 | 37 | uint8_t pull8Bit(); 38 | uint16_t pull16Bit(); 39 | 40 | uint16_t getStackPointer(); 41 | 42 | private: 43 | SystemBus *mSystemBus; 44 | Address mStackAddress; 45 | }; 46 | 47 | #endif // EMULATOR_STACK_H 48 | -------------------------------------------------------------------------------- /emulator/src/cpu/SystemBus.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_SYSTEMBUS_H 21 | #define EMULATOR_SYSTEMBUS_H 22 | 23 | #include 24 | #include 25 | 26 | #include "SystemBusDevice.h" 27 | 28 | class SystemBus { 29 | public: 30 | void registerDevice(SystemBusDevice *); 31 | void storeByte(const Address &, uint8_t); 32 | void storeTwoBytes(const Address &, uint16_t); 33 | uint8_t readByte(const Address &); 34 | uint16_t readTwoBytes(const Address &); 35 | Address readAddressAt(const Address &); 36 | void clockTick(int i); 37 | 38 | private: 39 | 40 | std::vector mDevices; 41 | }; 42 | 43 | #endif // EMULATOR_SYSTEMBUS_H 44 | -------------------------------------------------------------------------------- /emulator/test/resources/testing_example/test.dbg: -------------------------------------------------------------------------------- 1 | version major=2,minor=0 2 | info csym=0,file=1,lib=0,line=8,mod=1,scope=1,seg=6,span=6,sym=3,type=0 3 | file id=0,name="example.s",size=156,mtime=0x6450C2C4,mod=0 4 | line id=0,file=0,line=11,span=3 5 | line id=1,file=0,line=12,span=5 6 | line id=2,file=0,line=7,span=1 7 | line id=3,file=0,line=8,span=2 8 | line id=4,file=0,line=10 9 | line id=5,file=0,line=4,span=0 10 | line id=6,file=0,line=3 11 | line id=7,file=0,line=6 12 | mod id=0,name="example.o",file=0 13 | seg id=0,name="CODE",start=0x00E000,size=0x0007,addrsize=absolute,type=ro,oname="test.bin",ooffs=0 14 | seg id=1,name="RODATA",start=0x000000,size=0x0000,addrsize=absolute,type=rw 15 | seg id=2,name="BSS",start=0x000100,size=0x0000,addrsize=absolute,type=rw 16 | seg id=3,name="DATA",start=0x000000,size=0x0000,addrsize=absolute,type=rw 17 | seg id=4,name="ZEROPAGE",start=0x000000,size=0x0000,addrsize=zeropage,type=rw 18 | seg id=5,name="NULL",start=0x000000,size=0x0000,addrsize=absolute,type=rw 19 | span id=0,seg=0,start=0,size=1 20 | span id=1,seg=0,start=1,size=2 21 | span id=2,seg=0,start=3,size=1 22 | span id=3,seg=0,start=4,size=2 23 | span id=4,seg=0,start=0,size=7 24 | span id=5,seg=0,start=6,size=1 25 | scope id=0,name="",mod=0,size=7,span=4 26 | sym id=0,name="test_this_will_fail",addrsize=absolute,scope=0,def=4,val=0xE004,seg=0,type=lab 27 | sym id=1,name="test_this_will_pass",addrsize=absolute,scope=0,def=7,val=0xE001,seg=0,type=lab 28 | sym id=2,name="test_setup",addrsize=absolute,scope=0,def=6,val=0xE000,seg=0,type=lab 29 | -------------------------------------------------------------------------------- /hardware/kicad/65c816-test-board/65816-computer.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 1, 4 | "active_layer_preset": "", 5 | "auto_track_width": false, 6 | "hidden_nets": [], 7 | "high_contrast_mode": 0, 8 | "net_color_mode": 1, 9 | "opacity": { 10 | "pads": 1.0, 11 | "tracks": 1.0, 12 | "vias": 1.0, 13 | "zones": 0.6 14 | }, 15 | "ratsnest_display_mode": 0, 16 | "selection_filter": { 17 | "dimensions": true, 18 | "footprints": true, 19 | "graphics": true, 20 | "keepouts": true, 21 | "lockedItems": true, 22 | "otherItems": true, 23 | "pads": true, 24 | "text": true, 25 | "tracks": true, 26 | "vias": true, 27 | "zones": true 28 | }, 29 | "visible_items": [ 30 | 0, 31 | 1, 32 | 2, 33 | 3, 34 | 4, 35 | 5, 36 | 8, 37 | 9, 38 | 10, 39 | 11, 40 | 12, 41 | 13, 42 | 14, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36 64 | ], 65 | "visible_layers": "00010a0_00000000", 66 | "zone_display_mode": 0 67 | }, 68 | "meta": { 69 | "filename": "65816-computer.kicad_prl", 70 | "version": 3 71 | }, 72 | "project": { 73 | "files": [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /hardware/kicad/i2c-test-board/i2c-test-board.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 36, 4 | "active_layer_preset": "", 5 | "auto_track_width": true, 6 | "hidden_nets": [], 7 | "high_contrast_mode": 0, 8 | "net_color_mode": 1, 9 | "opacity": { 10 | "pads": 1.0, 11 | "tracks": 1.0, 12 | "vias": 1.0, 13 | "zones": 0.6 14 | }, 15 | "ratsnest_display_mode": 0, 16 | "selection_filter": { 17 | "dimensions": true, 18 | "footprints": true, 19 | "graphics": true, 20 | "keepouts": true, 21 | "lockedItems": true, 22 | "otherItems": true, 23 | "pads": true, 24 | "text": true, 25 | "tracks": true, 26 | "vias": true, 27 | "zones": true 28 | }, 29 | "visible_items": [ 30 | 0, 31 | 1, 32 | 2, 33 | 3, 34 | 4, 35 | 5, 36 | 8, 37 | 9, 38 | 10, 39 | 11, 40 | 12, 41 | 13, 42 | 14, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36 64 | ], 65 | "visible_layers": "80390ff_fffffff9", 66 | "zone_display_mode": 0 67 | }, 68 | "meta": { 69 | "filename": "i2c-test-board.kicad_prl", 70 | "version": 3 71 | }, 72 | "project": { 73 | "files": [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rom/Makefile: -------------------------------------------------------------------------------- 1 | CA65=/usr/bin/ca65 2 | LD65=/usr/bin/ld65 3 | MINIPRO=minipro 4 | 5 | .PHONY: all clean flashrom_at28c256 flashrom_sst39lf010 6 | 7 | all: rom.bin rom_emulator.bin nop.bin rom_at28c256.bin rom_sst39lf010.bin 8 | 9 | rom.bin: address.o interrupt.o main.o post.o sd_card.o uart.o vectors.o xmodem.o system.cfg 10 | $(LD65) -o rom.bin \ 11 | -Ln rom.lbl \ 12 | -C system.cfg \ 13 | address.o interrupt.o main.o post.o sd_card.o uart.o vectors.o xmodem.o 14 | 15 | rom_emulator.bin: address.o interrupt.o main_emulator.o post.o sd_card.o uart.o vectors.o xmodem.o system.cfg 16 | $(LD65) -o rom_emulator.bin \ 17 | -Ln rom_emulator.lbl \ 18 | -C system.cfg \ 19 | address.o interrupt.o main_emulator.o post.o sd_card.o uart.o vectors.o xmodem.o 20 | 21 | nop.bin: 22 | python3 -c "open('nop.bin', 'wb').write(b'\xea' * 8192)" 23 | 24 | main_emulator.o: main.s 25 | $(CA65) --feature string_escapes --cpu 65816 --debug-info -DWARM_BOOT=1 $< -o main_emulator.o 26 | 27 | %.o: %.s 28 | $(CA65) --feature string_escapes --cpu 65816 --debug-info $< 29 | 30 | clean: 31 | rm -f rom.bin rom_emulator.bin rom.lbl rom_emulator.lbl rom_at28c256.bin rom_sst39lf010.bin address.o interrupt.o main_emulator.o post.o sd_card.o uart.o vectors.o xmodem.o nop.bin rom.txt 32 | 33 | # AT28C256 - Used in 5 V system. 34 | rom_at28c256.bin: rom.bin 35 | cp -f rom.bin rom_at28c256.bin 36 | # padded to 32 KiB 37 | truncate -s 32768 rom_at28c256.bin 38 | 39 | flashrom_at28c256: rom_at28c256.bin 40 | $(MINIPRO) -p AT28C256 -w rom_at28c256.bin 41 | 42 | # SST39lF010 - Used in 3.3 V system 43 | rom_sst39lf010.bin: rom.bin 44 | cp -f rom.bin rom_sst39lf010.bin 45 | # padded to 128 KiB 46 | truncate -s 131072 rom_sst39lf010.bin 47 | 48 | flashrom_sst39lf010: rom_sst39lf010.bin 49 | $(MINIPRO) -p "SST39LF010@PLCC32" -w rom_sst39lf010.bin 50 | 51 | -------------------------------------------------------------------------------- /emulator/src/logging/Log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the simple-logger Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/simple-logger 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_LOG_H 21 | #define EMULATOR_LOG_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | class Log { 29 | private: 30 | static Log sDebugLog; 31 | static Log sVerboseLog; 32 | static Log sTraceLog; 33 | static Log sErrorLog; 34 | 35 | Log(const bool); 36 | const char *mTag; 37 | bool mEnabled; 38 | std::ostringstream mStream; 39 | 40 | public: 41 | // Returns debug log 42 | static Log &dbg(const char *); 43 | // Returns verbose log 44 | static Log &vrb(const char *); 45 | // Returns trace log 46 | static Log &trc(const char *); 47 | // Returns error log 48 | static Log &err(const char *); 49 | 50 | void enable(); 51 | Log &str(const char *); 52 | Log &hex(uint32_t); 53 | Log &hex(uint32_t, uint8_t); 54 | Log &dec(uint32_t); 55 | Log &dec(uint32_t, uint8_t); 56 | Log &sp(); 57 | 58 | void show(); 59 | }; 60 | 61 | #endif // EMULATOR_LOG_H 62 | 63 | -------------------------------------------------------------------------------- /emulator/src/cpu/Addressing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_ADDRESSING_H 21 | #define EMULATOR_ADDRESSING_H 22 | 23 | enum class AddressingMode { 24 | Interrupt, 25 | Accumulator, 26 | BlockMove, 27 | Implied, 28 | Immediate, 29 | Absolute, 30 | AbsoluteLong, 31 | AbsoluteIndirect, 32 | AbsoluteIndirectLong, 33 | AbsoluteIndexedIndirectWithX, // index with X 34 | AbsoluteIndexedWithX, 35 | AbsoluteLongIndexedWithX, 36 | AbsoluteIndexedWithY, 37 | DirectPage, 38 | DirectPageIndexedWithX, 39 | DirectPageIndexedWithY, 40 | DirectPageIndirect, 41 | DirectPageIndirectLong, 42 | DirectPageIndexedIndirectWithX, 43 | DirectPageIndirectIndexedWithY, 44 | DirectPageIndirectLongIndexedWithY, 45 | StackImplied, 46 | StackRelative, 47 | StackAbsolute, 48 | StackDirectPageIndirect, 49 | StackProgramCounterRelativeLong, 50 | StackRelativeIndirectIndexedWithY, 51 | ProgramCounterRelative, 52 | ProgramCounterRelativeLong 53 | }; 54 | 55 | #endif // EMULATOR_ADDRESSING_H 56 | -------------------------------------------------------------------------------- /emulator/src/cpu/Binary.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | 22 | #ifndef EMULATOR_BINARY_H 23 | #define EMULATOR_BINARY_H 24 | 25 | namespace Binary { 26 | uint8_t lower8BitsOf(uint16_t); 27 | uint8_t higher8BitsOf(uint16_t); 28 | uint16_t lower16BitsOf(uint32_t); 29 | bool is8bitValueNegative(uint8_t); 30 | bool is16bitValueNegative(uint16_t); 31 | bool is8bitValueZero(uint8_t); 32 | bool is16bitValueZero(uint16_t); 33 | void setLower8BitsOf16BitsValue(uint16_t *, uint8_t); 34 | void setBitIn8BitValue(uint8_t *, uint8_t); 35 | void clearBitIn8BitValue(uint8_t *, uint8_t); 36 | void setBitIn16BitValue(uint16_t *, uint8_t); 37 | void clearBitIn16BitValue(uint16_t *, uint8_t); 38 | 39 | uint8_t convert8BitToBcd(uint8_t); 40 | uint16_t convert16BitToBcd(uint16_t); 41 | bool bcdSum8Bit(uint8_t, uint8_t, uint8_t *, bool); 42 | bool bcdSum16Bit(uint16_t, uint16_t, uint16_t *, bool); 43 | bool bcdSubtract8Bit(uint8_t, uint8_t, uint8_t *, bool); 44 | bool bcdSubtract16Bit(uint16_t, uint16_t, uint16_t *, bool); 45 | } 46 | 47 | #endif // EMULATOR_BINARY_H -------------------------------------------------------------------------------- /software/snake/random.s: -------------------------------------------------------------------------------- 1 | ; random.s: pseudo-random numbers for the 65C816 2 | .export RAND_MAX, __rand, __srand, random_state 3 | 4 | RAND_MAX := 65535 5 | 6 | .segment "BSS" 7 | random_state: .res 2 8 | 9 | .segment "CODE" 10 | ;void srand(uint16_t value) 11 | __srand: 12 | .a16 ; assume 16-bit accumulator and index register 13 | .i16 14 | tsc ; Prologue 15 | sec 16 | sbc #0 17 | tcs 18 | phd 19 | tcd 20 | srand_value := 3 21 | lda > 7; 44 | lsr 45 | lsr 46 | lsr 47 | lsr 48 | lsr 49 | lsr 50 | lsr 51 | eor random_state 52 | sta random_state ; random_state ^= random_state << 9; 53 | asl 54 | asl 55 | asl 56 | asl 57 | asl 58 | asl 59 | asl 60 | asl 61 | asl 62 | eor random_state 63 | sta random_state ; random_state ^= random_state >> 13; 64 | lsr 65 | lsr 66 | lsr 67 | lsr 68 | lsr 69 | lsr 70 | lsr 71 | lsr 72 | lsr 73 | lsr 74 | lsr 75 | lsr 76 | lsr 77 | eor random_state 78 | sta random_state 79 | dec ; return random_state - 1; // to match built-in rand. 80 | rts 81 | -------------------------------------------------------------------------------- /software/kernel/wdccc_test.s: -------------------------------------------------------------------------------- 1 | .export test_wdcc_functions, __main, __forty_two 2 | 3 | .segment "CODE" 4 | test_wdcc_functions: 5 | .a16 ; use 16-bit accumulator and index registers 6 | .i16 7 | rep #%00110000 8 | jsr __do_nothing 9 | rts 10 | 11 | R0 := 1 12 | R1 := 5 13 | R2 := 9 14 | R3 := 13 15 | __do_nothing: 16 | .a16 17 | .i16 18 | tsc 19 | sec 20 | sbc #L2 21 | tcs 22 | phd 23 | tcd 24 | L4: 25 | pld 26 | tsc 27 | clc 28 | adc #L2 29 | tcs 30 | rts 31 | L2 := 0 32 | L3 := 1 33 | 34 | __do_nothing_about: 35 | .a16 36 | .i16 37 | tsc 38 | sec 39 | sbc #L5 40 | tcs 41 | phd 42 | tcd 43 | something_0 := 3 44 | L7: 45 | lda . 18 | */ 19 | 20 | #ifndef EMULATOR_CPU65816DEBUGGER_H 21 | #define EMULATOR_CPU65816DEBUGGER_H 22 | 23 | #include 24 | #include 25 | 26 | #include "DebugSymbols.h" 27 | #include "SystemBusDevice.h" 28 | #include "BuildConfig.h" 29 | #include "Cpu65816.h" 30 | 31 | class Cpu65816Debugger { 32 | public: 33 | Cpu65816Debugger(Cpu65816 &cpu, DebugSymbols &symbols, bool &trace); 34 | 35 | void step(); 36 | void setBreakPoint(const Address &); 37 | void dumpCpu() const; 38 | void logStatusRegister() const; 39 | void logOpCode(OpCode &) const; 40 | 41 | void doBeforeStep(std::function); 42 | void doAfterStep(std::function); 43 | void onBreakPoint(std::function); 44 | void onStp(std::function); 45 | 46 | private: 47 | std::function mOnBeforeStepHandler; 48 | std::function mOnAfterStepHandler; 49 | std::function mOnBreakPointHandler; 50 | std::function mStpHandler; 51 | 52 | // Let's assume $00:$0000 is not a valid address for code 53 | Address mBreakPointAddress{0x00, 0x0000}; 54 | bool mBreakpointHit = false; 55 | 56 | Cpu65816 &mCpu; 57 | DebugSymbols &mSymbols; 58 | bool mTrace; 59 | }; 60 | 61 | #endif // EMULATOR_CPU65816DEBUGGER_H 62 | -------------------------------------------------------------------------------- /emulator/src/devices/Uart.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_UART_H 2 | #define EMULATOR_UART_H 3 | 4 | #include "SystemBusDevice.h" 5 | #include "TerminalWrapper.h" 6 | 7 | struct UartState { 8 | uint8_t lineControlRegister; 9 | unsigned char charPending; 10 | bool hasCharPending; 11 | }; 12 | 13 | /** 14 | * Emulate just some features of a NXP SC16C752 15 | */ 16 | class Uart : public SystemBusDevice { 17 | public: 18 | // General register set 19 | static const int UART_RHR = 0x00; // Receiver Holding Register (RHR) - R 20 | static const int UART_THR = 0x00; // Transmit Holding Register (THR) - W 21 | static const int UART_IER = 0x01; // Interrupt Enable Register (IER) - R/W 22 | static const int UART_IIR = 0x02; // Interrupt Identification Register (IIR) - R 23 | static const int UART_FCR = 0x02; // FIFO Control Register (FCR) - W 24 | static const int UART_LCR = 0x03; // Line Control Register (LCR) - R/W 25 | static const int UART_MCR = 0x04; // Modem Control Register (MCR) - R/W 26 | static const int UART_LSR = 0x05; // Line Status Register (LSR) - R 27 | static const int UART_MSR = 0x06; // Modem Status Register (MSR) - R 28 | static const int UART_SPR = 0x07; // Scratchpad Register (SPR) - R/W 29 | // Special register set 30 | static const int UART_DLL = 0x00; // Divisor latch LSB - R/W 31 | static const int UART_DLM = 0x01; // Divisor latch MSB - R/W 32 | // Enhanced register set; 33 | static const int UART_EFR = 0x02; // Enhanced Feature Register (EFR) - R/W 34 | static const int UART_TCR = 0x06; // Transmission Control Register (TCR) - R/W 35 | static const int UART_TLR = 0x07; // Trigger Level Register (TLR) - R/W 36 | 37 | Uart(TerminalWrapper *terminal); 38 | 39 | void storeByte(const Address &address, uint8_t uint8) override; 40 | 41 | uint8_t readByte(const Address &address) override; 42 | 43 | bool decodeAddress(const Address &address, Address &address1) override; 44 | 45 | private: 46 | TerminalWrapper *terminal; 47 | UartState state; 48 | 49 | void tryRead(); 50 | }; 51 | 52 | 53 | #endif //EMULATOR_UART_H 54 | -------------------------------------------------------------------------------- /emulator/src/cpu/OpCode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_OPCODE_H 21 | #define EMULATOR_OPCODE_H 22 | 23 | #include 24 | 25 | #include "Addressing.h" 26 | 27 | class Cpu65816; 28 | 29 | class OpCode { 30 | private: 31 | uint8_t mCode; 32 | const char *const mName; 33 | AddressingMode mAddressingMode; 34 | void (Cpu65816::*mExecutor)(OpCode &); 35 | 36 | public: 37 | OpCode(uint8_t code, const char *const name, const AddressingMode &addressingMode) : 38 | mCode(code), mName(name), mAddressingMode(addressingMode), mExecutor(0) { 39 | } 40 | 41 | OpCode(uint8_t code, const char *const name, const AddressingMode &addressingMode, 42 | void (Cpu65816::*executor)(OpCode &)) : 43 | mCode(code), mName(name), mAddressingMode(addressingMode), mExecutor(executor) { 44 | } 45 | 46 | const uint8_t getCode() { 47 | return mCode; 48 | } 49 | 50 | const char *getName() { 51 | return mName; 52 | } 53 | 54 | const AddressingMode getAddressingMode() { 55 | return mAddressingMode; 56 | } 57 | 58 | const bool execute(Cpu65816 &cpu) { 59 | if (mExecutor != 0) { 60 | OpCode opCode = *this; 61 | (cpu.*mExecutor)(opCode); 62 | return true; 63 | } 64 | return false; 65 | } 66 | }; 67 | 68 | #endif // EMULATOR_OPCODE_H 69 | -------------------------------------------------------------------------------- /emulator/src/devices/Via.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_VIA_H 2 | #define EMULATOR_VIA_H 3 | 4 | #include 5 | #include "SystemBusDevice.h" 6 | #include "InterruptStatus.h" 7 | 8 | /** 9 | * State of the VIA 10 | */ 11 | struct ViaState { 12 | uint8_t outputRegisterB; 13 | uint8_t inputRegisterB; 14 | uint8_t inputRegisterA; 15 | uint8_t outputRegisterA; 16 | uint8_t dataDirectionRegisterA; 17 | uint8_t dataDirectionRegisterB; 18 | uint16_t timer1Counter; 19 | uint16_t timer1Latch; 20 | uint16_t timer2Counter; 21 | uint8_t shiftRegister; 22 | uint8_t auxiliaryControlRegister; 23 | uint8_t peripheralControlRegister; 24 | uint8_t interruptFlagRegister; 25 | uint8_t interruptEnableRegister; 26 | bool isTimer1Running; 27 | }; 28 | 29 | /** 30 | * Emulate some features of 65C22 Versatile Interface Adapter (VIA) 31 | */ 32 | class Via : public SystemBusDevice { 33 | public: 34 | // Register ID's as accessed over data bus 35 | static const int VIA_PORTB = 0x00; 36 | static const int VIA_PORTA = 0x01; 37 | static const int VIA_DDRB = 0x02; 38 | static const int VIA_DDRA = 0x03; 39 | static const int VIA_T1C_L= 0x04; 40 | static const int VIA_T1C_H = 0x05; 41 | static const int VIA_T1L_L = 0x06; 42 | static const int VIA_T1L_H = 0x07; 43 | static const int VIA_T2C_L = 0x08; 44 | static const int VIA_T2C_H = 0x09; 45 | static const int VIA_SR = 0x0A; 46 | static const int VIA_ACR = 0x0B; 47 | static const int VIA_PCR = 0x0C; 48 | static const int VIA_IFR = 0x0D; 49 | static const int VIA_IER = 0x0E; 50 | static const int VIA_PORTA_2 = 0x0F; 51 | 52 | explicit Via(); 53 | ~Via() override; 54 | 55 | void storeByte(const Address &, uint8_t) override; 56 | uint8_t readByte(const Address &) override; 57 | bool decodeAddress(const Address &, Address &) override; 58 | void clockTick(int ticks) override; 59 | std::shared_ptr getIrq(); 60 | private: 61 | void updateIrq(); 62 | void startTimer1(); 63 | 64 | ViaState state; 65 | 66 | std::shared_ptr interrupt; 67 | 68 | }; 69 | 70 | 71 | #endif //EMULATOR_VIA_H 72 | -------------------------------------------------------------------------------- /emulator/src/cpu/Stack.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Stack.h" 21 | #include "Log.h" 22 | 23 | #define LOG_TAG "Stack" 24 | 25 | Stack::Stack(SystemBus *systemBus) : 26 | mSystemBus(systemBus), 27 | mStackAddress(0x00, STACK_POINTER_DEFAULT) { 28 | Log::trc(LOG_TAG).str("Initialized at default location ").sp().hex(mStackAddress.getOffset(), 4).show(); 29 | } 30 | 31 | Stack::Stack(SystemBus *systemBus, uint16_t stackPointer) : 32 | mSystemBus(systemBus), 33 | mStackAddress(0x00, stackPointer) { 34 | Log::trc(LOG_TAG).str("Initialized at location ").sp().hex(mStackAddress.getOffset(), 4).show(); 35 | } 36 | 37 | void Stack::push8Bit(uint8_t value) { 38 | mSystemBus->storeByte(mStackAddress, value); 39 | mStackAddress.decrementOffsetBy(sizeof(uint8_t)); 40 | } 41 | 42 | void Stack::push16Bit(uint16_t value) { 43 | auto leastSignificant = (uint8_t) ((value) & 0xFF); 44 | auto mostSignificant = (uint8_t) (((value) & 0xFF00) >> 8); 45 | push8Bit(mostSignificant); 46 | push8Bit(leastSignificant); 47 | } 48 | 49 | uint8_t Stack::pull8Bit() { 50 | mStackAddress.incrementOffsetBy(sizeof(uint8_t)); 51 | return mSystemBus->readByte(mStackAddress); 52 | } 53 | 54 | uint16_t Stack::pull16Bit() { 55 | return (uint16_t) (pull8Bit() | (((uint16_t) pull8Bit()) << 8)); 56 | } 57 | 58 | uint16_t Stack::getStackPointer() { 59 | return mStackAddress.getOffset(); 60 | } 61 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_STX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeSTX" 23 | 24 | /** 25 | * This file contains the implementation for all STX OpCodes. 26 | */ 27 | 28 | void Cpu65816::executeSTX(OpCode &opCode) { 29 | Address dataAddress = getAddressOfOpCodeData(opCode); 30 | if (accumulatorIs8BitWide()) { 31 | mSystemBus.storeByte(dataAddress, Binary::lower8BitsOf(mX)); 32 | } else { 33 | mSystemBus.storeTwoBytes(dataAddress, mX); 34 | addToCycles(1); 35 | } 36 | 37 | switch (opCode.getCode()) { 38 | case (0x8E): // STX Absolute 39 | { 40 | addToProgramAddress(3); 41 | addToCycles(4); 42 | break; 43 | } 44 | case (0x86): // STX Direct Page 45 | { 46 | if (Binary::lower8BitsOf(mD) != 0) { 47 | addToCycles(1); 48 | } 49 | 50 | addToProgramAddress(2); 51 | addToCycles(3); 52 | break; 53 | } 54 | case (0x96): // STX Direct Page Indexed, Y 55 | { 56 | if (Binary::lower8BitsOf(mD) != 0) { 57 | addToCycles(1); 58 | } 59 | 60 | addToProgramAddress(2); 61 | addToCycles(4); 62 | break; 63 | } 64 | default: { 65 | LOG_UNEXPECTED_OPCODE(opCode); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_STY.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeSTY" 23 | 24 | /** 25 | * This file contains the implementation for all STY OpCodes. 26 | */ 27 | 28 | void Cpu65816::executeSTY(OpCode &opCode) { 29 | Address dataAddress = getAddressOfOpCodeData(opCode); 30 | if (accumulatorIs8BitWide()) { 31 | mSystemBus.storeByte(dataAddress, Binary::lower8BitsOf(mY)); 32 | } else { 33 | mSystemBus.storeTwoBytes(dataAddress, mY); 34 | addToCycles(1); 35 | } 36 | 37 | switch (opCode.getCode()) { 38 | case (0x8C): // STY Absolute 39 | { 40 | addToProgramAddress(3); 41 | addToCycles(4); 42 | break; 43 | } 44 | case (0x84): // STY Direct Page 45 | { 46 | if (Binary::lower8BitsOf(mD) != 0) { 47 | addToCycles(1); 48 | } 49 | 50 | addToProgramAddress(2); 51 | addToCycles(3); 52 | break; 53 | } 54 | case (0x94): // STY Direct Page Indexed, X 55 | { 56 | if (Binary::lower8BitsOf(mD) != 0) { 57 | addToCycles(1); 58 | } 59 | 60 | addToProgramAddress(2); 61 | addToCycles(4); 62 | break; 63 | } 64 | default: { 65 | LOG_UNEXPECTED_OPCODE(opCode); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /emulator/src/devices/TerminalWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "TerminalWrapper.h" 8 | 9 | TerminalWrapper::TerminalWrapper(bool skip) { 10 | if (skip) { 11 | sockfd = -1; 12 | connfd = -1; 13 | return; 14 | } 15 | // Adapted from examples at: 16 | // - https://www.linuxhowtos.org/C_C++/socket.htm 17 | // - https://www.ibm.com/docs/en/i/7.2?topic=uauaf-example-server-application-that-uses-af-unix-address-family 18 | sockfd = socket(AF_UNIX, SOCK_STREAM, 0); 19 | if (sockfd < 0) { 20 | throw std::runtime_error("couldn't create socket"); 21 | } 22 | const char *servpath = "serial1"; 23 | unlink(servpath); // Clean up 24 | struct sockaddr_un serv_addr{}; 25 | memset(&serv_addr, 0, sizeof(serv_addr)); 26 | serv_addr.sun_family = AF_UNIX; 27 | strcpy(serv_addr.sun_path, servpath); 28 | int res = bind(sockfd, (struct sockaddr *) &serv_addr, SUN_LEN(&serv_addr)); 29 | if (res < 0) { 30 | throw std::runtime_error("failed to bind"); 31 | } 32 | int rc2 = listen(sockfd, 1); 33 | if (rc2 < 0) { 34 | throw std::runtime_error("failed to listen"); 35 | } 36 | std::cout << "Waiting for client connect to UNIX socket " << servpath << std::endl; 37 | connfd = accept(sockfd, NULL, NULL); 38 | if (connfd < 0) { 39 | throw std::runtime_error("failed to accept"); 40 | } 41 | // non-blocking reads 42 | int flags = fcntl(connfd, F_GETFL); 43 | fcntl(connfd, F_SETFL, flags | O_NONBLOCK); 44 | } 45 | 46 | TerminalWrapper::~TerminalWrapper() { 47 | } 48 | 49 | void TerminalWrapper::writeChar(const unsigned char ch) const { 50 | if(sockfd < 0) { // Means not creating unix socket for minicom, pass output to stdout instead. 51 | std::cout << ch; 52 | return; 53 | } 54 | if (connfd < 0 ) { 55 | return; 56 | } 57 | write(connfd, &ch, 1); 58 | } 59 | 60 | bool TerminalWrapper::readChar(unsigned char &res) { 61 | res = 0x00; 62 | unsigned char c; 63 | if (read(connfd, &c, 1) == 1) { 64 | res = c; 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 65816 computer ![example branch parameter](https://github.com/mike42/65816-computer/actions/workflows/build.yml/badge.svg?branch=main) 2 | 3 | This is a prototype 65C816-based computer. It features a 1.8432 MHz processor, 1 megabyte of static RAM, a 65C22 I/O port, and an NXP SC16C752 dual UART. 4 | 5 | The ROM contains a simple test program. 6 | 7 | ## Blog series 8 | 9 | This is a learning project, and I am blogging about it as I progress. 10 | 11 | - [A first look at the 65C816 processor](https://mike42.me/blog/2022-01-a-first-look-at-the-65c816-processor) 12 | - [65C816 computer - initial prototype](https://mike42.me/blog/2022-02-65c816-computer-initial-prototype) 13 | - [Interfacing an NXP UART to an 8-bit computer](https://mike42.me/blog/2022-02-interfacing-an-nxp-uart-to-an-8-bit-computer) 14 | - [Let’s implement preemptive multitasking](https://mike42.me/blog/2022-03-lets-implement-preemptive-multitasking) 15 | - [Building an emulator for my 65C816 computer](https://mike42.me/blog/2022-04-building-an-emulator-for-my-65c816-computer) 16 | - [65C816 computer - second prototype](https://mike42.me/blog/2022-05-65c816-computer-second-prototype) 17 | - [Building a 65C816 test board](https://mike42.me/blog/2022-06-building-a-65c816-test-board) 18 | - [Let’s implement power-on self test (POST)](https://mike42.me/blog/2022-06-lets-implement-power-on-self-test-post) 19 | - [Let’s make a bootloader – Part 1](https://mike42.me/blog/2022-09-lets-make-a-bootloader-part-1) 20 | - [Let’s make a bootloader – Part 2](https://mike42.me/blog/2022-09-lets-make-a-bootloader-part-2) 21 | - [Converting my 65C816 computer project to 3.3 V](https://mike42.me/blog/2023-01-converting-my-65c816-computer-project-to-3-3-v) 22 | 23 | ## Licenses & acknowledgement 24 | 25 | With the exception of the files noted below, this work is © 2022-2023 Michael Billington, and is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). 26 | 27 | ### 65816 emulator 28 | 29 | The files in the `emulator/` directory are derived from Francesco Rigoni's work, primarily [lib65816](https://github.com/FrancescoRigoni/Lib65816). The emulator application is licensed under the GNU General Public License, version 3. Please see the README and LICENSE file in that directory for details. 30 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_STZ.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeSTZ" 23 | 24 | /** 25 | * This file contains the implementation for all STZ OpCodes. 26 | */ 27 | 28 | void Cpu65816::executeSTZ(OpCode &opCode) { 29 | Address dataAddress = getAddressOfOpCodeData(opCode); 30 | if (accumulatorIs8BitWide()) { 31 | mSystemBus.storeByte(dataAddress, 0x00); 32 | } else { 33 | mSystemBus.storeTwoBytes(dataAddress, 0x0000); 34 | addToCycles(1); 35 | } 36 | 37 | switch (opCode.getCode()) { 38 | case (0x9C): // STZ Absolute 39 | { 40 | addToProgramAddress(3); 41 | addToCycles(4); 42 | break; 43 | } 44 | case (0x64): // STZ Direct Page 45 | { 46 | if (Binary::lower8BitsOf(mD) != 0) { 47 | addToCycles(1); 48 | } 49 | 50 | addToProgramAddress(2); 51 | addToCycles(3); 52 | break; 53 | } 54 | case (0x9E): // STZ Absolute Indexed, X 55 | { 56 | addToProgramAddress(3); 57 | addToCycles(5); 58 | break; 59 | } 60 | case (0x74): // STZ Direct Page Indexed, X 61 | { 62 | if (Binary::lower8BitsOf(mD) != 0) { 63 | addToCycles(1); 64 | } 65 | 66 | addToProgramAddress(2); 67 | addToCycles(4); 68 | break; 69 | } 70 | default: { 71 | LOG_UNEXPECTED_OPCODE(opCode); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /emulator/src/devices/Uart.cpp: -------------------------------------------------------------------------------- 1 | #include "Uart.h" 2 | 3 | void Uart::storeByte(const Address &address, uint8_t value) { 4 | uint8_t reg = address.getOffset() & 0x07; 5 | // Enhanced register set - available only when LCR is 0xBF 6 | if (state.lineControlRegister == 0xbf) { 7 | switch (reg) { 8 | case UART_EFR: 9 | case UART_TCR: 10 | case UART_TLR: 11 | // Not implemented 12 | return; 13 | default: 14 | // Do nothing 15 | break; 16 | } 17 | } 18 | // Special register set - available only when top bit of LCR is 1. 19 | if ((state.lineControlRegister & 0x80) == 0x80) { 20 | switch (reg) { 21 | case UART_DLL: 22 | case UART_DLM: 23 | // Not implemented 24 | return; 25 | default: 26 | // Do nothing 27 | break; 28 | } 29 | } 30 | // Regular registers 31 | switch (reg) { 32 | case UART_THR: 33 | terminal->writeChar((char) value); 34 | return; 35 | case UART_LCR: 36 | state.lineControlRegister = value; 37 | return; 38 | } 39 | // If we get here, attempt was made to writeChar to register that is not implemented. 40 | return; 41 | } 42 | 43 | uint8_t Uart::readByte(const Address &address) { 44 | uint8_t reg = address.getOffset() & 0x07; 45 | switch (reg) { 46 | case UART_RHR: { 47 | tryRead(); 48 | if(state.hasCharPending) { 49 | uint8_t ret = state.charPending; 50 | state.charPending = 0x00; 51 | state.hasCharPending = false; 52 | return ret; 53 | } else { 54 | return 0x00; 55 | } 56 | } 57 | case UART_LSR: 58 | tryRead(); 59 | return state.hasCharPending? 0x21 : 0x20; 60 | } 61 | return 0; 62 | } 63 | 64 | void Uart::tryRead() { 65 | if(state.hasCharPending) { 66 | return; 67 | } 68 | unsigned char resultChar; 69 | bool hasChar = terminal->readChar(resultChar); 70 | state.hasCharPending = hasChar; 71 | state.charPending = resultChar; 72 | } 73 | 74 | bool Uart::decodeAddress(const Address &in, Address &out) { 75 | out = in; 76 | return in.getBank() == 0 && (in.getOffset() & 0xFE00) == 0xC200; 77 | } 78 | 79 | Uart::Uart(TerminalWrapper *terminal) : terminal(terminal), state({}) { 80 | } 81 | -------------------------------------------------------------------------------- /emulator/src/logging/Log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the simple-logger Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/simple-logger 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Log.h" 21 | 22 | #define HEX_PREFIX "$" 23 | 24 | Log Log::sVerboseLog(true); 25 | Log Log::sDebugLog(false); 26 | Log Log::sTraceLog(false); 27 | Log Log::sErrorLog(false); 28 | 29 | Log::Log(const bool enabled) : mEnabled(enabled) { 30 | } 31 | 32 | Log &Log::dbg(const char *tag) { 33 | sDebugLog.mTag = tag; 34 | return sDebugLog; 35 | } 36 | 37 | Log &Log::vrb(const char *tag) { 38 | sVerboseLog.mTag = tag; 39 | return sVerboseLog; 40 | } 41 | 42 | Log &Log::trc(const char *tag) { 43 | sTraceLog.mTag = tag; 44 | return sTraceLog; 45 | } 46 | 47 | Log &Log::err(const char *tag) { 48 | sErrorLog.mTag = tag; 49 | return sErrorLog; 50 | } 51 | 52 | Log &Log::str(const char *msg) { 53 | if (mEnabled) mStream << msg; 54 | return *this; 55 | } 56 | 57 | Log &Log::hex(uint32_t val) { 58 | if (mEnabled) mStream << HEX_PREFIX << std::hex << val; 59 | return *this; 60 | } 61 | 62 | Log &Log::hex(uint32_t val, uint8_t w) { 63 | if (mEnabled) mStream << HEX_PREFIX << std::setw(w) << std::setfill('0') << std::hex << val; 64 | return *this; 65 | } 66 | 67 | Log &Log::dec(uint32_t val) { 68 | if (mEnabled) mStream << std::dec << val; 69 | return *this; 70 | } 71 | 72 | Log &Log::dec(uint32_t val, uint8_t w) { 73 | if (mEnabled) mStream << std::setw(w) << std::setfill('0') << val; 74 | return *this; 75 | } 76 | 77 | Log &Log::sp() { 78 | if (mEnabled) mStream << " "; 79 | return *this; 80 | } 81 | 82 | void Log::show() { 83 | if (mEnabled) { 84 | std::cerr << mTag << ": " << mStream.str() << std::endl; 85 | mStream.str(""); 86 | mStream.clear(); 87 | } 88 | } 89 | 90 | void Log::enable() { 91 | mEnabled = true; 92 | } 93 | -------------------------------------------------------------------------------- /emulator/README.md: -------------------------------------------------------------------------------- 1 | # 65816 emulator 2 | 3 | This is an emulator for the custom 65C816 computer in this repository. 4 | 5 | ## Before you begin 6 | 7 | You will need to assemble the computer's firmware to run the emulator. This is expected to be located at `../rom/rom.bin`. 8 | 9 | To build the emulator itself, you will need: 10 | 11 | - cmake - tested on 3.22.1 12 | - GCC - tested on 11.2.0. 13 | - Google test and boost libraries, installed on Debian via command below. 14 | 15 | ``` 16 | sudo apt install libgtest-dev libboost1.74-dev libboost-program-options1.74-dev libboost-filesystem1.74-dev 17 | ``` 18 | 19 | The emulator is only tested on Linux, and only implements hardware features which are used by the computer. 20 | 21 | ## Quick start 22 | 23 | Compile and test a release build: 24 | 25 | ``` 26 | cmake -B build/release 27 | make -j8 -C build/release all test 28 | ``` 29 | 30 | Run the emulator: 31 | 32 | ``` 33 | ./build/release/emulator --rom ../rom/rom.bin 34 | ``` 35 | 36 | This will create a unix socket called 'serial1' in the current directory. In a separate terminal window, connect to this socket with minicom to interact with the emulated system. 37 | 38 | ``` 39 | minicom -D unix#serial 40 | ``` 41 | 42 | ## Test mode 43 | 44 | The emulator also functions as a unit test runner for the 65816 assembly projects in this repository. It uses debug information (a list of labels in VICE format) to locate and execute a `test_setup` routine, followed by every routine beginning with the name `test_`. If a routine leaves 0 in the accumulator after returning, it is recorded as a test pass. Any other value is recorded as a test failure. 45 | 46 | An example project is located in the `test/resources/testing_example/` directory to show how this feature works. Execute the tests with the following command: 47 | 48 | ``` 49 | ./build/release/emulator --test test/resources/testing_example/test.bin 50 | ``` 51 | 52 | ## Fuzz tests 53 | 54 | Fuzz tests are not built by default, since they depend on different build flags, as well as the use of clang. 55 | 56 | Build them with the following commands: 57 | 58 | ``` 59 | CC=clang CXX=clang++ cmake -DFUZZ=1 -B build/fuzz 60 | make -j8 -C build/fuzz 61 | ``` 62 | 63 | Next, run your fuzzer of choice from the `build/fuzz` directory. 64 | 65 | ## License & Attribution 66 | 67 | This emulator is licensed under the GNU General Public License, version 3. See `LICENSE` for details. 68 | 69 | It is derived from Francesco Rigoni's [lib65816](https://github.com/FrancescoRigoni/Lib65816), [Lib65816_Sample](https://github.com/FrancescoRigoni/Lib65816_Sample) and [Simple-Logger](https://github.com/FrancescoRigoni/Simple-Logger), with modifications by Michael Billington. 70 | -------------------------------------------------------------------------------- /emulator/src/cpu/SystemBusDevice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | 22 | #include "SystemBusDevice.h" 23 | 24 | Address Address::sumOffsetToAddressNoWrapAround(const Address &address, uint16_t offset) { 25 | uint8_t newBank = address.getBank(); 26 | uint16_t newOffset; 27 | auto offsetSum = (uint32_t) (address.getOffset() + offset); 28 | if (offsetSum >= BANK_SIZE_BYTES) { 29 | ++newBank; 30 | newOffset = static_cast(offsetSum - BANK_SIZE_BYTES); 31 | } else { 32 | newOffset = address.getOffset() + offset; 33 | } 34 | return {newBank, newOffset}; 35 | } 36 | 37 | Address Address::sumOffsetToAddressWrapAround(const Address &address, uint16_t offset) { 38 | return Address(address.getBank(), address.getOffset() + offset); 39 | } 40 | 41 | Address Address::sumOffsetToAddress(const Address &address, uint16_t offset) { 42 | // This wraps around by default 43 | // TODO figure out when to wrap around and when not to 44 | return sumOffsetToAddressWrapAround(address, offset); 45 | } 46 | 47 | bool Address::offsetsAreOnDifferentPages(uint16_t offsetFirst, uint16_t offsetSecond) { 48 | int pageOfFirst = static_cast(std::floor(offsetFirst / PAGE_SIZE_BYTES)); 49 | int pageOfSecond = static_cast(std::floor(offsetSecond / PAGE_SIZE_BYTES)); 50 | return pageOfFirst != pageOfSecond; 51 | } 52 | 53 | Address Address::newWithOffset(uint16_t offset) { 54 | return sumOffsetToAddress((const Address &) *this, offset); 55 | } 56 | 57 | Address Address::newWithOffsetNoWrapAround(uint16_t offset) { 58 | return sumOffsetToAddressNoWrapAround((const Address &) *this, offset); 59 | } 60 | 61 | Address Address::newWithOffsetWrapAround(uint16_t offset) { 62 | return sumOffsetToAddressWrapAround((const Address &) *this, offset); 63 | } 64 | 65 | void Address::decrementOffsetBy(uint16_t offset) { 66 | mOffset -= offset; 67 | } 68 | 69 | void Address::incrementOffsetBy(uint16_t offset) { 70 | mOffset += offset; 71 | } 72 | 73 | void SystemBusDevice::clockTick(int i) { 74 | // Do nothing 75 | } 76 | -------------------------------------------------------------------------------- /emulator/src/cpu/CpuStatus.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_CPUSTATUS_H 21 | #define EMULATOR_CPUSTATUS_H 22 | 23 | #include 24 | 25 | class CpuStatus { 26 | public: 27 | CpuStatus(); 28 | 29 | void setZeroFlag(); 30 | void clearZeroFlag(); 31 | bool zeroFlag(); 32 | 33 | void setSignFlag(); 34 | void clearSignFlag(); 35 | bool signFlag(); 36 | 37 | void setDecimalFlag(); 38 | void clearDecimalFlag(); 39 | bool decimalFlag(); 40 | 41 | void setInterruptDisableFlag(); 42 | void clearInterruptDisableFlag(); 43 | bool interruptDisableFlag(); 44 | 45 | void setAccumulatorWidthFlag(); 46 | void clearAccumulatorWidthFlag(); 47 | bool accumulatorWidthFlag(); 48 | 49 | void setIndexWidthFlag(); 50 | void clearIndexWidthFlag(); 51 | bool indexWidthFlag(); 52 | 53 | void setCarryFlag(); 54 | void clearCarryFlag(); 55 | bool carryFlag(); 56 | 57 | void setBreakFlag(); 58 | void clearBreakFlag(); 59 | bool breakFlag(); 60 | 61 | void setOverflowFlag(); 62 | void clearOverflowFlag(); 63 | bool overflowFlag(); 64 | 65 | void setEmulationFlag(); 66 | void clearEmulationFlag(); 67 | bool emulationFlag(); 68 | 69 | uint8_t getRegisterValue(); 70 | void setRegisterValue(uint8_t); 71 | 72 | void updateZeroFlagFrom8BitValue(uint8_t); 73 | void updateZeroFlagFrom16BitValue(uint16_t); 74 | void updateSignFlagFrom8BitValue(uint8_t); 75 | void updateSignFlagFrom16BitValue(uint16_t); 76 | void updateSignAndZeroFlagFrom8BitValue(uint8_t); 77 | void updateSignAndZeroFlagFrom16BitValue(uint16_t); 78 | 79 | private: 80 | bool mZeroFlag = false; 81 | bool mSignFlag = false; 82 | bool mDecimalFlag = false; 83 | bool mInterruptDisableFlag = false; 84 | bool mAccumulatorWidthFlag = false; 85 | bool mIndexWidthFlag = false; 86 | bool mCarryFlag = false; 87 | bool mEmulationFlag = true; // CPU Starts in emulation mode 88 | bool mOverflowFlag = false; 89 | bool mBreakFlag = false; 90 | }; 91 | 92 | #endif // EMULATOR_CPUSTATUS_H 93 | -------------------------------------------------------------------------------- /emulator/src/cpu/DebugSymbols.cpp: -------------------------------------------------------------------------------- 1 | #include "DebugSymbols.h" 2 | 3 | #include "boost/filesystem.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Log.h" 9 | 10 | #define LOG_TAG "DebugSymbols" 11 | 12 | void DebugSymbols::loadSymbolsForBinary(const boost::filesystem::path &imagePath) { 13 | boost::filesystem::path p = imagePath; // copy because path.replace_extension() is not const for whatever reason. 14 | boost::filesystem::path debugPath = p.replace_extension(".dbg"); 15 | if (boost::filesystem::exists(debugPath)) { 16 | Log::dbg(LOG_TAG).str("Found debug symbols, loading this format is not implemented: ").str( 17 | debugPath.c_str()).show(); 18 | } 19 | boost::filesystem::path labelPath = p.replace_extension(".lbl"); 20 | if (boost::filesystem::exists(labelPath)) { 21 | Log::vrb(LOG_TAG).str("Loading labels: ").str(labelPath.c_str()).show(); 22 | std::ifstream istream(labelPath, std::ios::binary); 23 | std::ostringstream ostream; 24 | ostream << istream.rdbuf(); 25 | std::string content = ostream.str(); 26 | DebugSymbols::loadLabelFile(content); 27 | } 28 | } 29 | 30 | void DebugSymbols::loadLabelLine(const std::string &line) { 31 | // input format: "al 00E001 .some_label" 32 | typedef boost::tokenizer > tokenizer; 33 | boost::char_separator sep{" "}; 34 | tokenizer tok{line, sep}; 35 | // iterate space-separated tokens within the line 36 | int i = 0; 37 | std::string name; 38 | std::string addrString; 39 | for (const auto &t: tok) { 40 | if (i == 0) { 41 | if (t != "al") { 42 | return; // only care about 'al' (add label) commands. 43 | } 44 | } else if (i == 1) { 45 | addrString = t; 46 | } else if (i == 2) { 47 | if (t.starts_with('.')) { 48 | // Trim expected leading '.' 49 | name = t.substr(1); 50 | } else { 51 | name = t; 52 | } 53 | } else { 54 | // Too many fields 55 | return; 56 | } 57 | i++; 58 | } 59 | if (i != 3) { 60 | // Not enough fields. 61 | return; 62 | } 63 | // Parse out the address 64 | unsigned long addrLong; 65 | try { 66 | addrLong = std::stoul(addrString, nullptr, 16); 67 | } catch (std::exception &e) { 68 | // Out of range or not valid hex address 69 | return; 70 | } 71 | auto address = Address((addrLong & 0xFF0000) >> 16, addrLong & 0xFFFF); 72 | labels.insert({name, address}); 73 | labelsReverse.insert({addrLong & 0xFFFFFF, name}); 74 | } 75 | 76 | void DebugSymbols::loadLabelFile(const std::string &content) { 77 | std::istringstream stream(content); 78 | std::string line; 79 | // Iterate lines 80 | while (std::getline(stream, line)) { 81 | loadLabelLine(line); 82 | } 83 | } 84 | 85 | DebugSymbols::DebugSymbols() : labels() { 86 | } 87 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_LDX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #include 23 | 24 | #define LOG_TAG "Cpu::executeLDX" 25 | 26 | /** 27 | * This file contains implementations for all LDX OpCodes. 28 | */ 29 | 30 | void Cpu65816::executeLDX8Bit(OpCode &opCode) { 31 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 32 | uint8_t value = mSystemBus.readByte(opCodeDataAddress); 33 | Binary::setLower8BitsOf16BitsValue(&mX, value); 34 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(value); 35 | } 36 | 37 | void Cpu65816::executeLDX16Bit(OpCode &opCode) { 38 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 39 | mX = mSystemBus.readTwoBytes(opCodeDataAddress); 40 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(mX); 41 | } 42 | 43 | void Cpu65816::executeLDX(OpCode &opCode) { 44 | if (indexIs16BitWide()) { 45 | executeLDX16Bit(opCode); 46 | addToCycles(1); 47 | } else { 48 | executeLDX8Bit(opCode); 49 | } 50 | 51 | switch (opCode.getCode()) { 52 | case (0xA2): // LDX Immediate 53 | { 54 | if (indexIs16BitWide()) { 55 | addToProgramAddress(1); 56 | } 57 | addToProgramAddressAndCycles(2, 2); 58 | break; 59 | } 60 | case (0xAE): // LDX Absolute 61 | { 62 | addToProgramAddressAndCycles(3, 4); 63 | break; 64 | } 65 | case (0xA6): // LDX Direct Page 66 | { 67 | if (Binary::lower8BitsOf(mD) != 0) { 68 | addToCycles(1); 69 | } 70 | addToProgramAddressAndCycles(2, 3); 71 | break; 72 | } 73 | case (0xBE): // LDX Absolute Indexed, Y 74 | { 75 | if (opCodeAddressingCrossesPageBoundary(opCode)) { 76 | addToCycles(1); 77 | } 78 | addToProgramAddressAndCycles(3, 4); 79 | break; 80 | } 81 | case (0xB6): // LDX Direct Page Indexed, Y 82 | { 83 | if (Binary::lower8BitsOf(mD) != 0) { 84 | addToCycles(1); 85 | } 86 | addToProgramAddressAndCycles(2, 4); 87 | break; 88 | } 89 | default: { 90 | LOG_UNEXPECTED_OPCODE(opCode); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_LDY.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #include 23 | 24 | #define LOG_TAG "Cpu::executeLDY" 25 | 26 | /** 27 | * This file contains implementations for all LDY OpCodes. 28 | */ 29 | 30 | void Cpu65816::executeLDY8Bit(OpCode &opCode) { 31 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 32 | uint8_t value = mSystemBus.readByte(opCodeDataAddress); 33 | Binary::setLower8BitsOf16BitsValue(&mY, value); 34 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(value); 35 | } 36 | 37 | void Cpu65816::executeLDY16Bit(OpCode &opCode) { 38 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 39 | mY = mSystemBus.readTwoBytes(opCodeDataAddress); 40 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(mY); 41 | } 42 | 43 | void Cpu65816::executeLDY(OpCode &opCode) { 44 | if (indexIs16BitWide()) { 45 | executeLDY16Bit(opCode); 46 | addToCycles(1); 47 | } else { 48 | executeLDY8Bit(opCode); 49 | } 50 | 51 | switch (opCode.getCode()) { 52 | case (0xA0): // LDY Immediate 53 | { 54 | if (indexIs16BitWide()) { 55 | addToProgramAddress(1); 56 | } 57 | addToProgramAddressAndCycles(2, 2); 58 | break; 59 | } 60 | case (0xAC): // LDY Absolute 61 | { 62 | addToProgramAddressAndCycles(3, 4); 63 | break; 64 | } 65 | case (0xA4): // LDY Direct Page 66 | { 67 | if (Binary::lower8BitsOf(mD) != 0) { 68 | addToCycles(1); 69 | } 70 | addToProgramAddressAndCycles(2, 3); 71 | break; 72 | } 73 | case (0xBC): // LDY Absolute Indexed, X 74 | { 75 | if (opCodeAddressingCrossesPageBoundary(opCode)) { 76 | addToCycles(1); 77 | } 78 | addToProgramAddressAndCycles(3, 4); 79 | break; 80 | } 81 | case (0xB4): // LDY Direct Page Indexed, X 82 | { 83 | if (Binary::lower8BitsOf(mD) != 0) { 84 | addToCycles(1); 85 | } 86 | addToProgramAddressAndCycles(2, 4); 87 | break; 88 | } 89 | default: { 90 | LOG_UNEXPECTED_OPCODE(opCode); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /emulator/src/cpu/SystemBusDevice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef EMULATOR_SYSTEM_BUS_DEVICE_H 21 | #define EMULATOR_SYSTEM_BUS_DEVICE_H 22 | 23 | #include 24 | 25 | #define BANK_SIZE_BYTES 0x10000 26 | #define PAGE_SIZE_BYTES 256 27 | 28 | class Address { 29 | private: 30 | uint8_t mBank; 31 | uint16_t mOffset; 32 | 33 | public: 34 | static bool offsetsAreOnDifferentPages(uint16_t, uint16_t); 35 | static Address sumOffsetToAddress(const Address &, uint16_t); 36 | static Address sumOffsetToAddressNoWrapAround(const Address &, uint16_t); 37 | static Address sumOffsetToAddressWrapAround(const Address &, uint16_t); 38 | 39 | Address() = default; 40 | 41 | Address(uint8_t bank, uint16_t offset) : mBank(bank), mOffset(offset) {}; 42 | 43 | Address newWithOffset(uint16_t); 44 | Address newWithOffsetNoWrapAround(uint16_t); 45 | Address newWithOffsetWrapAround(uint16_t); 46 | 47 | void incrementOffsetBy(uint16_t); 48 | void decrementOffsetBy(uint16_t); 49 | 50 | void getBankAndOffset(uint8_t *bank, uint16_t *offset) { 51 | *bank = mBank; 52 | *offset = mOffset; 53 | } 54 | 55 | uint8_t getBank() const { 56 | return mBank; 57 | } 58 | 59 | uint16_t getOffset() const { 60 | return mOffset; 61 | } 62 | 63 | uint32_t getAbsolute() const { 64 | return (mBank << 16) + mOffset; 65 | } 66 | }; 67 | 68 | /** 69 | Every device (PPU, APU, ...) implements this interface. 70 | */ 71 | class SystemBusDevice { 72 | public: 73 | virtual ~SystemBusDevice() {}; 74 | 75 | /** 76 | Stores one byte to the real address represented by the specified virtual address. 77 | That is: maps the virtual address to the real one and stores one byte in it. 78 | */ 79 | virtual void storeByte(const Address &, uint8_t) = 0; 80 | 81 | /** 82 | Reads one byte from the real address represented by the specified virtual address. 83 | That is: maps the virtual address to the real one and reads from it. 84 | */ 85 | virtual uint8_t readByte(const Address &) = 0; 86 | 87 | /** 88 | Returns true if the address was decoded successfully by this device. 89 | */ 90 | virtual bool decodeAddress(const Address &, Address &) = 0; 91 | 92 | /** 93 | * Called once per clock cycle 94 | */ 95 | virtual void clockTick(int i); 96 | }; 97 | 98 | #endif // EMULATOR_SYSTEM_BUS_DEVICE_H 99 | -------------------------------------------------------------------------------- /software/kernel/device/uart.s: -------------------------------------------------------------------------------- 1 | ; uart.s: driver for NXP SC16C752 UART 2 | 3 | .export uart_init, uart_print_char, uart_recv_char, uart_printz 4 | 5 | ; Assuming /CSA is connected to IO1 6 | UART_BASE = $c200 7 | 8 | ; UART registers 9 | ; Not included: FIFO ready register, Xon1 Xon2 words. 10 | UART_RHR = UART_BASE ; Receiver Holding Register (RHR) - R 11 | UART_THR = UART_BASE ; Transmit Holding Register (THR) - W 12 | UART_IER = UART_BASE + 1 ; Interrupt Enable Register (IER) - R/W 13 | UART_IIR = UART_BASE + 2 ; Interrupt Identification Register (IIR) - R 14 | UART_FCR = UART_BASE + 2 ; FIFO Control Register (FCR) - W 15 | UART_LCR = UART_BASE + 3 ; Line Control Register (LCR) - R/W 16 | UART_MCR = UART_BASE + 4 ; Modem Control Register (MCR) - R/W 17 | UART_LSR = UART_BASE + 5 ; Line Status Register (LSR) - R 18 | UART_MSR = UART_BASE + 6 ; Modem Status Register (MSR) - R 19 | UART_SPR = UART_BASE + 7 ; Scratchpad Register (SPR) - R/W 20 | ; Different meaning when LCR is logic 1 21 | UART_DLL = UART_BASE ; Divisor latch LSB - R/W 22 | UART_DLM = UART_BASE + 1 ; Divisor latch MSB - R/W 23 | ; Different meaning when LCR is %1011 1111 ($bh). 24 | UART_EFR = UART_BASE + 2 ; Enhanced Feature Register (EFR) - R/W 25 | UART_TCR = UART_BASE + 6 ; Transmission Control Register (TCR) - R/W 26 | UART_TLR = UART_BASE + 7 ; Trigger Level Register (TLR) - R/W 27 | 28 | .segment "CODE" 29 | ; uart_init: Initialize the UART 30 | uart_init: 31 | .a8 ; Use 8-bit accumulator 32 | sep #%00100000 33 | 34 | lda #$80 ; Enable divisor latches 35 | sta UART_LCR 36 | lda #1 ; Set divisior to 1 - on a 1.8432 MHZ XTAL1, this gets 115200bps. 37 | sta UART_DLL 38 | lda #0 39 | sta UART_DLM 40 | lda #%00010111 ; Sets up 8-n-1 41 | sta UART_LCR 42 | lda #%00001111 ; Enable FIFO, set DMA mode 1 43 | sta UART_FCR 44 | .a16 ; Revert to 16-bit accumulator 45 | rep #%00100000 46 | rts 47 | 48 | ; uart_print_char: Print a single character to the UART 49 | ; A - ASCII character to print. Only the least significant 8 bits are used 50 | uart_print_char: 51 | .a8 ; Use 8-bit accumulator 52 | sep #%00100000 53 | sta UART_THR 54 | .a16 ; Revert to 16-bit accumulator 55 | rep #%00100000 56 | rts 57 | 58 | ; uart_printz: Print characters to UART until a null is reached 59 | ; X - pointer to string to print 60 | uart_printz: 61 | .a8 ; Use 8-bit accumulator 62 | sep #%00100000 63 | @uart_printz_next: 64 | lda a:$0000, X ; a: to avoid this being interpreted as direct page 65 | beq @uart_printz_done 66 | sta UART_THR 67 | inx 68 | jmp @uart_printz_next 69 | @uart_printz_done: 70 | .a16 ; Revert to 16-bit accumulator 71 | rep #%00100000 72 | rts 73 | 74 | ; uart_recv_char: Block until a character is received, then load result to A register. 75 | uart_recv_char: 76 | .a8 ; Use 8-bit accumulator 77 | sep #%00100000 78 | lda UART_LSR 79 | and #%00000001 80 | cmp #%00000001 81 | bne uart_recv_char 82 | lda UART_RHR 83 | .a16 ; Revert to 16-bit accumulator 84 | rep #%00100000 85 | rts 86 | -------------------------------------------------------------------------------- /emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | project(Emulator 6 | LANGUAGES CXX) 7 | 8 | enable_testing() 9 | 10 | find_package(GTest REQUIRED) 11 | find_package(Boost 1.74 COMPONENTS program_options filesystem REQUIRED) 12 | 13 | include_directories(PRIVATE 14 | src/ 15 | src/cpu 16 | src/devices 17 | src/logging 18 | ) 19 | 20 | # Most code is in a library for easier testing 21 | add_library(cpu65816 22 | src/cpu/Addressing.cpp 23 | src/cpu/Binary.cpp 24 | src/cpu/Cpu65816.cpp 25 | src/cpu/Cpu65816Debugger.cpp 26 | src/cpu/CpuStatus.cpp 27 | src/cpu/opcodes/OpCode_ADC.cpp 28 | src/cpu/opcodes/OpCode_AND.cpp 29 | src/cpu/opcodes/OpCode_ASL.cpp 30 | src/cpu/opcodes/OpCode_BIT.cpp 31 | src/cpu/opcodes/OpCode_Branch.cpp 32 | src/cpu/opcodes/OpCode_CMP.cpp 33 | src/cpu/opcodes/OpCode_CPX_CPY.cpp 34 | src/cpu/opcodes/OpCode_EOR.cpp 35 | src/cpu/opcodes/OpCode_INC_DEC.cpp 36 | src/cpu/opcodes/OpCode_Interrupt.cpp 37 | src/cpu/opcodes/OpCode_JumpReturn.cpp 38 | src/cpu/opcodes/OpCode_LDA.cpp 39 | src/cpu/opcodes/OpCode_LDX.cpp 40 | src/cpu/opcodes/OpCode_LDY.cpp 41 | src/cpu/opcodes/OpCode_LSR.cpp 42 | src/cpu/opcodes/OpCode_Misc.cpp 43 | src/cpu/opcodes/OpCode_ORA.cpp 44 | src/cpu/opcodes/OpCode_ROL.cpp 45 | src/cpu/opcodes/OpCode_ROR.cpp 46 | src/cpu/opcodes/OpCode_SBC.cpp 47 | src/cpu/opcodes/OpCode_Stack.cpp 48 | src/cpu/opcodes/OpCode_STA.cpp 49 | src/cpu/opcodes/OpCode_StatusReg.cpp 50 | src/cpu/opcodes/OpCode_STX.cpp 51 | src/cpu/opcodes/OpCode_STY.cpp 52 | src/cpu/opcodes/OpCode_STZ.cpp 53 | src/cpu/opcodes/OpCodeTable.cpp 54 | src/cpu/opcodes/OpCode_Transfer.cpp 55 | src/cpu/opcodes/OpCode_TSB_TRB.cpp 56 | src/cpu/Stack.cpp 57 | src/cpu/SystemBus.cpp 58 | src/cpu/SystemBusDevice.cpp 59 | src/logging/Log.cpp 60 | src/devices/Ram.cpp 61 | src/devices/Rom.cpp 62 | src/devices/Via.cpp 63 | src/devices/InterruptStatus.cpp 64 | src/devices/Uart.cpp 65 | src/devices/TerminalWrapper.cpp 66 | src/cpu/SpeedLimit.cpp 67 | src/cpu/SpeedLimit.h src/cpu/DebugSymbols.cpp src/cpu/DebugSymbols.h) 68 | 69 | target_link_libraries(cpu65816 70 | Boost::filesystem) 71 | 72 | # main emulator executable 73 | add_executable(emulator 74 | src/main.cpp) 75 | 76 | target_link_libraries(emulator 77 | cpu65816 78 | Boost::program_options) 79 | 80 | target_include_directories(emulator PRIVATE 81 | ${Boost_INCLUDE_DIR}) 82 | 83 | # test executable 84 | add_executable(emulator_test 85 | test/DebugSymbolsTest.cpp) 86 | 87 | target_compile_definitions(emulator_test PUBLIC 88 | TEST_RESOURCES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/test/resources") 89 | 90 | target_include_directories(emulator_test PRIVATE 91 | ${GTest_INCLUDE_DIR}) 92 | 93 | target_link_libraries(emulator_test 94 | cpu65816 95 | gtest 96 | gtest_main) 97 | 98 | gtest_discover_tests(emulator_test) 99 | 100 | add_executable(fuzz_debug_symbols_parser 101 | test/DebugSymbolsFuzz.cpp) 102 | target_link_libraries(fuzz_debug_symbols_parser 103 | cpu65816 104 | -fsanitize=address,fuzzer) 105 | 106 | if(FUZZ) 107 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,fuzzer-no-link -fsanitize-coverage=edge,indirect-calls") 108 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,fuzzer-no-link -fsanitize-coverage=edge,indirect-calls") 109 | else () 110 | set_target_properties(fuzz_debug_symbols_parser PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) 111 | endif() 112 | -------------------------------------------------------------------------------- /rom/post.s: -------------------------------------------------------------------------------- 1 | ; post.s: Power-on self test routine for ROM. 2 | .import post_done, VIA_BASE 3 | .export post_start 4 | 5 | .macro pause len ; time-wasting loop as macro 6 | lda #len 7 | : 8 | ldx #64 9 | : 10 | ldy #255 11 | : 12 | dey 13 | cpy #0 14 | bne :- 15 | dex 16 | cpx #0 17 | bne :-- 18 | dec 19 | cmp #0 20 | bne :--- 21 | .endmacro 22 | 23 | .macro blink 24 | sec ; switch to emulation mode 25 | xce 26 | pause 1 27 | clc ; switch to native mode 28 | xce 29 | pause 2 30 | sec ; switch to emulation mode 31 | .endmacro 32 | 33 | .segment "CODE" 34 | post_start: 35 | jmp post_check_loram 36 | 37 | post_check_loram: 38 | ldx #%01010101 ; Power-on self test (POST) - do we have low RAM? 39 | ldy #%10101010 40 | stx a:$00 ; store known values at two addresses 41 | sty a:$01 42 | ldx a:$00 ; read back the values - unlikely to be correct if RAM not present 43 | ldy a:$01 44 | cpx #%01010101 45 | bne post_fail_loram 46 | cpy #%10101010 47 | bne post_fail_loram 48 | jmp post_check_hiram 49 | 50 | post_fail_loram: ; blink emulation mode output with two pulses 51 | pause 8 52 | blink 53 | blink 54 | jmp post_fail_loram ; repeat indefinitely 55 | 56 | post_check_hiram: 57 | ldx #%10101010 ; Power-on self test (POST) - do we have high RAM? 58 | ldy #%01010101 59 | lda #$08 ; data bank to high ram 60 | pha 61 | plb 62 | stx a:$00 ; store known values at two addresses 63 | sty a:$01 64 | ldx a:$00 ; read back the values - unlikely to be correct if RAM not present 65 | ldy a:$01 66 | cpx #%10101010 67 | bne post_fail_hiram 68 | cpy #%01010101 69 | bne post_fail_hiram 70 | lda #$00 ; reset data bank to boot-up value 71 | pha 72 | plb 73 | jmp post_check_via 74 | 75 | post_fail_hiram: ; blink emulation mode output with three pulses. we could use RAM here? 76 | pause 8 77 | blink 78 | blink 79 | blink 80 | jmp post_fail_hiram ; repeat indefinitely 81 | 82 | VIA_IER = $c00e 83 | post_check_via: ; Power-on self test (POST) - do we have a 65C22 Versatile Interface Adapter (VIA)? 84 | lda a:VIA_IER 85 | cmp #%10000000 ; start-up state, interrupts enabled overall (IFR7) but all interrupt sources (IFR0-6) disabled. 86 | bne post_fail_via 87 | jmp post_ok 88 | 89 | post_fail_via: 90 | pause 8 91 | blink 92 | blink 93 | blink 94 | blink 95 | jmp post_fail_via 96 | 97 | VIA_DDRB = VIA_BASE + $02 98 | VIA_T1C_L = VIA_BASE + $04 99 | VIA_T1C_H = VIA_BASE + $05 100 | VIA_ACR = VIA_BASE + $0b 101 | BEEP_FREQ_DIVIDER = 461 ; 1KHz, formula is CPU clock / (desired frequency * 2), or 921600 / (1000 * 2) ~= 461 102 | post_ok: ; all good, emit celebratory beep, approx 1KHz for 1/10th second 103 | ; Start beep 104 | lda #%10000000 ; VIA PIN PB7 only 105 | sta VIA_DDRB 106 | lda #%11000000 ; set ACR. first two bits = 11 is continuous square wave output on PB7 107 | sta VIA_ACR 108 | lda #BEEP_FREQ_DIVIDER ; set T1 high-order counter 111 | sta VIA_T1C_H 112 | ; wait approx 0.1 seconds 113 | pause 1 114 | ; Stop beep 115 | lda #%00000000 ; set ACR. returns to a one-shot mode 116 | sta VIA_ACR 117 | stz VIA_T1C_L ; zero the counters 118 | stz VIA_T1C_H 119 | ; POST is now done 120 | jmp post_done 121 | -------------------------------------------------------------------------------- /emulator/src/cpu/SystemBus.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include "SystemBus.h" 22 | #include "Log.h" 23 | 24 | #define LOG_TAG "SystemBus" 25 | 26 | void SystemBus::registerDevice(SystemBusDevice *device) { 27 | mDevices.push_back(device); 28 | } 29 | 30 | void SystemBus::storeByte(const Address &address, uint8_t value) { 31 | for (SystemBusDevice *device: mDevices) { 32 | Address decodedAddress; 33 | if (device->decodeAddress(address, decodedAddress)) { 34 | device->storeByte(decodedAddress, value); 35 | return; 36 | } 37 | } 38 | } 39 | 40 | void SystemBus::storeTwoBytes(const Address &address, uint16_t value) { 41 | for (SystemBusDevice *device: mDevices) { 42 | Address decodedAddress; 43 | if (device->decodeAddress(address, decodedAddress)) { 44 | uint8_t leastSignificantByte = (uint8_t) (value & 0xFF); 45 | uint8_t mostSignificantByte = (uint8_t) ((value & 0xFF00) >> 8); 46 | device->storeByte(decodedAddress, leastSignificantByte); 47 | decodedAddress.incrementOffsetBy(1); 48 | device->storeByte(decodedAddress, mostSignificantByte); 49 | return; 50 | } 51 | } 52 | } 53 | 54 | uint8_t SystemBus::readByte(const Address &address) { 55 | for (SystemBusDevice *device: mDevices) { 56 | Address decodedAddress; 57 | if (device->decodeAddress(address, decodedAddress)) { 58 | return device->readByte(decodedAddress); 59 | } 60 | } 61 | return 0; 62 | } 63 | 64 | uint16_t SystemBus::readTwoBytes(const Address &address) { 65 | for (SystemBusDevice *device: mDevices) { 66 | Address decodedAddress; 67 | if (device->decodeAddress(address, decodedAddress)) { 68 | uint8_t leastSignificantByte = device->readByte(decodedAddress); 69 | decodedAddress.incrementOffsetBy(sizeof(uint8_t)); 70 | uint8_t mostSignificantByte = device->readByte(decodedAddress); 71 | uint16_t value = ((uint16_t) mostSignificantByte << 8) | leastSignificantByte; 72 | return value; 73 | } 74 | } 75 | return 0; 76 | } 77 | 78 | Address SystemBus::readAddressAt(const Address &address) { 79 | Address decodedAddress{0x00, 0x0000}; 80 | for (SystemBusDevice *device: mDevices) { 81 | if (device->decodeAddress(address, decodedAddress)) { 82 | // Read offset 83 | uint8_t leastSignificantByte = device->readByte(decodedAddress); 84 | decodedAddress.incrementOffsetBy(sizeof(uint8_t)); 85 | uint8_t mostSignificantByte = device->readByte(decodedAddress); 86 | uint16_t offset = ((uint16_t) mostSignificantByte << 8) | leastSignificantByte; 87 | // Read bank 88 | decodedAddress.incrementOffsetBy(sizeof(uint8_t)); 89 | uint8_t bank = device->readByte(decodedAddress); 90 | return Address(bank, offset); 91 | } 92 | } 93 | return decodedAddress; 94 | } 95 | 96 | void SystemBus::clockTick(int cycles) { 97 | for (SystemBusDevice *device: mDevices) { 98 | device->clockTick(cycles); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_BIT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Interrupt.h" 21 | #include "Cpu65816.h" 22 | 23 | #define LOG_TAG "Cpu::executeBIT" 24 | 25 | /** 26 | * This file contains the implementation for all BIT OpCodes 27 | */ 28 | 29 | void Cpu65816::execute8BitBIT(OpCode &opCode) { 30 | const Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 31 | uint8_t value = mSystemBus.readByte(addressOfOpCodeData); 32 | bool isHighestBitSet = value & 0x80; 33 | bool isNextToHighestBitSet = value & 0x40; 34 | 35 | if (opCode.getAddressingMode() != AddressingMode::Immediate) { 36 | if (isHighestBitSet) mCpuStatus.setSignFlag(); 37 | else mCpuStatus.clearSignFlag(); 38 | if (isNextToHighestBitSet) mCpuStatus.setOverflowFlag(); 39 | else mCpuStatus.clearOverflowFlag(); 40 | } 41 | mCpuStatus.updateZeroFlagFrom8BitValue(value & Binary::lower8BitsOf(mA)); 42 | } 43 | 44 | void Cpu65816::execute16BitBIT(OpCode &opCode) { 45 | const Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 46 | uint16_t value = mSystemBus.readTwoBytes(addressOfOpCodeData); 47 | bool isHighestBitSet = value & 0x8000; 48 | bool isNextToHighestBitSet = value & 0x4000; 49 | 50 | if (opCode.getAddressingMode() != AddressingMode::Immediate) { 51 | if (isHighestBitSet) mCpuStatus.setSignFlag(); 52 | else mCpuStatus.clearSignFlag(); 53 | if (isNextToHighestBitSet) mCpuStatus.setOverflowFlag(); 54 | else mCpuStatus.clearOverflowFlag(); 55 | } 56 | mCpuStatus.updateZeroFlagFrom16BitValue(value & mA); 57 | } 58 | 59 | void Cpu65816::executeBIT(OpCode &opCode) { 60 | if (accumulatorIs8BitWide()) { 61 | execute8BitBIT(opCode); 62 | } else { 63 | execute16BitBIT(opCode); 64 | addToCycles(1); 65 | } 66 | 67 | switch (opCode.getCode()) { 68 | case (0x89): // BIT Immediate 69 | { 70 | if (accumulatorIs16BitWide()) { 71 | addToProgramAddress(1); 72 | } 73 | addToProgramAddressAndCycles(2, 2); 74 | break; 75 | } 76 | case (0x2C): // BIT Absolute 77 | { 78 | addToProgramAddressAndCycles(3, 4); 79 | break; 80 | } 81 | case (0x24): // BIT Direct Page 82 | { 83 | if (Binary::lower8BitsOf(mD) != 0) { 84 | addToCycles(1); 85 | } 86 | addToProgramAddressAndCycles(2, 3); 87 | break; 88 | } 89 | case (0x3C): // BIT Absolute Indexed, X 90 | { 91 | if (opCodeAddressingCrossesPageBoundary(opCode)) { 92 | addToCycles(1); 93 | } 94 | addToProgramAddressAndCycles(3, 4); 95 | break; 96 | } 97 | case (0x34): // BIT Direct Page Indexed, X 98 | { 99 | if (Binary::lower8BitsOf(mD) != 0) { 100 | addToCycles(1); 101 | } 102 | addToProgramAddressAndCycles(2, 4); 103 | break; 104 | } 105 | default: { 106 | LOG_UNEXPECTED_OPCODE(opCode); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_JumpReturn.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeJumpReturn" 23 | 24 | /** 25 | * This file contains the implementation for all OpCodes 26 | * that deal with jumps, calls and returns. 27 | */ 28 | 29 | void Cpu65816::executeJumpReturn(OpCode &opCode) { 30 | switch (opCode.getCode()) { 31 | case (0x20): // JSR Absolute 32 | { 33 | mStack.push16Bit(mProgramAddress.getOffset() + 2); 34 | uint16_t destinationAddress = getAddressOfOpCodeData(opCode).getOffset(); 35 | setProgramAddress(Address(mProgramAddress.getBank(), destinationAddress)); 36 | addToCycles(6); 37 | break; 38 | } 39 | case (0x22): // JSR Absolute Long 40 | { 41 | mStack.push8Bit(mProgramAddress.getBank()); 42 | mStack.push16Bit(mProgramAddress.getOffset() + 3); 43 | setProgramAddress(getAddressOfOpCodeData(opCode)); 44 | addToCycles(8); 45 | break; 46 | } 47 | case (0xFC): // JSR Absolute Indexed Indirect, X 48 | { 49 | Address destinationAddress = getAddressOfOpCodeData(opCode); 50 | mStack.push16Bit(mProgramAddress.getOffset() + 2); 51 | setProgramAddress(destinationAddress); 52 | addToCycles(8); 53 | break; 54 | } 55 | case (0x4C): // JMP Absolute 56 | { 57 | uint16_t destinationAddress = getAddressOfOpCodeData(opCode).getOffset(); 58 | setProgramAddress(Address(mProgramAddress.getBank(), destinationAddress)); 59 | addToCycles(3); 60 | break; 61 | } 62 | case (0x6C): // JMP Absolute Indirect 63 | { 64 | setProgramAddress(getAddressOfOpCodeData(opCode)); 65 | addToCycles(5); 66 | #ifdef EMU_65C02 67 | addToCycles(1); 68 | #endif 69 | break; 70 | } 71 | case (0x7C): // JMP Absolute Indexed Indirect, X 72 | { 73 | setProgramAddress(getAddressOfOpCodeData(opCode)); 74 | addToCycles(6); 75 | break; 76 | } 77 | case (0x5C): // JMP Absolute Long 78 | { 79 | setProgramAddress(getAddressOfOpCodeData(opCode)); 80 | addToCycles(4); 81 | break; 82 | } 83 | case (0xDC): // JMP Absolute Indirect Long 84 | { 85 | setProgramAddress(getAddressOfOpCodeData(opCode)); 86 | addToCycles(6); 87 | break; 88 | } 89 | case (0x6B): // RTL 90 | { 91 | uint16_t newOffset = mStack.pull16Bit() + 1; 92 | uint8_t newBank = mStack.pull8Bit(); 93 | 94 | Address returnAddress(newBank, newOffset); 95 | setProgramAddress(returnAddress); 96 | addToCycles(6); 97 | break; 98 | } 99 | case (0x60): // RTS 100 | { 101 | Address returnAddress(mProgramAddress.getBank(), mStack.pull16Bit() + 1); 102 | setProgramAddress(returnAddress); 103 | addToCycles(6); 104 | break; 105 | } 106 | default: { 107 | LOG_UNEXPECTED_OPCODE(opCode); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /rom/xmodem.s: -------------------------------------------------------------------------------- 1 | ; xmodem.s: Routines to receive a program to boot from serial 2 | .import uart_recv_char_with_timeout, uart_print_char, uart_recv_char, VIA_BASE 3 | 4 | .export xmodem_recv 5 | 6 | ; Destination address for programs loaded over serial 7 | SERIAL_PROGRAM_BANK = $01 ; Destination bank 8 | SERIAL_PROGRAM_BASE = $0000 ; Base 9 | 10 | ; Size of XMODEM block 11 | XMODEM_BLOCK_SIZE := 128 12 | 13 | ; ASCII control characters used by XMODEM 14 | ASCII_SOH := $01 15 | ASCII_EOT := $04 16 | ASCII_ACK := $06 17 | ASCII_NAK := $15 18 | 19 | .segment "ZEROPAGE" 20 | dest_block_ptr: .res 2 21 | 22 | .segment "CODE" 23 | xmodem_recv: 24 | php 25 | .a8 ; Switch to 16-bit index, 8-bit accumulator for per-byte work 26 | .i16 27 | sep #%00100000 28 | rep #%00010000 29 | phb ; Save data bank for later 30 | lda #SERIAL_PROGRAM_BANK ; Set data bank register to where the program will be saved 31 | pha 32 | plb 33 | ldx #SERIAL_PROGRAM_BASE ; Set up pointer to next block to work with 34 | stx dest_block_ptr 35 | @shell_block_nak: ; NAK, ACK once 36 | lda #ASCII_NAK ; NAK gets started 37 | jsr uart_print_char 38 | jsr click ; Click each time we send a NAK or ACK 39 | jsr uart_recv_char_with_timeout ; Check in loop w/ timeout 40 | bcc @shell_block_nak ; Not received yet 41 | cmp #ASCII_SOH ; If we do have char, should be SOH 42 | bne @shell_rx_fail ; Terminate transfer if we don't get SOH 43 | @shell_rx_block: 44 | ; Receive one block 45 | jsr uart_recv_char ; Block number 46 | jsr uart_recv_char ; Inverse block number 47 | ldy #0 ; Start at char 0 48 | @shell_rx_char: 49 | jsr uart_recv_char 50 | sta (dest_block_ptr), Y 51 | iny 52 | cpy #XMODEM_BLOCK_SIZE 53 | bne @shell_rx_char 54 | jsr uart_recv_char ; Checksum - TODO verify this and jump to shell_block_nak to repeat if not matching 55 | lda #ASCII_ACK ; ACK the packet 56 | jsr uart_print_char 57 | jsr click ; Click each time we send a NAK or ACK 58 | jsr uart_recv_char 59 | cmp #ASCII_EOT ; EOT char, no more blocks 60 | beq @shell_rx_done 61 | cmp #ASCII_SOH ; SOH char, next block on the way 62 | bne @shell_block_nak ; Anything else fail transfer for the block 63 | ; Move write pointer along for next block 64 | php 65 | .a16 66 | rep #%00100000 ; Some quick 16-bit arithetic 67 | lda #XMODEM_BLOCK_SIZE 68 | adc dest_block_ptr 69 | sta dest_block_ptr 70 | plp 71 | .a8 72 | jmp @shell_rx_block 73 | @shell_rx_done: 74 | lda #ASCII_ACK ; ACK the EOT as well. 75 | jsr uart_print_char 76 | jsr click ; Click each time we send a NAK or ACK 77 | jsr uart_recv_char_with_timeout ; induce delay, printing to the terminal immediately will not work 78 | lda #0 ; set accumulator to 0. 79 | xba ; set the other half to zero as well.. 80 | lda #0 81 | jmp @shell_rx_cleanup 82 | @shell_rx_fail: 83 | jsr uart_recv_char_with_timeout ; introduce delay, printing to the terminal immediately will not work. 84 | lda #1 85 | @shell_rx_cleanup: 86 | plb ; Restore previous data bank register 87 | plp ; Restore previous processor status 88 | rts 89 | 90 | VIA_PORTB = VIA_BASE + $00 91 | click: ; Make speaker click 92 | .a8 ; Assume 8-bit accumulator 93 | phb ; Swap to data bank 0 94 | lda #0 95 | pha 96 | plb 97 | lda #%10000000 ; PB7 98 | tsb a:VIA_PORTB ; Set to 1 99 | nop 100 | nop 101 | nop 102 | trb a:VIA_PORTB ; Set to 0 103 | plb ; Restore data bank 104 | rts 105 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_Misc.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeMisc" 23 | 24 | /** 25 | * This file contains implementations for all OpCodes that didn't fall into other categories. 26 | */ 27 | 28 | void Cpu65816::executeMisc(OpCode &opCode) { 29 | switch (opCode.getCode()) { 30 | case (0xEB): // XBA 31 | { 32 | uint8_t lowerA = Binary::lower8BitsOf(mA); 33 | uint8_t higherA = Binary::higher8BitsOf(mA); 34 | mA = higherA | (((uint16_t) (lowerA)) << 8); 35 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(higherA); 36 | addToProgramAddressAndCycles(1, 3); 37 | break; 38 | } 39 | case (0xDB): // STP 40 | { 41 | reset(); 42 | addToProgramAddress(1); 43 | addToCycles(3); 44 | break; 45 | } 46 | case (0xCB): // WAI 47 | { 48 | setRDYPin(false); 49 | 50 | addToProgramAddress(1); 51 | addToCycles(3); 52 | break; 53 | } 54 | case (0x42): // WDM 55 | { 56 | addToProgramAddress(2); 57 | addToCycles(2); 58 | break; 59 | } 60 | case (0xEA): // NOP 61 | { 62 | addToProgramAddress(1); 63 | addToCycles(2); 64 | break; 65 | } 66 | case (0x44): // MVP 67 | { 68 | Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 69 | uint8_t destinationBank = mSystemBus.readByte(addressOfOpCodeData); 70 | addressOfOpCodeData.incrementOffsetBy(1); 71 | uint8_t sourceBank = mSystemBus.readByte(addressOfOpCodeData); 72 | 73 | Address sourceAddress(sourceBank, mX); 74 | Address destinationAddress(destinationBank, mY); 75 | 76 | while (mA != 0xFFFF) { 77 | uint8_t toTransfer = mSystemBus.readByte(sourceAddress); 78 | mSystemBus.storeByte(destinationAddress, toTransfer); 79 | 80 | sourceAddress.decrementOffsetBy(1); 81 | destinationAddress.decrementOffsetBy(1); 82 | mA--; 83 | 84 | addToCycles(7); 85 | } 86 | mDB = destinationBank; 87 | 88 | addToProgramAddress(3); 89 | break; 90 | } 91 | case (0x54): // MVN 92 | { 93 | Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 94 | uint8_t destinationBank = mSystemBus.readByte(addressOfOpCodeData); 95 | addressOfOpCodeData.incrementOffsetBy(1); 96 | uint8_t sourceBank = mSystemBus.readByte(addressOfOpCodeData); 97 | 98 | Address sourceAddress(sourceBank, mX); 99 | Address destinationAddress(destinationBank, mY); 100 | 101 | while (mA != 0xFFFF) { 102 | uint8_t toTransfer = mSystemBus.readByte(sourceAddress); 103 | mSystemBus.storeByte(destinationAddress, toTransfer); 104 | 105 | sourceAddress.incrementOffsetBy(1); 106 | destinationAddress.incrementOffsetBy(1); 107 | mA--; 108 | 109 | addToCycles(7); 110 | } 111 | mDB = destinationBank; 112 | 113 | addToProgramAddress(3); 114 | break; 115 | } 116 | default: { 117 | LOG_UNEXPECTED_OPCODE(opCode); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /hardware/programmable_logic/pin_to_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | pin_to_csv.py: Convert Galette .pin file to .csv file suitable for KiPart. 4 | 5 | This may be used to generate a KiCAD .lib (symbol library) file for the parts 6 | in this repository. 7 | """ 8 | import argparse 9 | import csv 10 | import sys 11 | from dataclasses import dataclass 12 | from typing import List 13 | 14 | 15 | @dataclass 16 | class PinDef: 17 | pin: int 18 | name: str 19 | type: str 20 | side: str 21 | 22 | @staticmethod 23 | def from_str(inp: str): 24 | # Parse one line of pin file to PinDef 25 | parts = [x.strip() for x in inp.split("|")] 26 | if len(parts) != 3: 27 | # blank lines, etc. 28 | return None 29 | if not parts[0].isnumeric(): 30 | # Header line 31 | return None 32 | # Should have a pin definition here 33 | pin_number = int(parts[0]) 34 | # Mapping for pin name 35 | pin_name = parts[1] 36 | if pin_name[0] == "/": 37 | # Active-low /FOO becomes ~FOO in KiCad 38 | pin_name = "~" + pin_name[1:] 39 | # Mapping for pin type and side 40 | pin_type_lookup = { 41 | "Clock/Input": "input", 42 | "Input": "input", 43 | "GND": "power_in", 44 | "VCC": "power_in", 45 | "Output": "output", 46 | "NC": "no_connect" 47 | } 48 | pin_side_lookup = { 49 | "Clock/Input": "left", 50 | "Input": "left", 51 | "GND": "bottom", 52 | "VCC": "top", 53 | "Output": "right", 54 | "NC": "left" 55 | } 56 | raw_pin_type = parts[2] 57 | pin_type = pin_type_lookup.get(raw_pin_type) 58 | pin_side = pin_side_lookup.get(raw_pin_type) 59 | if pin_type is None or pin_side is None: 60 | raise ValueError(f"Pin type '{raw_pin_type}' is not supported by this tool") 61 | return PinDef(pin_number, pin_name, pin_type, pin_side) 62 | 63 | @dataclass 64 | class PinDefList: 65 | definitions: List[PinDef] 66 | 67 | @staticmethod 68 | def from_str_list(inp: List[str]): 69 | """ Convert list of pin definition strings""" 70 | pin_definitions = [PinDef.from_str(x) for x in inp] 71 | # Filter out lines that did not contain a pin definition (headers/formatting) 72 | pin_definitions = [x for x in pin_definitions if x is not None] 73 | # Check for duplicate pin names/numbers 74 | pin_number_list = [x.pin for x in pin_definitions] 75 | pin_number_set = set(pin_number_list) 76 | if len(pin_number_set) != len(pin_number_list): 77 | raise ValueError("Duplicate pin number in input file.") 78 | pin_name_list = [x.name for x in pin_definitions if x.name != "NC"] 79 | pin_name_set = set(pin_name_list) 80 | if len(pin_name_set) != len(pin_name_list): 81 | raise ValueError("Duplicate pin name in input file.") 82 | # Everything is OK! :) 83 | return PinDefList(pin_definitions) 84 | 85 | 86 | def main(argv: List[str]): 87 | # Parse command-line arguments 88 | parser = argparse.ArgumentParser(description="Convert Galette .pin file to .csv file suitable for KiPart.") 89 | parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin) 90 | parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout) 91 | parser.add_argument('--name', type=str) 92 | args = parser.parse_args(argv) 93 | try: 94 | # Parse inputs 95 | pin_definitions = PinDefList.from_str_list(args.infile.readlines()) 96 | # Deliver outputs 97 | args.outfile.write(f"{args.name}\n\n") 98 | fieldnames = ["Pin", "Type", "Name", "Side"] 99 | writer = csv.DictWriter(args.outfile, fieldnames=fieldnames) 100 | writer.writeheader() 101 | writer.writerows([{'Pin': x.pin, "Type": x.type, "Name": x.name, "Side": x.side} for x in pin_definitions.definitions]) 102 | finally: 103 | args.infile.close() 104 | args.outfile.close() 105 | 106 | 107 | if __name__ == "__main__": 108 | #eg. main(["irq_controller.pin", "irq_controller.csv", "--name", "IRQ_Controller"]) 109 | main(sys.argv[1:]) 110 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_Interrupt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeInterrupt" 23 | 24 | /** 25 | * This file contains the implementation for all OpCodes 26 | * that deal with interrupts. 27 | */ 28 | 29 | void Cpu65816::executeInterrupt(OpCode &opCode) { 30 | switch (opCode.getCode()) { 31 | case (0x00): // BRK 32 | { 33 | if (mCpuStatus.emulationFlag()) { 34 | mStack.push16Bit(static_cast(mProgramAddress.getOffset() + 2)); 35 | mCpuStatus.setBreakFlag(); 36 | mStack.push8Bit(mCpuStatus.getRegisterValue()); 37 | mCpuStatus.setInterruptDisableFlag(); 38 | #ifdef EMU_65C02 39 | mCpuStatus.clearDecimalFlag(); 40 | #endif 41 | Address newAddress(0x00, mSystemBus.readTwoBytes(Address(0x00, INTERRUPT_VECTOR_EMULATION_BRK_IRQ))); 42 | setProgramAddress(newAddress); 43 | addToCycles(7); 44 | } else { 45 | mStack.push8Bit(mProgramAddress.getBank()); 46 | mStack.push16Bit(static_cast(mProgramAddress.getOffset() + 2)); 47 | mStack.push8Bit(mCpuStatus.getRegisterValue()); 48 | mCpuStatus.setInterruptDisableFlag(); 49 | mCpuStatus.clearDecimalFlag(); 50 | Address newAddress(0x00, mSystemBus.readTwoBytes(Address(0x00, INTERRUPT_VECTOR_NATIVE_BRK))); 51 | setProgramAddress(newAddress); 52 | addToCycles(8); 53 | } 54 | break; 55 | } 56 | case (0x02): // COP 57 | { 58 | if (mCpuStatus.emulationFlag()) { 59 | mStack.push16Bit(static_cast(mProgramAddress.getOffset() + 2)); 60 | mStack.push8Bit(mCpuStatus.getRegisterValue()); 61 | mCpuStatus.setInterruptDisableFlag(); 62 | Address newAddress(0x00, mSystemBus.readTwoBytes(Address(0x00, INTERRUPT_VECTOR_EMULATION_COP))); 63 | setProgramAddress(newAddress); 64 | addToCycles(7); 65 | } else { 66 | mStack.push8Bit(mProgramAddress.getBank()); 67 | mStack.push16Bit(static_cast(mProgramAddress.getOffset() + 2)); 68 | mStack.push8Bit(mCpuStatus.getRegisterValue()); 69 | mCpuStatus.setInterruptDisableFlag(); 70 | Address newAddress(0x00, mSystemBus.readTwoBytes(Address(0x00, INTERRUPT_VECTOR_NATIVE_COP))); 71 | setProgramAddress(newAddress); 72 | addToCycles(8); 73 | } 74 | mCpuStatus.clearDecimalFlag(); 75 | break; 76 | } 77 | case (0x40): // RTI 78 | { 79 | // Note: The picture in the 65816 programming manual about this looks wrong. 80 | // This implementation follows the text instead. 81 | mCpuStatus.setRegisterValue(mStack.pull8Bit()); 82 | 83 | if (mCpuStatus.emulationFlag()) { 84 | Address newProgramAddress(mProgramAddress.getBank(), mStack.pull16Bit()); 85 | mProgramAddress = newProgramAddress; 86 | addToCycles(6); 87 | } else { 88 | uint16_t offset = mStack.pull16Bit(); 89 | uint8_t bank = mStack.pull8Bit(); 90 | Address newProgramAddress(bank, offset); 91 | mProgramAddress = newProgramAddress; 92 | addToCycles(7); 93 | } 94 | break; 95 | } 96 | default: { 97 | LOG_UNEXPECTED_OPCODE(opCode); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_Branch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #include 23 | 24 | #define LOG_TAG "Cpu::executeBranch" 25 | 26 | /** 27 | * This file contains the implementation for all branch OpCodes 28 | */ 29 | 30 | int Cpu65816::executeBranchShortOnCondition(bool condition, OpCode &opCode) { 31 | uint8_t opCycles = 2; 32 | uint8_t destination = mSystemBus.readByte(getAddressOfOpCodeData(opCode)); 33 | // This is the address of the next instruction 34 | uint16_t actualDestination; 35 | if (condition) { 36 | // One extra cycle if the branch is taken 37 | opCycles++; 38 | uint16_t destination16; 39 | if (Binary::is8bitValueNegative(destination)) { 40 | destination16 = 0xFF00 | destination; 41 | } else { 42 | destination16 = destination; 43 | } 44 | actualDestination = mProgramAddress.getOffset() + 2 + destination16; 45 | // Emulation mode requires 1 extra cycle on page boundary crossing 46 | if (Address::offsetsAreOnDifferentPages(mProgramAddress.getOffset(), actualDestination) && 47 | mCpuStatus.emulationFlag()) { 48 | opCycles++; 49 | } 50 | } else { 51 | actualDestination = mProgramAddress.getOffset() + 2; 52 | } 53 | Address newProgramAddress(mProgramAddress.getBank(), actualDestination); 54 | mProgramAddress = newProgramAddress; 55 | return opCycles; 56 | } 57 | 58 | int Cpu65816::executeBranchLongOnCondition(bool condition, OpCode &opCode) { 59 | if (condition) { 60 | uint16_t destination = mSystemBus.readTwoBytes(getAddressOfOpCodeData(opCode)); 61 | mProgramAddress.incrementOffsetBy(3 + destination); 62 | } 63 | // CPU cycles: 4 64 | return 4; 65 | } 66 | 67 | void Cpu65816::executeBranch(OpCode &opCode) { 68 | 69 | switch (opCode.getCode()) { 70 | case (0xD0): // BNE 71 | { 72 | addToCycles(executeBranchShortOnCondition(!mCpuStatus.zeroFlag(), opCode)); 73 | break; 74 | } 75 | case (0xF0): // BEQ 76 | { 77 | addToCycles(executeBranchShortOnCondition(mCpuStatus.zeroFlag(), opCode)); 78 | break; 79 | } 80 | case (0x90): // BCC 81 | { 82 | addToCycles(executeBranchShortOnCondition(!mCpuStatus.carryFlag(), opCode)); 83 | break; 84 | } 85 | case (0xB0): // BCS 86 | { 87 | addToCycles(executeBranchShortOnCondition(mCpuStatus.carryFlag(), opCode)); 88 | break; 89 | } 90 | case (0x10): // BPL 91 | { 92 | int cycles = executeBranchShortOnCondition(!mCpuStatus.signFlag(), opCode); 93 | addToCycles(cycles); 94 | break; 95 | } 96 | case (0x30): // BMI 97 | { 98 | addToCycles(executeBranchShortOnCondition(mCpuStatus.signFlag(), opCode)); 99 | break; 100 | } 101 | case (0x50): // BVC 102 | { 103 | addToCycles(executeBranchShortOnCondition(!mCpuStatus.overflowFlag(), opCode)); 104 | break; 105 | } 106 | case (0x70): // BVS 107 | { 108 | addToCycles(executeBranchShortOnCondition(mCpuStatus.overflowFlag(), opCode)); 109 | break; 110 | } 111 | case (0x80): // BRA 112 | { 113 | addToCycles(executeBranchShortOnCondition(true, opCode)); 114 | break; 115 | } 116 | case (0x82): // BRL 117 | { 118 | addToCycles(executeBranchLongOnCondition(true, opCode)); 119 | break; 120 | } 121 | default: { 122 | LOG_UNEXPECTED_OPCODE(opCode); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_StatusReg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeStatusReg" 23 | 24 | /** 25 | * This file contains the implementation for all OpCodes 26 | * that deal directly with the status register. 27 | */ 28 | 29 | void Cpu65816::executeStatusReg(OpCode &opCode) { 30 | switch (opCode.getCode()) { 31 | case (0xC2): // REP #const 32 | { 33 | uint8_t value = mSystemBus.readByte(getAddressOfOpCodeData(opCode)); 34 | uint8_t statusByte = mCpuStatus.getRegisterValue(); 35 | mCpuStatus.setRegisterValue(statusByte & ~value); 36 | addToProgramAddressAndCycles(2, 3); 37 | break; 38 | } 39 | case (0x38): // SEC 40 | { 41 | mCpuStatus.setCarryFlag(); 42 | addToProgramAddressAndCycles(1, 2); 43 | break; 44 | } 45 | case (0xF8): // SED 46 | { 47 | mCpuStatus.setDecimalFlag(); 48 | addToProgramAddressAndCycles(1, 2); 49 | break; 50 | } 51 | case (0x78): // SEI 52 | { 53 | mCpuStatus.setInterruptDisableFlag(); 54 | addToProgramAddressAndCycles(1, 2); 55 | break; 56 | } 57 | case (0x58): // CLI 58 | { 59 | mCpuStatus.clearInterruptDisableFlag(); 60 | addToProgramAddressAndCycles(1, 2); 61 | break; 62 | } 63 | case (0xE2): // SEP 64 | { 65 | uint8_t value = mSystemBus.readByte(getAddressOfOpCodeData(opCode)); 66 | if (mCpuStatus.emulationFlag()) { 67 | // In emulation mode status bits 4 and 5 are not affected 68 | // 0xCF = 11001111 69 | value &= 0xCF; 70 | } 71 | uint8_t statusReg = mCpuStatus.getRegisterValue(); 72 | statusReg |= value; 73 | mCpuStatus.setRegisterValue(statusReg); 74 | 75 | addToProgramAddressAndCycles(2, 3); 76 | break; 77 | } 78 | case (0x18): // CLC 79 | { 80 | mCpuStatus.clearCarryFlag(); 81 | addToProgramAddressAndCycles(1, 2); 82 | break; 83 | } 84 | case (0xD8): // CLD 85 | { 86 | mCpuStatus.clearDecimalFlag(); 87 | addToProgramAddressAndCycles(1, 2); 88 | break; 89 | } 90 | case (0xB8): // CLV 91 | { 92 | mCpuStatus.clearOverflowFlag(); 93 | addToProgramAddressAndCycles(1, 2); 94 | break; 95 | } 96 | case (0xFB): // XCE 97 | { 98 | bool oldCarry = mCpuStatus.carryFlag(); 99 | bool oldEmulation = mCpuStatus.emulationFlag(); 100 | if (oldCarry) mCpuStatus.setEmulationFlag(); 101 | else mCpuStatus.clearEmulationFlag(); 102 | if (oldEmulation) mCpuStatus.setCarryFlag(); 103 | else mCpuStatus.clearCarryFlag(); 104 | 105 | if (mCpuStatus.emulationFlag()) { 106 | mA &= 0xFF; 107 | mX &= 0xFF; 108 | mY &= 0xFF; 109 | mCpuStatus.setAccumulatorWidthFlag(); 110 | mCpuStatus.setIndexWidthFlag(); 111 | } 112 | 113 | if(mCpuStatus.emulationFlag()) { 114 | // Set stack high to 01 if in emulation mode, otherwise maintain stack. 115 | uint16_t newStackPointer = 0x0100 + (mStack.getStackPointer() & 0xff); 116 | mStack = Stack(&mSystemBus,newStackPointer); 117 | } 118 | 119 | addToProgramAddressAndCycles(1, 2); 120 | break; 121 | } 122 | default: { 123 | LOG_UNEXPECTED_OPCODE(opCode); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_TSB_TRB.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Interrupt.h" 21 | #include "Cpu65816.h" 22 | 23 | #define LOG_TAG "Cpu::executeTSBTRB" 24 | 25 | /** 26 | * This file contains the implementation for TSB and TRB OpCodes 27 | */ 28 | 29 | void Cpu65816::execute8BitTSB(OpCode &opCode) { 30 | const Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 31 | uint8_t value = mSystemBus.readByte(addressOfOpCodeData); 32 | uint8_t lowerA = Binary::lower8BitsOf(mA); 33 | const uint8_t result = value | lowerA; 34 | mSystemBus.storeByte(addressOfOpCodeData, result); 35 | 36 | if ((value & lowerA) == 0) mCpuStatus.setZeroFlag(); 37 | else mCpuStatus.clearZeroFlag(); 38 | } 39 | 40 | void Cpu65816::execute16BitTSB(OpCode &opCode) { 41 | const Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 42 | uint16_t value = mSystemBus.readTwoBytes(addressOfOpCodeData); 43 | const uint16_t result = value | mA; 44 | mSystemBus.storeTwoBytes(addressOfOpCodeData, result); 45 | 46 | if ((value & mA) == 0) mCpuStatus.setZeroFlag(); 47 | else mCpuStatus.clearZeroFlag(); 48 | } 49 | 50 | void Cpu65816::execute8BitTRB(OpCode &opCode) { 51 | const Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 52 | uint8_t value = mSystemBus.readByte(addressOfOpCodeData); 53 | uint8_t lowerA = Binary::lower8BitsOf(mA); 54 | const uint8_t result = value & ~lowerA; 55 | mSystemBus.storeByte(addressOfOpCodeData, result); 56 | 57 | if ((value & lowerA) == 0) mCpuStatus.setZeroFlag(); 58 | else mCpuStatus.clearZeroFlag(); 59 | } 60 | 61 | void Cpu65816::execute16BitTRB(OpCode &opCode) { 62 | const Address addressOfOpCodeData = getAddressOfOpCodeData(opCode); 63 | uint16_t value = mSystemBus.readTwoBytes(addressOfOpCodeData); 64 | const uint16_t result = value & ~mA; 65 | mSystemBus.storeTwoBytes(addressOfOpCodeData, result); 66 | 67 | if ((value & mA) == 0) mCpuStatus.setZeroFlag(); 68 | else mCpuStatus.clearZeroFlag(); 69 | } 70 | 71 | void Cpu65816::executeTSBTRB(OpCode &opCode) { 72 | switch (opCode.getCode()) { 73 | case (0x0C): // TSB Absolute 74 | { 75 | if (accumulatorIs8BitWide()) { 76 | execute8BitTSB(opCode); 77 | } else { 78 | execute16BitTSB(opCode); 79 | addToCycles(2); 80 | } 81 | addToProgramAddressAndCycles(3, 6); 82 | break; 83 | } 84 | case (0x04): // TSB Direct Page 85 | { 86 | if (accumulatorIs8BitWide()) { 87 | execute8BitTSB(opCode); 88 | } else { 89 | execute16BitTSB(opCode); 90 | addToCycles(2); 91 | } 92 | if (Binary::lower8BitsOf(mD) != 0) { 93 | addToCycles(1); 94 | } 95 | addToProgramAddressAndCycles(2, 5); 96 | break; 97 | } 98 | case (0x1C): // TRB Absolute 99 | { 100 | if (accumulatorIs8BitWide()) { 101 | execute8BitTRB(opCode); 102 | } else { 103 | execute16BitTRB(opCode); 104 | addToCycles(2); 105 | } 106 | addToProgramAddressAndCycles(3, 6); 107 | break; 108 | } 109 | case (0x14): // TRB Direct Page 110 | { 111 | if (accumulatorIs8BitWide()) { 112 | execute8BitTRB(opCode); 113 | } else { 114 | execute16BitTRB(opCode); 115 | addToCycles(2); 116 | } 117 | if (Binary::lower8BitsOf(mD) != 0) { 118 | addToCycles(1); 119 | } 120 | addToProgramAddressAndCycles(2, 5); 121 | break; 122 | } 123 | default: { 124 | LOG_UNEXPECTED_OPCODE(opCode); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_ASL.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeASL" 23 | 24 | #define DO_ASL_8_BIT(value) { \ 25 | bool newCarry = value & 0x80; \ 26 | value = value << 1; \ 27 | if (newCarry) mCpuStatus.setCarryFlag(); \ 28 | else mCpuStatus.clearCarryFlag(); \ 29 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(value); \ 30 | } 31 | 32 | #define DO_ASL_16_BIT(value) { \ 33 | bool newCarry = value & 0x8000; \ 34 | value = value << 1; \ 35 | if (newCarry) mCpuStatus.setCarryFlag(); \ 36 | else mCpuStatus.clearCarryFlag(); \ 37 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(value); \ 38 | } 39 | 40 | /** 41 | * This file contains implementations for all ASL OpCodes. 42 | */ 43 | 44 | void Cpu65816::executeMemoryASL(OpCode &opCode) { 45 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 46 | 47 | if (accumulatorIs8BitWide()) { 48 | uint8_t value = mSystemBus.readByte(opCodeDataAddress); 49 | DO_ASL_8_BIT(value); 50 | mSystemBus.storeByte(opCodeDataAddress, value); 51 | } else { 52 | uint16_t value = mSystemBus.readTwoBytes(opCodeDataAddress); 53 | DO_ASL_16_BIT(value); 54 | mSystemBus.storeTwoBytes(opCodeDataAddress, value); 55 | } 56 | } 57 | 58 | void Cpu65816::executeAccumulatorASL(OpCode &opCode) { 59 | if (accumulatorIs8BitWide()) { 60 | uint8_t value = Binary::lower8BitsOf(mA); 61 | DO_ASL_8_BIT(value); 62 | Binary::setLower8BitsOf16BitsValue(&mA, value); 63 | } else { 64 | DO_ASL_16_BIT(mA); 65 | } 66 | } 67 | 68 | void Cpu65816::executeASL(OpCode &opCode) { 69 | switch (opCode.getCode()) { 70 | case (0x0A): // ASL Accumulator 71 | { 72 | executeAccumulatorASL(opCode); 73 | addToProgramAddressAndCycles(1, 2); 74 | break; 75 | } 76 | case (0x0E): // ASL Absolute 77 | { 78 | if (accumulatorIs16BitWide()) { 79 | addToCycles(2); 80 | } 81 | 82 | executeMemoryASL(opCode); 83 | addToProgramAddressAndCycles(3, 6); 84 | break; 85 | } 86 | case (0x06): // ASL Direct Page 87 | { 88 | if (accumulatorIs16BitWide()) { 89 | addToCycles(2); 90 | } 91 | if (Binary::lower8BitsOf(mD) != 0) { 92 | addToCycles(1); 93 | } 94 | 95 | executeMemoryASL(opCode); 96 | addToProgramAddressAndCycles(2, 5); 97 | break; 98 | } 99 | case (0x1E): // ASL Absolute Indexed, X 100 | { 101 | if (accumulatorIs16BitWide()) { 102 | addToCycles(2); 103 | } 104 | #ifdef EMU_65C02 105 | if (!opCodeAddressingCrossesPageBoundary(opCode)) { 106 | subtractFromCycles(1); 107 | } 108 | #endif 109 | executeMemoryASL(opCode); 110 | addToProgramAddressAndCycles(3, 7); 111 | break; 112 | } 113 | case (0x16): // ASL Direct Page Indexed, X 114 | { 115 | if (accumulatorIs16BitWide()) { 116 | addToCycles(2); 117 | } 118 | if (Binary::lower8BitsOf(mD) != 0) { 119 | addToCycles(1); 120 | } 121 | 122 | executeMemoryASL(opCode); 123 | addToProgramAddressAndCycles(2, 6); 124 | break; 125 | } 126 | default: { 127 | LOG_UNEXPECTED_OPCODE(opCode); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_LSR.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeLSR" 23 | 24 | #define DO_LSR_8_BIT(value) { \ 25 | bool newCarry = value & 0x01; \ 26 | value = value >> 1; \ 27 | if (newCarry) mCpuStatus.setCarryFlag(); \ 28 | else mCpuStatus.clearCarryFlag(); \ 29 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(value); \ 30 | } 31 | 32 | #define DO_LSR_16_BIT(value) { \ 33 | bool newCarry = value & 0x0001; \ 34 | value = value >> 1; \ 35 | if (newCarry) mCpuStatus.setCarryFlag(); \ 36 | else mCpuStatus.clearCarryFlag(); \ 37 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(value); \ 38 | } 39 | 40 | /** 41 | * This file contains implementations for all LSR OpCodes. 42 | */ 43 | 44 | void Cpu65816::executeMemoryLSR(OpCode &opCode) { 45 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 46 | 47 | if (accumulatorIs8BitWide()) { 48 | uint8_t value = mSystemBus.readByte(opCodeDataAddress); 49 | DO_LSR_8_BIT(value); 50 | mSystemBus.storeByte(opCodeDataAddress, value); 51 | } else { 52 | uint16_t value = mSystemBus.readTwoBytes(opCodeDataAddress); 53 | DO_LSR_16_BIT(value); 54 | mSystemBus.storeTwoBytes(opCodeDataAddress, value); 55 | } 56 | } 57 | 58 | void Cpu65816::executeAccumulatorLSR(OpCode &opCode) { 59 | if (accumulatorIs8BitWide()) { 60 | uint8_t value = Binary::lower8BitsOf(mA); 61 | DO_LSR_8_BIT(value); 62 | Binary::setLower8BitsOf16BitsValue(&mA, value); 63 | } else { 64 | DO_LSR_16_BIT(mA); 65 | } 66 | } 67 | 68 | void Cpu65816::executeLSR(OpCode &opCode) { 69 | switch (opCode.getCode()) { 70 | case (0x4A): // LSR Accumulator 71 | { 72 | executeAccumulatorLSR(opCode); 73 | addToProgramAddressAndCycles(1, 2); 74 | break; 75 | } 76 | case (0x4E): // LSR Absolute 77 | { 78 | executeMemoryLSR(opCode); 79 | if (accumulatorIs16BitWide()) { 80 | addToCycles(2); 81 | } 82 | addToProgramAddressAndCycles(3, 6); 83 | break; 84 | } 85 | case (0x46): // LSR Direct Page 86 | { 87 | executeMemoryLSR(opCode); 88 | if (accumulatorIs16BitWide()) { 89 | addToCycles(2); 90 | } 91 | if (Binary::lower8BitsOf(mD) != 0) { 92 | addToCycles(1); 93 | } 94 | 95 | addToProgramAddressAndCycles(2, 5); 96 | break; 97 | } 98 | case (0x5E): // LSR Absolute Indexed, X 99 | { 100 | executeMemoryLSR(opCode); 101 | if (accumulatorIs16BitWide()) { 102 | addToCycles(2); 103 | } 104 | 105 | #ifdef EMU_65C02 106 | if (!opCodeAddressingCrossesPageBoundary(opCode)) { 107 | subtractFromCycles(1); 108 | } 109 | #endif 110 | 111 | addToProgramAddressAndCycles(3, 7); 112 | break; 113 | } 114 | case (0x56): // LSR Direct Page Indexed, X 115 | { 116 | executeMemoryLSR(opCode); 117 | if (accumulatorIs16BitWide()) { 118 | addToCycles(2); 119 | } 120 | if (Binary::lower8BitsOf(mD) != 0) { 121 | addToCycles(1); 122 | } 123 | 124 | addToProgramAddressAndCycles(2, 6); 125 | break; 126 | } 127 | default: { 128 | LOG_UNEXPECTED_OPCODE(opCode); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_STA.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeSTA" 23 | 24 | /** 25 | * This file contains the implementation for all STA OpCodes. 26 | */ 27 | 28 | void Cpu65816::executeSTA(OpCode &opCode) { 29 | 30 | Address dataAddress = getAddressOfOpCodeData(opCode); 31 | if (accumulatorIs8BitWide()) { 32 | mSystemBus.storeByte(dataAddress, Binary::lower8BitsOf(mA)); 33 | } else { 34 | mSystemBus.storeTwoBytes(dataAddress, mA); 35 | addToCycles(1); 36 | } 37 | 38 | switch (opCode.getCode()) { 39 | case (0x8D): // STA Absolute 40 | { 41 | addToProgramAddress(3); 42 | addToCycles(4); 43 | break; 44 | } 45 | case (0x8F): // STA Absolute Long 46 | { 47 | addToProgramAddress(4); 48 | addToCycles(5); 49 | break; 50 | } 51 | case (0x85): // STA Direct Page 52 | { 53 | if (Binary::lower8BitsOf(mD) != 0) { 54 | addToCycles(1); 55 | } 56 | 57 | addToProgramAddress(2); 58 | addToCycles(3); 59 | break; 60 | } 61 | case (0x92): // STA Direct Page Indirect 62 | { 63 | if (Binary::lower8BitsOf(mD) != 0) { 64 | addToCycles(1); 65 | } 66 | 67 | addToProgramAddress(2); 68 | addToCycles(5); 69 | break; 70 | } 71 | case (0x87): // STA Direct Page Indirect Long 72 | { 73 | if (Binary::lower8BitsOf(mD) != 0) { 74 | addToCycles(1); 75 | } 76 | 77 | addToProgramAddress(2); 78 | addToCycles(6); 79 | break; 80 | } 81 | case (0x9D): // STA Absolute Indexed, X 82 | { 83 | addToProgramAddress(3); 84 | addToCycles(5); 85 | break; 86 | } 87 | case (0x9F): // STA Absolute Long Indexed, X 88 | { 89 | addToProgramAddress(4); 90 | addToCycles(5); 91 | break; 92 | } 93 | case (0x99): // STA Absolute Indexed, Y 94 | { 95 | addToProgramAddress(3); 96 | addToCycles(5); 97 | break; 98 | } 99 | case (0x95): // STA Direct Page Indexed, X 100 | { 101 | if (Binary::lower8BitsOf(mD) != 0) { 102 | addToCycles(1); 103 | } 104 | 105 | addToProgramAddress(2); 106 | addToCycles(4); 107 | break; 108 | } 109 | case (0x81): // STA Direct Page Indexed Indirect, X 110 | { 111 | if (Binary::lower8BitsOf(mD) != 0) { 112 | addToCycles(1); 113 | } 114 | 115 | addToProgramAddress(2); 116 | addToCycles(6); 117 | break; 118 | } 119 | case (0x91): // STA Direct Page Indirect Indexed, Y 120 | { 121 | if (Binary::lower8BitsOf(mD) != 0) { 122 | addToCycles(1); 123 | } 124 | 125 | addToProgramAddress(2); 126 | addToCycles(6); 127 | break; 128 | } 129 | case (0x97): // STA Direct Page Indirect Long Indexed, Y 130 | { 131 | if (Binary::lower8BitsOf(mD) != 0) { 132 | addToCycles(1); 133 | } 134 | 135 | addToProgramAddress(2); 136 | addToCycles(6); 137 | break; 138 | } 139 | case (0x83): // STA Stack Relative 140 | { 141 | addToProgramAddress(2); 142 | addToCycles(4); 143 | break; 144 | } 145 | case (0x93): // STA Stack Relative Indirect Indexed, Y 146 | { 147 | addToProgramAddress(2); 148 | addToCycles(7); 149 | break; 150 | } 151 | default: { 152 | LOG_UNEXPECTED_OPCODE(opCode); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /rom/main.s: -------------------------------------------------------------------------------- 1 | ; main.s: ROM startup code 2 | .import BOOTLOADER_BASE, post_start, uart_init, spi_sd_init, hexdump_memory_block, xmodem_recv 3 | 4 | .importzp ROM_PRINT_STRING, ROM_READ_CHAR, ROM_READ_DISK, ROM_PRINT_CHAR 5 | 6 | .export reset, post_done 7 | 8 | .segment "CODE" 9 | reset: 10 | .a8 11 | .i8 12 | clc ; switch to native mode 13 | xce 14 | jmp post_start 15 | 16 | post_done: ; POST passed, time to run some code 17 | .a16 ; use 16-bit accumulator and index registers 18 | .i16 19 | rep #%00110000 20 | jsr uart_init ; show a startup message 21 | ldx #rom_message 22 | cop ROM_PRINT_STRING 23 | .if .defined(WARM_BOOT) 24 | ; stub out boot process if WARM_BOOT is defined at build time. 25 | ; this is a hack for the emulator: it will place the code in RAM for us, and 26 | ; these disk routines will not work. 27 | ldx #warm_boot_message 28 | cop ROM_PRINT_STRING 29 | jml $010000 30 | .endif 31 | ; Current boot order 32 | jsr sd_init_multiple_attempts ; attempt SD card init 33 | cmp #0 34 | bne @boot_no_sd 35 | jsr boot_from_sd 36 | @boot_no_sd: ; no SD card, or boot from SD declined 37 | jsr boot_from_serial 38 | jsr boot_fail 39 | 40 | ; attempt to reset SD card up to 3 times - first attempt will often fail from a cold start 41 | MAX_ATTEMPTS := 30 42 | sd_init_multiple_attempts: 43 | ldx #0 44 | @sd_init_again: 45 | phx 46 | jsr spi_sd_init ; attempt SD reset 47 | plx 48 | cmp #0 49 | beq @sd_init_ok 50 | inx 51 | cpx #MAX_ATTEMPTS ; compare to max attempts 52 | bne @sd_init_again 53 | ldx #string_sd_reset_fail 54 | cop ROM_PRINT_STRING 55 | lda #1 ; return nonzero 56 | @sd_init_ok: 57 | rts 58 | 59 | boot_from_sd: 60 | ldx #BOOTLOADER_BASE ; destination address 61 | lda #0 ; block number 62 | ldy #1 ; number of blocks to read 63 | cop ROM_READ_DISK ; read boot sector to RAM 64 | ; Hexdump the boot sector 65 | ldx #BOOTLOADER_BASE ; source address 66 | jsr hexdump_memory_block 67 | ; Prompt for whether to run the bootloader. 68 | ldx #boot_prompt_sd 69 | cop ROM_PRINT_STRING 70 | cop ROM_READ_CHAR 71 | cmp #'y' 72 | beq @boot_from_sd_ok 73 | cmp #'Y' 74 | beq @boot_from_sd_ok 75 | ldx #newline 76 | cop ROM_PRINT_STRING 77 | rts 78 | @boot_from_sd_ok: 79 | ldx #newline 80 | cop ROM_PRINT_STRING 81 | jmp BOOTLOADER_BASE 82 | 83 | boot_from_serial: 84 | ldx #boot_prompt_serial ; Ask if user wants to boot from serial 85 | cop ROM_PRINT_STRING 86 | cop ROM_READ_CHAR 87 | cmp #'y' 88 | beq @boot_from_serial_selected 89 | cmp #'Y' 90 | beq @boot_from_serial_selected 91 | ldx #newline ; Serial not selected 92 | cop ROM_PRINT_STRING 93 | rts 94 | @boot_from_serial_selected: 95 | ldx #newline ; Serial is selected 96 | cop ROM_PRINT_STRING 97 | jsr xmodem_recv ; Receive program over serial 98 | cmp #0 ; 0 is OK, nonzero means problem 99 | bne @boot_from_serial_fail 100 | ldx #start_of_line ; move cursor back to start of line - some whitespace is printed by xmodem_recv 101 | cop ROM_PRINT_STRING 102 | jml $010000 ; Run the uploaded program 103 | @boot_from_serial_fail: 104 | ldx #start_of_line ; move cursor back to start of line 105 | cop ROM_PRINT_STRING 106 | ldx #string_boot_serial_fail ; Something went wrong with the transfer 107 | cop ROM_PRINT_STRING 108 | rts 109 | 110 | boot_fail: 111 | ldx #halt_message 112 | cop ROM_PRINT_STRING 113 | stp 114 | 115 | rom_message: ; ASCII art startup message with ROM revision. 116 | .byte $1b 117 | .asciiz "[2J+---------------------------------+\r\n| __ ____ ____ ___ _ __ |\r\n| / /_| ___| / ___( _ )/ |/ /_ |\r\n| | '_ \\___ \\| | / _ \\| | '_ \\ |\r\n| | (_) |__) | |__| (_) | | (_) | |\r\n| \\___/____/ \\____\\___/|_|\\___/ |\r\n| |\r\n| ROM revision 20 |\r\n+---------------------------------+\r\n" 118 | halt_message: .asciiz "No boot options remaining. Halted\r\n" 119 | boot_prompt_sd: .asciiz "Boot from SD card? (y/N) " 120 | string_sd_reset_fail: .asciiz "SD card init failed\r\n" 121 | boot_prompt_serial: .asciiz "Boot from serial (y/N) " 122 | string_boot_serial_fail: .asciiz "Serial transfer failed\r\n" 123 | .if .defined(WARM_BOOT) 124 | warm_boot_message: .asciiz "Performing warm boot\r\n" 125 | .endif 126 | start_of_line: 127 | .byte $1b 128 | .asciiz "[1G" 129 | newline: .asciiz "\r\n" 130 | -------------------------------------------------------------------------------- /emulator/src/devices/Via.cpp: -------------------------------------------------------------------------------- 1 | #include "Via.h" 2 | #include "Log.h" 3 | 4 | #define LOG_TAG "Via" 5 | 6 | const int IFR5 = 0x20; 7 | const int IFR6 = 0x40; 8 | const int IFR7 = 0x80; 9 | 10 | Via::Via() { 11 | state = {}; 12 | state.interruptEnableRegister = 0x80; 13 | interrupt = std::make_shared(); 14 | } 15 | 16 | Via::~Via() { 17 | 18 | } 19 | 20 | void Via::storeByte(const Address &address, uint8_t value) { 21 | uint8_t reg = address.getOffset() & 0x0F; 22 | switch(reg) { 23 | case VIA_PORTB: 24 | state.outputRegisterB = value; 25 | Log::dbg(LOG_TAG).str("Port B updated ").hex(value).show(); 26 | break; 27 | case VIA_PORTA: 28 | case VIA_PORTA_2: 29 | state.outputRegisterA = value; 30 | Log::dbg(LOG_TAG).str("Port A updated ").hex(value).show(); 31 | break; 32 | case VIA_DDRB: 33 | state.dataDirectionRegisterB = value; 34 | break; 35 | case VIA_DDRA: 36 | state.dataDirectionRegisterA = value; 37 | break; 38 | case VIA_T1C_L: 39 | state.timer1Latch = (state.timer1Latch & 0xFF00) | value; 40 | break; 41 | case VIA_T1C_H: 42 | state.timer1Latch = (state.timer1Latch & 0x00FF) | (value << 8); 43 | startTimer1(); 44 | break; 45 | case VIA_T1L_L: 46 | state.timer1Latch = (state.timer1Latch & 0xFF00) | value; 47 | break; 48 | case VIA_T1L_H: 49 | state.timer1Latch = (state.timer1Latch & 0x00FF) | (value << 8); 50 | break; 51 | case VIA_T2C_L: 52 | state.timer2Counter = (state.timer2Counter & 0xFF00) | value; 53 | break; 54 | case VIA_T2C_H: 55 | state.timer2Counter = (state.timer2Counter & 0x00FF) | (value << 8); 56 | break; 57 | case VIA_SR: 58 | state.shiftRegister = value; 59 | break; 60 | case VIA_ACR: 61 | state.auxiliaryControlRegister = value; 62 | break; 63 | case VIA_PCR: 64 | state.peripheralControlRegister = value; 65 | break; 66 | case VIA_IFR: 67 | state.interruptFlagRegister = value; 68 | break; 69 | case VIA_IER: 70 | state.interruptEnableRegister = value; 71 | break; 72 | } 73 | } 74 | 75 | uint8_t Via::readByte(const Address &address) { 76 | uint8_t reg = address.getOffset() & 0x0F; 77 | switch(reg) { 78 | case VIA_T1C_L: 79 | // Clear interrupt flag for timer 1 80 | state.interruptFlagRegister = state.interruptFlagRegister & (~IFR6); 81 | updateIrq(); 82 | return state.timer1Counter & 0xFF; 83 | case VIA_T1C_H: 84 | return (state.timer1Counter >> 8) & 0xFF; 85 | case VIA_IER: 86 | return state.interruptEnableRegister; 87 | } 88 | // TODO return values.. 89 | return 0; 90 | } 91 | 92 | bool Via::decodeAddress(const Address &in, Address &out) { 93 | out = in; 94 | return in.getBank() == 0 && (in.getOffset() & 0xFE00) == 0xC000; 95 | } 96 | 97 | void Via::clockTick(int i) { 98 | if(state.isTimer1Running) { 99 | if(state.timer1Counter < i) { 100 | // might reset timer depending on config 101 | const int ACR_T1_CONTINUOUS = 0x40; 102 | if((state.auxiliaryControlRegister & ACR_T1_CONTINUOUS) == ACR_T1_CONTINUOUS) { 103 | // Reload counter from latch and go again 104 | state.timer1Counter = state.timer1Latch; 105 | } else { 106 | // One-shot mode 107 | state.timer1Counter = 0; 108 | state.isTimer1Running = false; 109 | } 110 | // set IFR5 111 | state.interruptFlagRegister = state.interruptFlagRegister | IFR6; 112 | // might fire interrupt depending on IER 113 | updateIrq(); 114 | } else { 115 | state.timer1Counter -= i; 116 | } 117 | } 118 | } 119 | 120 | void Via::startTimer1() { 121 | // Copy latch to timer 122 | state.isTimer1Running = true; 123 | state.timer1Counter = state.timer1Latch; 124 | // Clear interrupt 125 | state.interruptFlagRegister = state.interruptFlagRegister & (~IFR6); 126 | updateIrq(); 127 | } 128 | 129 | void Via::updateIrq() { 130 | // See page 25 of the datasheet for longer formula 131 | int activeInterrupts = (state.interruptFlagRegister & state.interruptEnableRegister) & 0x7F; 132 | bool irqActive = activeInterrupts != 0; 133 | // Set or clear IFR7 accordingly 134 | if(irqActive) { 135 | state.interruptFlagRegister = state.interruptFlagRegister | IFR7; 136 | } else { 137 | state.interruptFlagRegister = state.interruptFlagRegister & (~IFR7); 138 | } 139 | this->interrupt->set(irqActive); 140 | } 141 | 142 | std::shared_ptr Via::getIrq() { 143 | return interrupt; 144 | } 145 | -------------------------------------------------------------------------------- /rom/uart.s: -------------------------------------------------------------------------------- 1 | ; uart.s: driver for NXP SC16C752 UART 2 | .import UART_BASE 3 | .export uart_init, uart_print_char, uart_recv_char, uart_printz, uart_recv_char_with_timeout 4 | 5 | ; UART registers 6 | ; Not included: FIFO ready register, Xon1 Xon2 words. 7 | UART_RHR = UART_BASE ; Receiver Holding Register (RHR) - R 8 | UART_THR = UART_BASE ; Transmit Holding Register (THR) - W 9 | UART_IER = UART_BASE + 1 ; Interrupt Enable Register (IER) - R/W 10 | UART_IIR = UART_BASE + 2 ; Interrupt Identification Register (IIR) - R 11 | UART_FCR = UART_BASE + 2 ; FIFO Control Register (FCR) - W 12 | UART_LCR = UART_BASE + 3 ; Line Control Register (LCR) - R/W 13 | UART_MCR = UART_BASE + 4 ; Modem Control Register (MCR) - R/W 14 | UART_LSR = UART_BASE + 5 ; Line Status Register (LSR) - R 15 | UART_MSR = UART_BASE + 6 ; Modem Status Register (MSR) - R 16 | UART_SPR = UART_BASE + 7 ; Scratchpad Register (SPR) - R/W 17 | ; Different meaning when LCR is logic 1 18 | UART_DLL = UART_BASE ; Divisor latch LSB - R/W 19 | UART_DLM = UART_BASE + 1 ; Divisor latch MSB - R/W 20 | ; Different meaning when LCR is %1011 1111 ($bh). 21 | UART_EFR = UART_BASE + 2 ; Enhanced Feature Register (EFR) - R/W 22 | UART_TCR = UART_BASE + 6 ; Transmission Control Register (TCR) - R/W 23 | UART_TLR = UART_BASE + 7 ; Trigger Level Register (TLR) - R/W 24 | 25 | .segment "CODE" 26 | ; uart_init: Initialize the UART 27 | uart_init: 28 | .a8 ; Use 8-bit accumulator 29 | sep #%00100000 30 | 31 | lda #$80 ; Enable divisor latches 32 | sta f:UART_LCR 33 | lda #1 ; Set divisior to 1 - on a 1.8432 MHZ XTAL1, this gets 115200bps. 34 | sta f:UART_DLL 35 | lda #0 36 | sta f:UART_DLM 37 | lda #%00010111 ; Sets up 8-n-1 38 | sta f:UART_LCR 39 | lda #%00001111 ; Enable FIFO, set DMA mode 1 40 | sta f:UART_FCR 41 | .a16 ; Revert to 16-bit accumulator 42 | rep #%00100000 43 | rts 44 | 45 | ; uart_print_char: Print a single character to the UART 46 | ; A - ASCII character to print. Only the least significant 8 bits are used 47 | uart_print_char: 48 | php 49 | .a8 ; Use 8-bit accumulator 50 | sep #%00100000 51 | pha 52 | @uart_print_wait_for_ready: ; wait until transmit register is empty 53 | lda f:UART_LSR 54 | and #%00100000 55 | cmp #%00100000 56 | bne @uart_print_wait_for_ready 57 | pla 58 | sta f:UART_THR 59 | plp ; Revert to previous setting 60 | rts 61 | 62 | ; uart_printz: Print characters to UART until a null is reached 63 | ; X - pointer to string to print 64 | uart_printz: 65 | php 66 | .a8 ; Use 8-bit accumulator 67 | sep #%00100000 68 | @uart_printz_next: 69 | lda a:$0000, X ; a: to avoid this being interpreted as direct page 70 | beq @uart_printz_done 71 | jsr uart_print_char 72 | ; sta UART_THR ; much faster but drops chars.. 73 | inx 74 | jmp @uart_printz_next 75 | @uart_printz_done: 76 | plp ; Revert to previous setting 77 | rts 78 | 79 | ; uart_recv_char: Block until a character is received, then load result to A register. 80 | uart_recv_char: 81 | php 82 | .a8 ; Use 8-bit accumulator 83 | sep #%00100000 84 | @uart_recv_wait_for_ready: 85 | lda f:UART_LSR 86 | and #%00000001 87 | cmp #%00000001 88 | bne @uart_recv_wait_for_ready 89 | lda f:UART_RHR 90 | plp ; Revert to previous setting 91 | rts 92 | 93 | ; uart_recv_char_with_timeout: Load any received character to A register and set carry bit. If no character is received 94 | ; after a short/arbitrary delay, then this will return with A=0 and a clear carry bit. 95 | uart_recv_char_with_timeout: 96 | php 97 | .a8 ; Switch to 16-bit index, 8-bit accumulator for per-byte work 98 | .i16 99 | sep #%00100000 100 | rep #%00010000 101 | ldx #$0004 ; Tune outer loop $0000-$ffff to adjust timeout 102 | : ldy #$ffff ; Inner loop 103 | : lda f:UART_LSR ; Check for character 104 | and #%00000001 105 | cmp #%00000001 106 | beq @uart_recv_got_char 107 | dey ; Inner and outer loop 108 | cpy #0 109 | bne :- 110 | dex 111 | cpx #0 112 | bne :-- 113 | lda #0 ; Timeout here 114 | plp ; Revert to previous setting 115 | clc ; ... but clear carry bit 116 | rts 117 | @uart_recv_got_char: ; Character recieved here 118 | lda f:UART_RHR 119 | plp ; Revert to previous setting 120 | sec ; ... but set carry bit 121 | rts 122 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_CPX_CPY.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeCPXCPY" 23 | 24 | /** 25 | * This file contains implementations for all CPX and CPY OpCodes. 26 | */ 27 | 28 | void Cpu65816::execute8BitCPX(OpCode &opCode) { 29 | uint8_t value = mSystemBus.readByte(getAddressOfOpCodeData(opCode)); 30 | uint8_t result = Binary::lower8BitsOf(mX) - value; 31 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(result); 32 | if (Binary::lower8BitsOf(mX) >= value) mCpuStatus.setCarryFlag(); 33 | else mCpuStatus.clearCarryFlag(); 34 | } 35 | 36 | void Cpu65816::execute16BitCPX(OpCode &opCode) { 37 | uint16_t value = mSystemBus.readTwoBytes(getAddressOfOpCodeData(opCode)); 38 | uint16_t result = mX - value; 39 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(result); 40 | if (mX >= value) mCpuStatus.setCarryFlag(); 41 | else mCpuStatus.clearCarryFlag(); 42 | } 43 | 44 | void Cpu65816::execute8BitCPY(OpCode &opCode) { 45 | uint8_t value = mSystemBus.readByte(getAddressOfOpCodeData(opCode)); 46 | uint8_t result = Binary::lower8BitsOf(mY) - value; 47 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(result); 48 | if (Binary::lower8BitsOf(mY) >= value) mCpuStatus.setCarryFlag(); 49 | else mCpuStatus.clearCarryFlag(); 50 | } 51 | 52 | void Cpu65816::execute16BitCPY(OpCode &opCode) { 53 | uint16_t value = mSystemBus.readTwoBytes(getAddressOfOpCodeData(opCode)); 54 | uint16_t result = mY - value; 55 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(result); 56 | if (mY >= value) mCpuStatus.setCarryFlag(); 57 | else mCpuStatus.clearCarryFlag(); 58 | } 59 | 60 | void Cpu65816::executeCPXCPY(OpCode &opCode) { 61 | switch (opCode.getCode()) { 62 | case (0xE0): // CPX Immediate 63 | { 64 | if (indexIs8BitWide()) { 65 | execute8BitCPX(opCode); 66 | } else { 67 | execute16BitCPX(opCode); 68 | addToProgramAddress(1); 69 | addToCycles(1); 70 | } 71 | addToProgramAddressAndCycles(2, 2); 72 | break; 73 | } 74 | case (0xEC): // CPX Absolute 75 | { 76 | if (indexIs8BitWide()) { 77 | execute8BitCPX(opCode); 78 | } else { 79 | execute16BitCPX(opCode); 80 | addToCycles(1); 81 | } 82 | addToProgramAddressAndCycles(3, 4); 83 | break; 84 | } 85 | case (0xE4): // CPX Direct Page 86 | { 87 | if (indexIs8BitWide()) { 88 | execute8BitCPX(opCode); 89 | } else { 90 | execute16BitCPX(opCode); 91 | addToCycles(1); 92 | } 93 | if (Binary::lower8BitsOf(mD) != 0) { 94 | addToCycles(1); 95 | } 96 | addToProgramAddressAndCycles(2, 3); 97 | break; 98 | } 99 | case (0xC0): // CPY Immediate 100 | { 101 | if (indexIs8BitWide()) { 102 | execute8BitCPY(opCode); 103 | } else { 104 | execute16BitCPY(opCode); 105 | addToProgramAddress(1); 106 | addToCycles(1); 107 | } 108 | addToProgramAddressAndCycles(2, 2); 109 | break; 110 | } 111 | case (0xCC): // CPY Absolute 112 | { 113 | if (indexIs8BitWide()) { 114 | execute8BitCPY(opCode); 115 | } else { 116 | execute16BitCPY(opCode); 117 | addToCycles(1); 118 | } 119 | addToProgramAddressAndCycles(3, 4); 120 | break; 121 | } 122 | case (0xC4): // CPY Direct Page 123 | { 124 | if (indexIs8BitWide()) { 125 | execute8BitCPY(opCode); 126 | } else { 127 | execute16BitCPY(opCode); 128 | addToCycles(1); 129 | } 130 | if (Binary::lower8BitsOf(mD) != 0) { 131 | addToCycles(1); 132 | } 133 | addToProgramAddressAndCycles(2, 3); 134 | break; 135 | } 136 | default: { 137 | LOG_UNEXPECTED_OPCODE(opCode); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /rom/interrupt.s: -------------------------------------------------------------------------------- 1 | ; interrupt.s: Handling of software interrupts, the interface into the ROM for 2 | ; software (eg. bootloaders) 3 | ; 4 | ; Usage: Set registers and use 'cop' to trigger software interrupt. 5 | ; Eg: 6 | ; ldx #'a' 7 | ; cop ROM_PRINT_CHAR 8 | ; CPU should be in native mode with all registers 16-bit. 9 | 10 | .import uart_printz, uart_recv_char, uart_print_char, spi_sd_block_read 11 | .export cop_handler 12 | .export ROM_PRINT_CHAR, ROM_READ_CHAR, ROM_PRINT_STRING, ROM_READ_DISK 13 | 14 | ; Routines available in ROM via software interrupts. 15 | ; Print one ASCII char. 16 | ; A is char to print 17 | ROM_PRINT_CHAR := $00 18 | 19 | ; Read one ASCII char. 20 | ; Returns typed character in A register 21 | ROM_READ_CHAR := $01 22 | 23 | ; Print a null-terminated ASCII string. 24 | ; X is address of string, use data bank register for addresses outside bank 0. 25 | ROM_PRINT_STRING := $02 26 | 27 | ; Read data from disk to RAM in 512 byte blocks. 28 | ; X is address to write to, use data bank register for addresses outside bank 0. 29 | ; A is low 2 bytes of block number 30 | ; Y is number of blocks to read 31 | ROM_READ_DISK := $03 32 | 33 | .segment "CODE" 34 | ; table of routines 35 | cop_routines: 36 | .word rom_print_char_handler 37 | .word rom_read_char_hanlder 38 | .word rom_print_string_handler 39 | .word rom_read_disk_handler 40 | 41 | cop_handler: 42 | .a16 ; use 16-bit accumulator and index registers 43 | .i16 44 | rep #%00110000 45 | ; Save caller context to stack 46 | pha ; Push A, X, Y 47 | phx 48 | phy 49 | phb ; Push data bank, direct register 50 | phd 51 | ; Set up stack frame for COP handler 52 | tsc 53 | sec 54 | sbc #cop_handler_local_vars_size 55 | tcs 56 | phd 57 | tcd 58 | caller_k := 15 59 | caller_ret := 13 60 | caller_p := 12 61 | caller_a := 10 62 | caller_x := 8 63 | caller_y := 6 64 | caller_b := 5 65 | caller_d := 3 66 | cop_call_addr := 0 67 | ; set up 24 bit pointer to COP instruction 68 | ldx . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeROL" 23 | 24 | #define DO_ROL_8_BIT(value) { \ 25 | bool carryWasSet = mCpuStatus.carryFlag(); \ 26 | bool carryWillBeSet = (value & 0x80) != 0; \ 27 | value = value << 1; \ 28 | if (carryWasSet) Binary::setBitIn8BitValue(&value, 0); \ 29 | else Binary::clearBitIn8BitValue(&value, 0); \ 30 | if (carryWillBeSet) mCpuStatus.setCarryFlag(); \ 31 | else mCpuStatus.clearCarryFlag(); \ 32 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(value); \ 33 | } 34 | 35 | #define DO_ROL_16_BIT(value) { \ 36 | bool carryWasSet = mCpuStatus.carryFlag(); \ 37 | bool carryWillBeSet = (value & 0x8000) != 0; \ 38 | value = value << 1; \ 39 | if (carryWasSet) Binary::setBitIn16BitValue(&value, 0); \ 40 | else Binary::clearBitIn16BitValue(&value, 0); \ 41 | if (carryWillBeSet) mCpuStatus.setCarryFlag(); \ 42 | else mCpuStatus.clearCarryFlag(); \ 43 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(value); \ 44 | } 45 | 46 | /** 47 | * This file contains implementations for all ROL OpCodes. 48 | */ 49 | void Cpu65816::executeMemoryROL(OpCode &opCode) { 50 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 51 | 52 | if (accumulatorIs8BitWide()) { 53 | uint8_t value = mSystemBus.readByte(opCodeDataAddress); 54 | DO_ROL_8_BIT(value); 55 | mSystemBus.storeByte(opCodeDataAddress, value); 56 | } else { 57 | uint16_t value = mSystemBus.readTwoBytes(opCodeDataAddress); 58 | DO_ROL_16_BIT(value); 59 | mSystemBus.storeTwoBytes(opCodeDataAddress, value); 60 | } 61 | } 62 | 63 | void Cpu65816::executeAccumulatorROL(OpCode &opCode) { 64 | if (accumulatorIs8BitWide()) { 65 | uint8_t value = Binary::lower8BitsOf(mA); 66 | DO_ROL_8_BIT(value); 67 | Binary::setLower8BitsOf16BitsValue(&mA, value); 68 | } else { 69 | uint16_t value = mA; 70 | DO_ROL_16_BIT(value); 71 | mA = value; 72 | } 73 | } 74 | 75 | void Cpu65816::executeROL(OpCode &opCode) { 76 | switch (opCode.getCode()) { 77 | case (0x2A): // ROL accumulator 78 | { 79 | executeAccumulatorROL(opCode); 80 | addToProgramAddressAndCycles(1, 2); 81 | break; 82 | } 83 | case (0x2E): // ROL #addr 84 | { 85 | executeMemoryROL(opCode); 86 | if (accumulatorIs8BitWide()) { 87 | addToProgramAddressAndCycles(3, 6); 88 | } else { 89 | addToProgramAddressAndCycles(3, 8); 90 | } 91 | break; 92 | } 93 | case (0x26): // ROL Direct Page 94 | { 95 | executeMemoryROL(opCode); 96 | int opCycles = Binary::lower8BitsOf(mD) != 0 ? 1 : 0; 97 | if (accumulatorIs8BitWide()) { 98 | addToProgramAddressAndCycles(2, 5 + opCycles); 99 | } else { 100 | addToProgramAddressAndCycles(2, 7 + opCycles); 101 | } 102 | break; 103 | } 104 | case (0x3E): // ROL Absolute Indexed, X 105 | { 106 | executeMemoryROL(opCode); 107 | #ifdef EMU_65C02 108 | short opCycles = opCodeAddressingCrossesPageBoundary(opCode) ? 0 : -1; 109 | #else 110 | short opCycles = 0; 111 | #endif 112 | if (accumulatorIs8BitWide()) { 113 | addToProgramAddressAndCycles(3, 7 + opCycles); 114 | } else { 115 | addToProgramAddressAndCycles(3, 9 + opCycles); 116 | } 117 | break; 118 | } 119 | case (0x36): // ROL Direct Page Indexed, X 120 | { 121 | executeMemoryROL(opCode); 122 | int opCycles = Binary::lower8BitsOf(mD) != 0 ? 1 : 0; 123 | if (accumulatorIs8BitWide()) { 124 | addToProgramAddressAndCycles(2, 6 + opCycles); 125 | } else { 126 | addToProgramAddressAndCycles(2, 8 + opCycles); 127 | } 128 | break; 129 | } 130 | default: { 131 | LOG_UNEXPECTED_OPCODE(opCode); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_ROR.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeROR" 23 | 24 | #define DO_ROR_8_BIT(value) { \ 25 | bool carryWasSet = mCpuStatus.carryFlag(); \ 26 | bool carryWillBeSet = (value & 0x01) != 0; \ 27 | value = value >> 1; \ 28 | if (carryWasSet) Binary::setBitIn8BitValue(&value, 7); \ 29 | else Binary::clearBitIn8BitValue(&value, 7); \ 30 | if (carryWillBeSet) mCpuStatus.setCarryFlag(); \ 31 | else mCpuStatus.clearCarryFlag(); \ 32 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(value); \ 33 | } 34 | 35 | #define DO_ROR_16_BIT(value) { \ 36 | bool carryWasSet = mCpuStatus.carryFlag(); \ 37 | bool carryWillBeSet = (value & 0x0001) != 0; \ 38 | value = value >> 1; \ 39 | if (carryWasSet) Binary::setBitIn16BitValue(&value, 15); \ 40 | else Binary::clearBitIn16BitValue(&value, 15); \ 41 | if (carryWillBeSet) mCpuStatus.setCarryFlag(); \ 42 | else mCpuStatus.clearCarryFlag(); \ 43 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(value); \ 44 | } 45 | 46 | /** 47 | * This file contains implementations for all ROR OpCodes. 48 | */ 49 | void Cpu65816::executeMemoryROR(OpCode &opCode) { 50 | Address opCodeDataAddress = getAddressOfOpCodeData(opCode); 51 | 52 | if (accumulatorIs8BitWide()) { 53 | uint8_t value = mSystemBus.readByte(opCodeDataAddress); 54 | DO_ROR_8_BIT(value); 55 | mSystemBus.storeByte(opCodeDataAddress, value); 56 | } else { 57 | uint16_t value = mSystemBus.readTwoBytes(opCodeDataAddress); 58 | DO_ROR_16_BIT(value); 59 | mSystemBus.storeTwoBytes(opCodeDataAddress, value); 60 | } 61 | } 62 | 63 | void Cpu65816::executeAccumulatorROR(OpCode &opCode) { 64 | if (accumulatorIs8BitWide()) { 65 | uint8_t value = Binary::lower8BitsOf(mA); 66 | DO_ROR_8_BIT(value); 67 | Binary::setLower8BitsOf16BitsValue(&mA, value); 68 | } else { 69 | uint16_t value = mA; 70 | DO_ROR_16_BIT(value); 71 | mA = value; 72 | } 73 | } 74 | 75 | void Cpu65816::executeROR(OpCode &opCode) { 76 | switch (opCode.getCode()) { 77 | case (0x6A): // ROR accumulator 78 | { 79 | executeAccumulatorROR(opCode); 80 | addToProgramAddressAndCycles(1, 2); 81 | break; 82 | } 83 | case (0x6E): // ROR #addr 84 | { 85 | executeMemoryROR(opCode); 86 | if (accumulatorIs8BitWide()) { 87 | addToProgramAddressAndCycles(3, 6); 88 | } else { 89 | addToProgramAddressAndCycles(3, 8); 90 | } 91 | break; 92 | } 93 | case (0x66): // ROR Direct Page 94 | { 95 | executeMemoryROR(opCode); 96 | int opCycles = Binary::lower8BitsOf(mD) != 0 ? 1 : 0; 97 | if (accumulatorIs8BitWide()) { 98 | addToProgramAddressAndCycles(2, 5 + opCycles); 99 | } else { 100 | addToProgramAddressAndCycles(2, 7 + opCycles); 101 | } 102 | break; 103 | } 104 | case (0x7E): // ROR Absolute Indexed, X 105 | { 106 | executeMemoryROR(opCode); 107 | #ifdef EMU_65C02 108 | short opCycles = opCodeAddressingCrossesPageBoundary(opCode) ? 0 : -1; 109 | #else 110 | short opCycles = 0; 111 | #endif 112 | if (accumulatorIs8BitWide()) { 113 | addToProgramAddressAndCycles(3, 7 + opCycles); 114 | } else { 115 | addToProgramAddressAndCycles(3, 9 + opCycles); 116 | } 117 | break; 118 | } 119 | case (0x76): // ROR Direct Page Indexed, X 120 | { 121 | executeMemoryROR(opCode); 122 | int opCycles = Binary::lower8BitsOf(mD) != 0 ? 1 : 0; 123 | if (accumulatorIs8BitWide()) { 124 | addToProgramAddressAndCycles(2, 6 + opCycles); 125 | } else { 126 | addToProgramAddressAndCycles(2, 8 + opCycles); 127 | } 128 | break; 129 | } 130 | default: { 131 | LOG_UNEXPECTED_OPCODE(opCode); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /software/kernel/panic.s: -------------------------------------------------------------------------------- 1 | .export panic 2 | 3 | .segment "CODE" 4 | ; panic: Dump registers to console and halt CPU. 5 | panic: 6 | php ; Save processor status 7 | .a16 8 | .i16 9 | rep #%00110000 10 | ; Preserve registers to stack 11 | pha ; Push A, X, Y 12 | phx 13 | phy 14 | phb ; Push data bank, program bank 15 | phk 16 | phd ; push direct page register 17 | tsc 18 | pha ; push stack pointer as well - does not match exact value from when panic was called 19 | tsc ; set direct register to stack pointer 20 | tcd 21 | ; Set up 16 bit values for printing 22 | lda $0d 23 | and #$ff 24 | pha ; P 25 | lda $03 26 | pha ; D 27 | lda $01 28 | pha ; S 29 | lda $05 30 | and #$ff 31 | pha ; PBR 32 | lda $06 33 | and #$ff 34 | pha ; DBR 35 | 36 | lda $07 ; Y 37 | pha 38 | lda $09 ; X 39 | pha 40 | lda $0b ; A 41 | pha 42 | lda $0e ; PC 43 | pha 44 | ; Print the panic message 45 | ldx #panic_message 46 | jsr kprintf 47 | lda #$ff ; this will be the exit code of the emulator process 48 | stp 49 | 50 | panic_message: .asciiz "\n** kernel panic at PC = $%u **\nA = $%u, X = $%u, Y = $%u\nDBR = $%u, PBR = $%u\nS = $%u, D = $%u, P = $%u\n" 51 | 52 | ; kprintf: print a formatted string, in a style similar to printf. Contains unconventional stack usage, intentionally not exported. 53 | ; X - pointer to format string 54 | ; Stack - a 16 bit value will be read from the stack and formatted each time %c %s %x or %u is encountered 55 | kprintf: 56 | ldy #0 ; Y is used by some code paths, so always clobber it 57 | .a8 ; Use 8-bit accumulator 58 | sep #%00100000 59 | @kprintf_next: 60 | lda a:$0000, X ; a: to avoid this being interpreted as direct page 61 | beq @kprintf_done ; stop on null 62 | inx 63 | cmp #'%' ; break on special % char 64 | beq @kprintf_check_specifier 65 | jsr _kprintchar 66 | jmp @kprintf_next 67 | @kprintf_check_specifier: ; handle % char by reading format specifier 68 | lda a:$0000, X 69 | beq @kprintf_done ; end of string.. 70 | inx 71 | cmp #'%' ; %% 72 | bne @check_char 73 | lda #'%' 74 | jsr _kprintchar 75 | jmp @kprintf_next 76 | @check_char: 77 | cmp #'c' ; %c 78 | bne @check_string 79 | jmp @kprintf_char 80 | @check_string: 81 | cmp #'s' ; %s 82 | bne @check_hex 83 | jmp @kprintf_string 84 | @check_hex: 85 | cmp #'x' ; %x 86 | bne @check_unsigned_dec 87 | jmp @kprintf_hex 88 | @check_unsigned_dec: 89 | cmp #'u' ; %u 90 | bne @bad_format 91 | jmp @kprintf_unsigned_dec 92 | @bad_format: ; This is an error 93 | lda #'?' 94 | @kprintf_done: 95 | .a16 ; Revert to 16-bit accumulator 96 | rep #%00100000 97 | rts 98 | 99 | @kprintf_char: ; print one character %c 100 | .a16 101 | rep #%00100000 102 | ply ; return address 103 | pla ; A is char to print 104 | phy ; put return address back 105 | .a8 106 | sep #%00100000 107 | jsr _kprintchar 108 | jmp @kprintf_next 109 | 110 | @kprintf_string: ; print null-terminated string %s 111 | .a16 112 | rep #%00100000 113 | pla ; return address 114 | ply 115 | pha ; put return address back 116 | .a8 117 | sep #%00100000 118 | @string_next_char: 119 | lda a:$0000, Y 120 | beq @string_done 121 | iny 122 | jsr _kprintchar 123 | jmp @string_next_char 124 | @string_done: 125 | jmp @kprintf_next 126 | 127 | @kprintf_hex: ; print a hex value %x 128 | .a16 129 | rep #%00100000 130 | ply ; return address 131 | pla ; A is char to print 132 | phy ; put return address back 133 | phx 134 | pha ; save A for later 135 | xba 136 | and #$00ff 137 | cmp #0 ; skip first byte if 0 138 | beq @kprintf_hex_second_byte 139 | jsr @kprintf_hex_byte ; print most significant byte. 140 | @kprintf_hex_second_byte: 141 | pla 142 | and #$00ff 143 | jsr @kprintf_hex_byte ; print least significant byte 144 | .a8 ; switch back to 8-bit accumulator 145 | sep #%00100000 146 | plx 147 | jmp @kprintf_next 148 | @kprintf_hex_byte: 149 | .a16 ; accumulator is 16-bit when called 150 | .a8 ; use 8-bit accumulator 151 | sep #%00100000 152 | pha ; store byte for later 153 | lsr ; shift out lower nibble 154 | lsr 155 | lsr 156 | lsr 157 | tax 158 | lda hex_chars, X ; convert 0-15 to ascii char for hex digit 159 | jsr _kprintchar 160 | pla 161 | and #$0f 162 | tax 163 | lda hex_chars, X 164 | jsr _kprintchar 165 | .a16 ; Revert to 16-bit accumulator 166 | rep #%00100000 167 | rts 168 | 169 | @kprintf_unsigned_dec: ; TODO not supported 170 | jmp @kprintf_hex 171 | 172 | _kprintchar: 173 | .a8 174 | sta $c200 175 | rts 176 | 177 | hex_chars: .byte '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 178 | -------------------------------------------------------------------------------- /emulator/src/cpu/opcodes/OpCode_CMP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the 65816 Emulator Library. 3 | * Copyright (c) 2018 Francesco Rigoni. 4 | * 5 | * https://github.com/FrancescoRigoni/Lib65816 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "Cpu65816.h" 21 | 22 | #define LOG_TAG "Cpu::executeCMP" 23 | 24 | /** 25 | * This file contains the implementation for all CMP OpCodes 26 | */ 27 | 28 | void Cpu65816::execute8BitCMP(OpCode &opCode) { 29 | Address valueAddress = getAddressOfOpCodeData(opCode); 30 | uint8_t value = mSystemBus.readByte(valueAddress); 31 | uint8_t result = Binary::lower8BitsOf(mA) - value; 32 | mCpuStatus.updateSignAndZeroFlagFrom8BitValue(result); 33 | if (Binary::lower8BitsOf(mA) >= value) { 34 | mCpuStatus.setCarryFlag(); 35 | } else { 36 | mCpuStatus.clearCarryFlag(); 37 | } 38 | } 39 | 40 | void Cpu65816::execute16BitCMP(OpCode &opCode) { 41 | Address valueAddress = getAddressOfOpCodeData(opCode); 42 | uint16_t value = mSystemBus.readTwoBytes(valueAddress); 43 | uint16_t result = mA - value; 44 | mCpuStatus.updateSignAndZeroFlagFrom16BitValue(result); 45 | if (mA >= value) { 46 | mCpuStatus.setCarryFlag(); 47 | } else { 48 | mCpuStatus.clearCarryFlag(); 49 | } 50 | } 51 | 52 | void Cpu65816::executeCMP(OpCode &opCode) { 53 | if (accumulatorIs8BitWide()) { 54 | execute8BitCMP(opCode); 55 | } else { 56 | execute16BitCMP(opCode); 57 | addToCycles(1); 58 | } 59 | 60 | switch (opCode.getCode()) { 61 | case (0xC9): // CMP Immediate 62 | { 63 | if (accumulatorIs16BitWide()) { 64 | addToProgramAddress(1); 65 | } 66 | addToProgramAddressAndCycles(2, 2); 67 | break; 68 | } 69 | case (0xCD): // CMP Absolute 70 | { 71 | addToProgramAddressAndCycles(3, 4); 72 | break; 73 | } 74 | case (0xCF): // CMP Absolute Long 75 | { 76 | addToProgramAddressAndCycles(4, 5); 77 | break; 78 | } 79 | case (0xC5): // CMP Direct Page 80 | { 81 | if (Binary::lower8BitsOf(mD) != 0) { 82 | addToCycles(1); 83 | } 84 | addToProgramAddressAndCycles(2, 3); 85 | break; 86 | } 87 | case (0xD2): // CMP Direct Page Indirect 88 | { 89 | if (Binary::lower8BitsOf(mD) != 0) { 90 | addToCycles(1); 91 | } 92 | addToProgramAddressAndCycles(2, 5); 93 | break; 94 | } 95 | case (0xC7): // CMP Direct Page Indirect Long 96 | { 97 | addToProgramAddressAndCycles(2, 6); 98 | break; 99 | } 100 | case (0xDD): // CMP Absolute Indexed, X 101 | { 102 | if (opCodeAddressingCrossesPageBoundary(opCode)) { 103 | addToCycles(1); 104 | } 105 | addToProgramAddressAndCycles(3, 4); 106 | break; 107 | } 108 | case (0xDF): // CMP Absolute Long Indexed, X 109 | { 110 | addToProgramAddressAndCycles(4, 5); 111 | break; 112 | } 113 | case (0xD9): // CMP Absolute Indexed, Y 114 | { 115 | if (opCodeAddressingCrossesPageBoundary(opCode)) { 116 | addToCycles(1); 117 | } 118 | addToProgramAddressAndCycles(3, 4); 119 | break; 120 | } 121 | case (0xD5): // CMP Direct Page Indexed, X 122 | { 123 | if (Binary::lower8BitsOf(mD) != 0) { 124 | addToCycles(1); 125 | } 126 | addToProgramAddressAndCycles(2, 4); 127 | break; 128 | } 129 | case (0xC1): // CMP Direct Page Indexed Indirect, X 130 | { 131 | if (Binary::lower8BitsOf(mD) != 0) { 132 | addToCycles(1); 133 | } 134 | addToProgramAddressAndCycles(2, 6); 135 | break; 136 | } 137 | case (0xD1): // CMP Direct Page Indexed Indirect, Y 138 | { 139 | if (Binary::lower8BitsOf(mD) != 0) { 140 | addToCycles(1); 141 | } 142 | if (opCodeAddressingCrossesPageBoundary(opCode)) { 143 | addToCycles(1); 144 | } 145 | addToProgramAddressAndCycles(2, 5); 146 | break; 147 | } 148 | case (0xD7): // CMP Direct Page Indirect Long Indexed, Y 149 | { 150 | if (Binary::lower8BitsOf(mD) != 0) { 151 | addToCycles(1); 152 | } 153 | addToProgramAddressAndCycles(2, 6); 154 | break; 155 | } 156 | case (0xC3): // CMP Stack Relative 157 | { 158 | addToProgramAddressAndCycles(2, 4); 159 | break; 160 | } 161 | case (0xD3): // CMP Stack Relative Indirect Indexed, Y 162 | { 163 | addToProgramAddressAndCycles(2, 7); 164 | break; 165 | } 166 | default: { 167 | LOG_UNEXPECTED_OPCODE(opCode); 168 | } 169 | } 170 | } 171 | --------------------------------------------------------------------------------