First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
1
lenser/vic20/viewer/__init__.py
Normal file
1
lenser/vic20/viewer/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""VIC-20 6502 viewer (assembled by xa)."""
|
||||
82
lenser/vic20/viewer/assemble.py
Normal file
82
lenser/vic20/viewer/assemble.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""Assemble the VIC-20 viewer with `xa` and lay out the 8K autostart cartridge.
|
||||
|
||||
The cartridge occupies $A000-$BFFF (8192 bytes). The KERNAL recognises the
|
||||
"A0CBM" signature at $A004 and jumps through the cold vector at $A000, so no 6502
|
||||
reset vector is needed. MAME's `vic20 -cart` wants a full 8K image; smaller .a0
|
||||
files fail with an I/O error.
|
||||
|
||||
ROM layout (fixed so the viewer can copy from constant addresses):
|
||||
$A000 header + viewer code
|
||||
$A800 CHARSRC character set (2048 bytes -> RAM $1400)
|
||||
$B000 SCRSRC screen ( 506 bytes -> RAM $1E00)
|
||||
$B200 COLSRC colour RAM ( 506 bytes -> RAM $9600)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
CART_BASE = 0xA000
|
||||
CART_SIZE = 0x2000
|
||||
CHARSRC = 0xA800
|
||||
SCRSRC = 0xB000
|
||||
COLSRC = 0xB200
|
||||
|
||||
|
||||
class AssemblerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def have_xa() -> bool:
|
||||
return shutil.which("xa") is not None
|
||||
|
||||
|
||||
def _assemble(bg: int, border: int, aux: int) -> bytes:
|
||||
if not have_xa():
|
||||
raise AssemblerError("The 'xa' (xa65) assembler was not found on PATH.\n"
|
||||
"Install it with: sudo apt install xa65")
|
||||
wrapper = (f"* = ${CART_BASE:04X}\n"
|
||||
f"#define CHARSRC ${CHARSRC:04X}\n"
|
||||
f"#define SCRSRC ${SCRSRC:04X}\n"
|
||||
f"#define COLSRC ${COLSRC:04X}\n"
|
||||
f"#define BG {bg & 15}\n"
|
||||
f"#define BORDER {border & 7}\n"
|
||||
f"#define AUX {aux & 15}\n"
|
||||
'#include "viewer.s"\n')
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
out = os.path.join(td, "v.bin")
|
||||
fd, wrap = tempfile.mkstemp(suffix=".s", prefix="_wrap_", dir=VIEWER_DIR)
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(wrapper)
|
||||
proc = subprocess.run(["xa", "-o", out, os.path.basename(wrap)],
|
||||
capture_output=True, text=True, cwd=VIEWER_DIR)
|
||||
if proc.returncode != 0:
|
||||
raise AssemblerError(f"xa failed:\n{proc.stdout}{proc.stderr}")
|
||||
with open(out, "rb") as f:
|
||||
code = f.read()
|
||||
finally:
|
||||
os.unlink(wrap)
|
||||
return code
|
||||
|
||||
|
||||
def build_cart(data: dict) -> bytes:
|
||||
"""data carries chardata(2048), screen(506), color(506) and bg/border/aux."""
|
||||
code = _assemble(data["bg"], data["border"], data["aux"])
|
||||
if len(code) > (CHARSRC - CART_BASE):
|
||||
raise AssemblerError(f"viewer code is {len(code)} bytes, overruns CHARSRC")
|
||||
chardata = bytes(bytearray(data["chardata"]))
|
||||
screen = bytes(bytearray(data["screen"]))
|
||||
color = bytes(bytearray(data["color"]))
|
||||
|
||||
rom = bytearray(b"\x00" * CART_SIZE)
|
||||
rom[0:len(code)] = code
|
||||
rom[CHARSRC - CART_BASE:CHARSRC - CART_BASE + len(chardata)] = chardata
|
||||
rom[SCRSRC - CART_BASE:SCRSRC - CART_BASE + len(screen)] = screen
|
||||
rom[COLSRC - CART_BASE:COLSRC - CART_BASE + len(color)] = color
|
||||
return bytes(rom)
|
||||
95
lenser/vic20/viewer/viewer.s
Normal file
95
lenser/vic20/viewer/viewer.s
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
; VIC-20 image viewer (autostart 8K cartridge at $A000).
|
||||
;
|
||||
; The KERNAL does NOT initialise the VIC before launching an autostart cart, so
|
||||
; this code programs every VIC register itself, copies the character set, screen
|
||||
; and colour data from the cartridge ROM into RAM, then loops forever showing the
|
||||
; picture.
|
||||
;
|
||||
; Layout in unexpanded RAM -- char set $1400-$1BFF (256 chars), screen $1E00,
|
||||
; colour RAM $9600 (fixed). $9005 = $FD selects screen $1E00 + char base $1400.
|
||||
;
|
||||
; The build (assemble.py) appends the data blocks at the fixed ROM addresses
|
||||
; CHARSRC / SCRSRC / COLSRC and fills in the BG / BORDER / AUX colour defines.
|
||||
|
||||
.word cold ; $A000 cold-start vector
|
||||
.word cold ; $A002 warm-start vector
|
||||
.byte $41,$30,$C3,$C2,$CD ; "A0CBM" autostart signature
|
||||
|
||||
; zero-page scratch (KERNAL-safe temporaries)
|
||||
src = $fb ; $fb/$fc copy source pointer
|
||||
dst = $fd ; $fd/$fe copy destination pointer
|
||||
|
||||
cold:
|
||||
sei
|
||||
cld
|
||||
ldx #$ff
|
||||
txs
|
||||
|
||||
; --- copy the 256-char set (2048 bytes = 8 pages) ROM -> $1400 ---
|
||||
lda #<CHARSRC
|
||||
sta src
|
||||
lda #>CHARSRC
|
||||
sta src+1
|
||||
lda #$00
|
||||
sta dst
|
||||
lda #$14
|
||||
sta dst+1
|
||||
ldx #8 ; pages to copy
|
||||
jsr copypages
|
||||
|
||||
; --- copy the screen (506 bytes -> 2 pages) ROM -> $1E00 ---
|
||||
lda #<SCRSRC
|
||||
sta src
|
||||
lda #>SCRSRC
|
||||
sta src+1
|
||||
lda #$00
|
||||
sta dst
|
||||
lda #$1e
|
||||
sta dst+1
|
||||
ldx #2
|
||||
jsr copypages
|
||||
|
||||
; --- copy colour RAM (506 bytes -> 2 pages) ROM -> $9600 ---
|
||||
lda #<COLSRC
|
||||
sta src
|
||||
lda #>COLSRC
|
||||
sta src+1
|
||||
lda #$00
|
||||
sta dst
|
||||
lda #$96
|
||||
sta dst+1
|
||||
ldx #2
|
||||
jsr copypages
|
||||
|
||||
; --- program the VIC ---
|
||||
lda #$05
|
||||
sta $9000 ; horizontal origin
|
||||
lda #$19
|
||||
sta $9001 ; vertical origin
|
||||
lda #$96
|
||||
sta $9002 ; 22 columns + screen address bit 9
|
||||
lda #$2e
|
||||
sta $9003 ; 23 rows, 8x8 chars
|
||||
lda #$fd
|
||||
sta $9005 ; screen $1E00 + char base $1400
|
||||
lda #(AUX<<4)
|
||||
sta $900e ; auxiliary colour (bits 4-7), volume 0
|
||||
lda #((BG<<4)|$08|BORDER)
|
||||
sta $900f ; bg (bits 4-7) | normal mode (bit3) | border (0-2)
|
||||
|
||||
loop:
|
||||
jmp loop ; hold the picture forever
|
||||
|
||||
; copy X pages of 256 bytes from (src) to (dst)
|
||||
copypages:
|
||||
ldy #$00
|
||||
cp1:
|
||||
lda (src),y
|
||||
sta (dst),y
|
||||
iny
|
||||
bne cp1
|
||||
inc src+1
|
||||
inc dst+1
|
||||
dex
|
||||
bne cp1
|
||||
rts
|
||||
Loading…
Add table
Add a link
Reference in a new issue