commit 3fc52b8adc772d582d24c6c23f3894ea5eb64fd4 Author: Rui Barbosa <1+ruifpb@noreply.git.ruifpb.pt> Date: Tue Sep 29 12:52:16 2026 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5eab68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + + +.idea/ +symon*.jar +*.bin +*.map \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9bc07d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +OUTPUT := example.bin +TARGET_CPU := 65c02 + +MEM_MAP_FILE := $(OUTPUT:.bin=.map) + +# Tool list +CC65 := cc65 +CA65 := ca65 +LD65 := ld65 +CL65 := cl65 + +# Searches directly under src and one level under. If a file is under 2 folders +#such as src/math/float/types.s, either one more level needs to be added, or +#switch these to a shell find $(shell find src -name '*.s') +C_SOURCES := $(wildcard src/*.c) $(wildcard src/*/*.s) +ASM_SOURCES := $(wildcard src/*.s) $(wildcard src/*/*.s) +OBJECTS := $(ASM_SOURCES:.s=.o) + +.PHONY: all clean + +all: $(OUTPUT) + +$(OUTPUT): $(C_SOURCES) $(ASM_SOURCES) + @mkdir -p $(dir $@) + $(CL65) -t none --cpu $(TARGET_CPU) -C computer.cfg -m $(MEM_MAP_FILE) -O -o $@ $^ + +clean: + rm -f $(OBJECTS) + rm -f $(OUTPUT) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e8c1f9 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# 65C02 Assembly Project Template + +This template is based on the memory map for Mike42's computer/board. [Github](https://github.com/mike42/6502-computer/) + diff --git a/computer.cfg b/computer.cfg new file mode 100644 index 0000000..c93fa5c --- /dev/null +++ b/computer.cfg @@ -0,0 +1,16 @@ +# Physical Memory configuration +MEMORY { + ZP: start = $00, size = $0100, type = rw, file = ""; + RAM: start = $0200, size = $7d00, type = rw, file = ""; + PRG: start = $C000, size = $4000, type = ro, file = %O, fill = yes, fillval = $00; +} + +# In-Code Segment separators +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + BSS: load = RAM, type = bss; + CODE: load = PRG, type = ro, start = $C000; + DATA: load = PRG, run = RAM, type = rw, define = yes; + RODATA: load = PRG, type = ro; + VECTORS: load = PRG, type = ro, start = $FFFA; +} \ No newline at end of file diff --git a/src/hardware/hardware.inc b/src/hardware/hardware.inc new file mode 100644 index 0000000..131d024 --- /dev/null +++ b/src/hardware/hardware.inc @@ -0,0 +1,31 @@ +.ifndef HARDWARE_INC +HARDWARE_INC = 1 + +; CPU settings +CPU_HZ = 1843200 + +; Serial/ACIA addresses +SERIAL_DATA = $8400 +SERIAL_STATUS = $8401 +SERIAL_COMMAND = $8402 +SERIAL_CONTROL = $8403 + +; VIA addresses +VIA_PORTB = $8000 +VIA_PORTA = $8001 +VIA_DDRB = $8002 +VIA_DDRA = $8003 +VIA_T1C_L = $8004 +VIA_T1C_H = $8005 +VIA_T1L_L = $8006 +VIA_T1L_H = $8007 +VIA_T2C_L = $8008 +VIA_T2C_H = $8009 +VIA_SR = $800a +VIA_ACR = $800b +VIA_PCR = $800c +VIA_IFR = $800d +VIA_IER = $800e +VIA_PORTA_2 = $800f + +.endif \ No newline at end of file diff --git a/src/hardware/serial.s b/src/hardware/serial.s new file mode 100644 index 0000000..392e1c5 --- /dev/null +++ b/src/hardware/serial.s @@ -0,0 +1,90 @@ +; Serial port helper function +; Serial is handled by the 65c51n (this thing has a few bugs) +.export serial_setup, serial_recv_char, serial_print_char, serial_baud_19200, serial_baud_9600 + +.include "hardware.inc" + +; ----------------------------- +; Active wait loop calculations +; ----------------------------- +INNER_LOOP = 9 ; Inner loop count (sets granularity of the waiting period) + +; cycles per outer pass: ldx(2) + innerLoop(5*INNER_LOOP-1) + dey(2) + bne(3) +PASS_CYC = INNER_LOOP * 5 + 6 + +; passes for each 10bit frame (8N1), rounded up +PASSES_19200 = (10 * CPU_HZ/19200 + PASS_CYC - 1) / PASS_CYC +PASSES_9600 = (10 * CPU_HZ/9600 + PASS_CYC - 1) / PASS_CYC + +.assert PASSES_9600 < 256, error, "CPU clock too fast for this delay; raise INNER_LOOP" + +; ACIA control register presets: internal BAUD, 8N1 +SERIAL_CTRL_19200 = %00011111 +SERIAL_CTRL_9600 = %00011110 + ; ||||````- 19200/9600 baud + ; |||`----- Receiver clock is baud rate + ; |``------ 8bit word + ; `-------- 1 stop bit + +; ----------------------------- +; ----------------------------- + +.zeropage +tx_passes: .res 1 ; Variable to store outer loop passes + +.code +serial_setup: + ; 65c51n has a few bugs. check http://forum.6502.org/viewtopic.php?f=4&t=2543&start=30#p29795 + lda #$00 + sta SERIAL_STATUS ; Write to the status register for program reset + + lda #$0b ; %0000 1011 + sta SERIAL_COMMAND ; || |||`- Data terminal ready (DTRB low) + ; || ||`-- IRQB disabled + ; || ``--- RTSB low, transmit interrupt disabled + ; |`------ Receiver normal mode + ; `------- Parity disabled + + jsr serial_baud_19200 + rts + +serial_baud_19200: + lda #SERIAL_CTRL_19200 + sta SERIAL_CONTROL + lda #PASSES_19200 + sta tx_passes + rts + +serial_baud_9600: + lda #SERIAL_CTRL_9600 + sta SERIAL_CONTROL + lda #PASSES_9600 + sta tx_passes + rts + +serial_recv_char: + lda SERIAL_STATUS ; read status + and #$08 ; mask receive status flag + beq serial_recv_char; if buffer is empty, loop + lda SERIAL_DATA ; get byte + rts + +serial_print_char: + sta SERIAL_DATA ; send character + ;fall through to the delay routine + +serial_delay: + phy ; save y reg + phx ; save x register + ldy tx_passes ; outer passes +@ydelay: + ldx #INNER_LOOP ; Init x register +@xdelay: ; loop time was calculated above by the assembler + dex ; decrement low index + bne @xdelay ; loop back until x is 0 + dey ; decrement high + bne @ydelay ; loop until y is 0 +@delay_done: + plx ; return x + ply ; return y + rts diff --git a/src/hardware/via.s b/src/hardware/via.s new file mode 100644 index 0000000..f87e278 --- /dev/null +++ b/src/hardware/via.s @@ -0,0 +1 @@ +.include "hardware.inc" \ No newline at end of file diff --git a/src/main.s b/src/main.s new file mode 100644 index 0000000..ab2f3bf --- /dev/null +++ b/src/main.s @@ -0,0 +1,72 @@ +.import serial_setup, serial_print_char, serial_recv_char +.import copy_data + +; --------------- +; ZP +; --------------- +.zeropage +; Zero-page variables ($0000 - $00ff) + +; --------------- +; BSS +; --------------- +.bss +; RAM variables ($0200 - $7fff) + +; --------------- +; DATA +; --------------- +.data +; Initialized RAM variables ($0200 - $7fff) + +; --------------- +; CODE +; --------------- +.code +; Actual program code ($c000 - $fff9) +reset: ; Program starts here +; System setup + sei ; disable interrupts while setting things up + cld ; clear BCD mode flag (undefined on NMOS 6502, auto cleared on 65C02) + ldx #$ff + txs ; Reset stack pointer + + jsr copy_data ; copy data variables from ROM to RAM + + jsr serial_setup ; setup serial interface (65C51N). 19200 baud set as default + +print_hello_world: + ldx #0 +@loop: + lda str_hello_world,x ; get char + beq @end ; IF char is 0, done + jsr serial_print_char ; send char to console + inx ; increment x (next char) + jmp @loop +@end: + jmp @end + + +irq_handler: ; IRQ Handler + rti ; Do nothing + +nmi_handler: ; NMI handler + rti ; Do nothing + + +; --------------- +; RODATA +; --------------- +.rodata +; Constants +str_hello_world: .asciiz "Hello, World!" + + +; --------------- +; VECTORS +; --------------- +.segment "VECTORS" +; NMI, RESET and IRQ vectors +.word nmi_handler +.word reset +.word irq_handler \ No newline at end of file diff --git a/src/mem_copy.s b/src/mem_copy.s new file mode 100644 index 0000000..dfe06c2 --- /dev/null +++ b/src/mem_copy.s @@ -0,0 +1,46 @@ +.import __DATA_LOAD__, __DATA_RUN__, __DATA_SIZE__ +.export copy_data + +.zeropage +src: .res 2 +dst: .res 2 + +.code +copy_data: +; Copy data segment from ROM to final RAM location + lda #<__DATA_LOAD__ ; get source address low byte + sta src + lda #>__DATA_LOAD__ ; get source address high byte + sta src+1 + + lda #<__DATA_RUN__ ; get runtime final address low byte + sta dst + lda #>__DATA_RUN__ ; get runtime final address high byte + sta dst+1 + + ldx #>__DATA_SIZE__ ; number of 256 byte pages to copy (high byte) + beq @tail ; if no full pages to copy, skip + ldy #0 + +@page: + lda (src),y ; grab byte + sta (dst),y ; store byte + iny + bne @page ; for 256 cycles + inc src+1 ; increment source high byte (next memory page) + inc dst+1 ; increment destination high byte (next memory page) + dex ; decrement full pages counter + bne @page ; if no more full pages, stop + +@tail: ; copy remaining non full page + ldy #0 + cpy #<__DATA_SIZE__ ; check if there is data to copy + beq @done ; if no data, done +@byte: + lda (src),y + sta (dst),y + iny + cpy #<__DATA_SIZE__ ; when y == __DATA_SIZE__, done + bne @byte ; else, continue +@done: + rts \ No newline at end of file