diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26ac00d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +yarn-error.log +tokagetone/ diff --git a/LICENSE b/asm/LICENSE similarity index 100% rename from LICENSE rename to asm/LICENSE diff --git a/README.md b/asm/README.md similarity index 100% rename from README.md rename to asm/README.md diff --git a/card.asm b/asm/card.asm similarity index 100% rename from card.asm rename to asm/card.asm diff --git a/card.prg b/asm/card.prg similarity index 100% rename from card.prg rename to asm/card.prg diff --git a/img/assembly.png b/asm/img/assembly.png similarity index 100% rename from img/assembly.png rename to asm/img/assembly.png diff --git a/img/debugger.png b/asm/img/debugger.png similarity index 100% rename from img/debugger.png rename to asm/img/debugger.png diff --git a/img/demo.gif b/asm/img/demo.gif similarity index 100% rename from img/demo.gif rename to asm/img/demo.gif diff --git a/img/emulator.png b/asm/img/emulator.png similarity index 100% rename from img/emulator.png rename to asm/img/emulator.png diff --git a/message.asm b/asm/message.asm similarity index 100% rename from message.asm rename to asm/message.asm diff --git a/music.sid b/asm/music.sid similarity index 100% rename from music.sid rename to asm/music.sid diff --git a/screen1.koa b/asm/screen1.koa similarity index 100% rename from screen1.koa rename to asm/screen1.koa diff --git a/screen2.koa b/asm/screen2.koa similarity index 100% rename from screen2.koa rename to asm/screen2.koa diff --git a/sidinfo.asm b/asm/sidinfo.asm similarity index 100% rename from sidinfo.asm rename to asm/sidinfo.asm diff --git a/sprites.asm b/asm/sprites.asm similarity index 100% rename from sprites.asm rename to asm/sprites.asm diff --git a/llvm-mos/.gitignore b/llvm-mos/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/llvm-mos/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/llvm-mos/Makefile b/llvm-mos/Makefile new file mode 100644 index 0000000..760d6a9 --- /dev/null +++ b/llvm-mos/Makefile @@ -0,0 +1,41 @@ +# +# GNU Makefile +# + +VICE=/Applications/vice-arm64-sdl2-3.7.1/bin/x64sc -autostartprgmode 1 -statusbar + +all: demo + +demo: + @echo "Compiling demo" + # -c Only run preprocess, compile, and assemble steps, do not link + mos-c64-clang -c -o build/music.o music.s + mos-c64-clang -c -o build/screens.o screens.s + mos-c64-clang -c -o build/demo.o demo.c + mos-c64-clang++ -std=c++20 -finput-charset=utf-8 -c -o build/strings.o strings.cpp + mos-c64-clang++ -std=c++20 -Os -flto \ + -Wl,--section-start=.sid=0x1000, \ + -Wl,--section-start=.bank0screen=0x0c00, \ + -Wl,--section-start=.bank0bitmap=0x2000 \ + -Wl,--section-start=.bank1screen=0x4c00, \ + -Wl,--section-start=.bank1bitmap=0x6000 \ + -o build/demo.prg build/demo.o build/music.o build/screens.o build/strings.o + mos-c64-clang++ -std=c++20 -Os -flto \ + -Wl,--section-start=.sid=0x1000 \ + -Wl,--section-start=.bank0screen=0x0c00 \ + -Wl,--section-start=.bank0bitmap=0x2000 \ + -Wl,--section-start=.bank1screen=0x4c00, \ + -Wl,--section-start=.bank1bitmap=0x6000 \ + -Wl,--lto-emit-asm -o build/demo.s build/demo.o build/music.o build/screens.o build/strings.o + +run: demo + @echo "Running demo" + $(VICE) build/demo.prg + +sid: + @echo "SID file info" + ./tools/sidinfo.py music.sid + +.PHONY: all demo run + +# EOF - Makefile diff --git a/llvm-mos/README.md b/llvm-mos/README.md new file mode 100644 index 0000000..a1edaba --- /dev/null +++ b/llvm-mos/README.md @@ -0,0 +1,21 @@ +# C64 Demo using llvm-mos + +This a follow up of my old [pure 6510 assembly demo](../asm), ported to llvm-mos. + +## Requirements + +* https://github.com/llvm-mos/llvm-mos - Port of LLVM to the MOS 6502 and related processors +* https://github.com/llvm-mos/llvm-mos-sdk - SDK for developing with the llvm-mos compiler + + +## Useful links + + * 6502 opcodes - http://6502.org/tutorials/6502opcodes.html + * 6502 / 6510 Instruction Set - https://c64os.com/post/6502instructions + * llm-mos Wiki - https://llvm-mos.org/wiki/Welcome + * Gnu Assembler - https://sourceware.org/binutils/docs/as + +## Other llvm-mos demos: + + * C64 sprite example: https://gist.github.com/juj/84fc977c7f928e9343bb6f5f74c45a57 + * sidplay: https://github.com/kklis/sidplay diff --git a/llvm-mos/demo.c b/llvm-mos/demo.c new file mode 100644 index 0000000..79d0799 --- /dev/null +++ b/llvm-mos/demo.c @@ -0,0 +1,137 @@ +#include "macros.h" +#include <6502.h> +#include +#include +#include +#include + +extern unsigned bank0bgcolor; +extern unsigned bank1bgcolor; +extern char bank0bitmap; +extern char bank1bitmap; + +extern char *message; +extern unsigned char messageLength; + +unsigned char screen_state = 0; +unsigned char counter = 254; + +unsigned char raster_h_offset = 7; +unsigned char msg_offset = 0; + +void background(unsigned char color) { + poke(&VIC.bordercolor, color); + poke(&VIC.bgcolor0, color); +} + +void recolor() { + // switch to Standard Bitmap Mode now (bit 5 set to 1) + // Bit #5: 0 = Text mode; 1 = Bitmap mode. + // see https://www.c64-wiki.com/wiki/Standard_Bitmap_Mode + poke(&VIC.ctrl1, 0b00111011); + // tells the VIC-II where to "look for graphics" + // in this case: + // 3 (%0011) * 1024 = $0c00 = address to start of color information + // bit 3 is set, then the bitmap starts at vic bank address + $2000 = $2000 or + // $6000 see https://www.c64-wiki.com/wiki/53272 + poke(&VIC.addr, 0b00111000); + // bit 5 must be cleared to enter Standard Bitmap Mode + // see https://www.c64-wiki.com/wiki/Standard_Bitmap_Mode + poke(&VIC.ctrl2, 0b11011000); + if (screen_state == 0) { + background(bank1bgcolor); + vic_bank(0b0000010); + } else { + background(bank0bgcolor); + vic_bank(0b0000011); + } + counter++; + if (counter == 255) { + counter = 0; + screen_state = screen_state == 1 ? 0 : 1; + } +} + +void scrollMessage() { + // use bank 0 $0000-$3FFF, ROM chars available at $1000-$1FFF + // see https://www.c64-wiki.com/wiki/VIC_bank + vic_bank(0b00000011); + // switch to Standard Character Mode now (bit 5 set to 0) + // Bit #5: 0 = Text mode; 1 = Bitmap mode. + // see https://www.c64-wiki.com/wiki/Standard_Character_Mode and + // http://sta.c64.org/cbm64mem.html + poke(&VIC.ctrl1, 0b00011011); + // tells the VIC-II where to "look for graphics" + // in this case: + // 15 (%1111) * 1024 = $3c00 = address to screen character RAM + // 2 (%010) * 2048 = $1000 = where to access the character set ROM address + // see https://www.c64-wiki.com/wiki/53272 + poke(&VIC.addr, 0b11110100); + + raster_h_offset--; + poke(&VIC.ctrl2, raster_h_offset); + if (raster_h_offset == 0) { + raster_h_offset = 7; + + if (msg_offset == messageLength) { + msg_offset = 0; + } + + for (uint8_t i = 0; i < 40; i++) { + poke(0x3fc0 + i, message[msg_offset + i]); + } + + msg_offset++; + } +} + +void clearBitmapLines(char *bitmap, uint16_t start, uint16_t end) { + for (uint16_t i = start; i < end; i = i + 1) { + bitmap[i] = 0; + } +} + +// see https://llvm-mos.org/wiki/C_interrupts +__attribute__((interrupt, no_isr)) void play() { + interrupt_ack(); + scrollMessage(); + sid_play($1021); + recolor(); + interrupt_exit(); +} + +int main(void) { + // we don't need the BASIC ROM after we start + // see https://www.c64-wiki.com/wiki/Zeropage + poke(0x0001, 0b00110110); + + // initialize the SID song + sid_init($1048); + + cls(); + + // C64 bitmaps are 320x200 pixels, or 8000 bytes. Each line is 40 bytes. + clearBitmapLines(&bank0bitmap, 8000 - 40 * 16, 8000); + clearBitmapLines(&bank1bitmap, 8000 - 40 * 16, 8000); + + SEI(); + poke(&VIC.rasterline, 240); + pokew(IRQVec, (unsigned int)&play); + // interrupt control - enable all interrupts with $7b (icr=$DC0D) + // see http://sta.c64.org/cbm64mem.html + poke(&CIA1.icr, 0b01111011); + // interrupt control register - enable raster interrupt with $81 ($d01a) + // see http://sta.c64.org/cbm64mem.html + poke(&VIC.imr, 0b10000001); + CLI(); + + while (1) { + /* + if (raster_line == 255) { + recolor(); + // call sid play routing + sid_play($1021); + } + */ + } +} diff --git a/llvm-mos/macros.h b/llvm-mos/macros.h new file mode 100644 index 0000000..8e7494e --- /dev/null +++ b/llvm-mos/macros.h @@ -0,0 +1,42 @@ +#include +#include <6502.h> +#include <_vic2.h> + +#define IRQVec 0x0314 +#define BRKVec 0x0316 +#define NMIVec 0x0318 + +// https://github.com/cc65/cc65/blob/master/include/peekpoke.h + +#define poke(addr,val) (*(unsigned char*) (addr) = (val)) +#define pokew(addr,val) (*(unsigned*) (addr) = (val)) +#define peek(addr) (*(unsigned char*) (addr)) +#define peekw(addr) (*(unsigned*) (addr)) + +// various +#define sid_init(addr) __attribute__((leaf)) asm("LDA #$00\nTAX\nTAY\nJSR "#addr) +#define sid_play(addr) __attribute__((leaf)) asm("JSR "#addr) +#define raster_line (*((volatile unsigned char *)(struct __vic2 *)&VIC.rasterline)) + +// clear screen. See http://sta.c64.org/cbm64scrfunc.html +#define cls() __attribute__((leaf)) asm("JSR $e544") + +// https://www.c64-wiki.com/wiki/VIC_bank +#define vic_bank(pattern) (*((volatile unsigned char *)(struct __6526 *)&CIA2.pra))=0b11111100|pattern + +#define raster_interrupt(line, fn) \ + (*((volatile unsigned char *)(struct __vic2 *)&VIC.rasterline))=line; \ + (*(volatile unsigned char *)0x0314)=(unsigned int)fn & 0xffff; \ + (*(volatile unsigned char *)0x0315)=(unsigned int)fn >> 8; + +#define interrupts_disable() __attribute__((leaf)) asm("SEI") +#define interrupts_enable() __attribute__((leaf)) asm("CLI") + +// everytime an interrupt triggers, we need to acknowledge it by setting the +// corresponding VIC interrupt flag +// if we don't ack the irq, then it will be called again after we exit it +// see http://sta.c64.org/cbm64mem.html +#define interrupt_ack() (*((volatile unsigned char *)(struct __vic2 *)&VIC.irr)) = 0b11111111 + +// when exiting an irq, we need to restore the accumulator, and indexes y and x from the stack first +#define interrupt_exit() __attribute__((leaf)) asm("PLA\nTAY\nPLA\nTAX\nPLA\nRTI\n") diff --git a/llvm-mos/music.s b/llvm-mos/music.s new file mode 100644 index 0000000..b65f6c3 --- /dev/null +++ b/llvm-mos/music.s @@ -0,0 +1,14 @@ +# SID music section +# see --section-start=.sid=0x1000 in Makefile for sid address +# R flag is needed to avoid linker garbage collection +# see https://sourceware.org/binutils/docs/as/Section.html +# +# First 126 bytes of music.sid are header, so skip them +# .incbin "file"[,skip[,count]] +# https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt + +.global sid +.type sid,@object +.section .sid,"awR",@progbits +sid: +.incbin "music.sid", 126 diff --git a/llvm-mos/music.sid b/llvm-mos/music.sid new file mode 100644 index 0000000..b5f385c Binary files /dev/null and b/llvm-mos/music.sid differ diff --git a/llvm-mos/screen1.koa b/llvm-mos/screen1.koa new file mode 100644 index 0000000..f1371aa Binary files /dev/null and b/llvm-mos/screen1.koa differ diff --git a/llvm-mos/screen2.koa b/llvm-mos/screen2.koa new file mode 100644 index 0000000..764f9c7 Binary files /dev/null and b/llvm-mos/screen2.koa differ diff --git a/llvm-mos/screens.s b/llvm-mos/screens.s new file mode 100644 index 0000000..05b345d --- /dev/null +++ b/llvm-mos/screens.s @@ -0,0 +1,48 @@ +# https://www.c64-wiki.de/wiki/Koala_Painter + +.global bank0bitmap +.type bank0bitmap,@object +.section .bank0bitmap,"awR",@progbits +bank0bitmap: +.incbin "screen1.koa", 2, 8000 + +.global bank0screen +.type bank0screen,@object +.section .bank0screen,"awR",@progbits +bank0screen: +.incbin "screen1.koa", 8002, 1000 + +# .global bank0color +# .type bank0color,@object +# .section .bank0color,"awR",@progbits +# bank0color: +# .incbin "screen1.koa", 9002, 1000 + +.global bank0bgcolor +.type bank0bgcolor,@object +bank0bgcolor: +.incbin "screen1.koa", 10002, 1 + +.global bank1bitmap +.type bank1bitmap,@object +.section .bank1bitmap,"awR",@progbits +bank1bitmap: +.incbin "screen2.koa", 2, 8000 + +.global bank1screen +.type bank1screen,@object +.section .bank1screen,"awR",@progbits +bank1screen: +.incbin "screen2.koa", 8002, 1000 + +# .global bank1color +# .type bank1color,@object +# .section .bank1color,"awR",@progbits +# bank1color: +# .incbin "screen2.koa", 9002, 1000 + +.global bank1bgcolor +.type bank1bgcolor,@object +bank1bgcolor: +.incbin "screen2.koa", 10002, 1 + diff --git a/llvm-mos/strings.cpp b/llvm-mos/strings.cpp new file mode 100644 index 0000000..4386184 --- /dev/null +++ b/llvm-mos/strings.cpp @@ -0,0 +1,5 @@ +// https://llvm-mos.org/wiki/Character_set +#include + +const char *message = U"THIS C64 DEMO USES VIC-II GRAPHICS, SPRITES, RASTER INTERRUPTS, A RANDOM GENERATOR AND SID MUSIC - MORE AT HTTPS://HITHUB.COM/CELSO/C64 "_uv; +unsigned char messageLength = 136; diff --git a/llvm-mos/tools.s b/llvm-mos/tools.s new file mode 100644 index 0000000..a32edbf --- /dev/null +++ b/llvm-mos/tools.s @@ -0,0 +1,7 @@ +.include "c64.inc" + +.global ack +.type ack,@function +ack: +STA VIC_IRR +RTS diff --git a/llvm-mos/tools/sidinfo.py b/llvm-mos/tools/sidinfo.py new file mode 100755 index 0000000..d6544cd --- /dev/null +++ b/llvm-mos/tools/sidinfo.py @@ -0,0 +1,96 @@ +#!/opt/homebrew/bin/python3.12 + +import sys +import os + +if len(sys.argv) <= 1: + print(f"{sys.argv[0]} sid_file.sid") + sys.exit() + +try: + f = open(sys.argv[1], 'rb') +except FileNotFoundError: + print(f"file {sys.argv[1]} does not exist") +else: + with f: + data = f.read(126) + fs = os.stat(sys.argv[1]) + print(f"00-03, magicID: ", end="") + print(f"{data[0:4].hex()} ({data[0:4]})") + version = int.from_bytes(data[4:6], byteorder='big') + print(f"04-05, version: {data[4:6].hex()}") + dataOffset = int.from_bytes(data[6:8], byteorder='big') + print(f"06-07, dataOffset: {dataOffset} (0x{data[6:8].hex()})") + loadAddress = int.from_bytes(data[8:10], byteorder='big') + print(f"08-09, loadAddress: ", end="") + if(loadAddress==0): + print(f"0 - specified in the two first two bytes of the data: (0x{data[dataOffset:dataOffset+2][::-1].hex()})") + else: + print(f"{loadAddress} (0x{data[8:10].hex()})") + initAddress = int.from_bytes(data[0xa:0xc], byteorder='big') + print(f"0A-0B, initAddress: ", end="") + if(initAddress==0): + print(f"0 - same as loadAddress") + else: + print(f"0x{data[0xa:0xc].hex()}") + playAddress = int.from_bytes(data[0xc:0xd], byteorder='big') + print(f"0C-0D, playAddress: ", end="") + if(initAddress==0): + print(f"0 - init sub installs interrupt handler") + else: + print(f"0x{data[0xc:0xe].hex()}") + print(f"0E-0F, # of songs: {int.from_bytes(data[0xe:0x10], byteorder='big')} 0x{data[0xe:0x10].hex()}") + print(f"10-11, startSong: {int.from_bytes(data[0x10:0x12], byteorder='big')} 0x{data[0x10:0x12].hex()}") + speed = int.from_bytes(data[0x12:0x16], byteorder='big') + print(f"12-15, speed: { speed }") + print(f"16-35, name: {data[0x16:0x36]}") + print(f"36-55, author: {data[0x36:0x56]}") + print(f"56-75, released: {data[0x56:0x76]}") + if(version==1): + print(f"76-{ hex(fs.st_size-1)[2:] }, data from 0x76 (118) to { hex(fs.st_size-1) } ({ fs.st_size })") + else: + flags = int.from_bytes(data[0x76:0x78],'big') + print(f"76-77, flags: { bin(flags) }") + print(f" bit 0 (musPlayer): { 'Compute Sidplayer MUS data' if (flags & 0b1) else 'built-in player' }") + print(f" bit 1 (compatiblity): { 'PlaySID specific or C64 Basic' if (flags & 0b10) else 'C64 compatible' }") + print(f" bit 2-3 (clock): ", end="") + v = (flags & 0b1100)>>2 + match v: + case 0b00: print("Unknown") + case 0b01: print("PAL") + case 0b10: print("NTSC") + case 0b11: print("PAL & NTSC") + print(f" bit 4-5 (sidModel): ", end="") + v = (flags & 0b110000)>>4 + match v: + case 0b00: print("Unknown") + case 0b01: print("MOS6581") + case 0b10: print("MOS8580") + case 0b11: print("MOS6581 and MOS8580") + print(f" bit 6-7 (sidModel 2): ", end="") + v = (flags & 0b11000000)>>6 + match v: + case 0b00: print("Unknown") + case 0b01: print("MOS6581") + case 0b10: print("MOS8580") + case 0b11: print("MOS6581 and MOS8580") + print(f" bit 8-9 (sidModel 3): ", end="") + v = (flags & 0b1100000000)>>8 + match v: + case 0b00: print("Unknown") + case 0b01: print("MOS6581") + case 0b10: print("MOS8580") + case 0b11: print("MOS6581 and MOS8580") + startPage = int.from_bytes(data[0x78:0x79]) + print(f"78, startPage: ", end="") + if(startPage==0): + print("0x00 - clean (does not write outside its data range)") + else: + print(f"0x{ data[0x78:0x79].hex() }") + print(f"79, pageLength: 0x{ data[0x79:0x7a].hex() }") + print(f"7A, secondSIDAddress: 0x{ data[0x7a:0x7b].hex() }") + print(f"7B, thirdSIDAddress: 0x{ data[0x7b:0x7c].hex() }") + if(loadAddress==0): + print(f"7E-{ hex(fs.st_size-1)[2:] }, data from 0x7E (126) to { hex(fs.st_size-1) } ({ fs.st_size })") + else: + print(f"7C-{ hex(fs.st_size-1)[2:] }, data from 0x7C (124) to { hex(fs.st_size-1) } ({ fs.st_size })")