A second screen for the Commodore 64

ryan@hack.net / @hacknet - last updated 11-Jun-2021

IOBASE = token IOBASE+1 = lsb address IOBASE+2 = msb address IOBASE+3 = dataThe frame buffer is linear and not difficult to use like the native bitmapped C64 modes. It starts at $00000 in the static RAM.

- 4-layer PCB. Gerber files included. A bevel for the edge adds a lot to the cost, so just sand it by hand (don't skip this, you can destroy the female contacts).
- Cart case top and bottom. STL files included.
- 22uF 6.6mm Aluminum polarized capacitor
- Momentary switch, like pn 430156043726, if you'd like a reset button for your computer.
- .1" header strips
- 0603 resistors: 2 499R, 3 300R, 2 30R
- 0603 capacitors: 10 0.1uF, 7 0.01uF
- Generic 1117 3.3V Regulator
- 2 3.2x1.6 LEDs (helpful for debugging, but not required)
- XC95144XL-5TQ100C CPLD (speed not important)
- JEDEC 128kx8 SO Async SRAM a la AS6C1008-55PCN (don't go slower)
- Right angle DE15 high density VGA through-hole female connector

In multicolor mode, pixels are emitted at the same rate as monochrome mode, but each color channel has a different resolution. Green is 1/2x the pixel rate, while Red and Blue are 1/4x the pixel rate. The bit pattern to color channel mapping is per byte (chunky) and is:

G0 G1 G2 G3 R0 R1 B0 B1While the on-screen representation of each framebuffer byte is the following:

R0 R0 R0 R0 R1 R1 R1 R1 G0 G0 G1 G1 G2 G2 G3 G3 B0 B0 B0 B0 B1 B1 B1 B1

convert input.tiff -resize 640x480 -colors 2 -depth 1 output.monoMulticolor mode:

convert input.tiff +dither -posterize 2 -resize 640x480 output.tiff convert output.tiff -separate channel%d.pngThere's likely a way to use IM to pack the color channels, but I found it easier to just do it in python:

from PIL import Image from array import * import numpy as np ir = Image.open("channel0.png") ig = Image.open("channel1.png") ib = Image.open("channel2.png") ir = ir.resize((640,480)) ig = ig.resize((640,480)) ib = ib.resize((640,480)) r = ir.load() g = ig.load() b = ib.load() arr=np.zeros((480,80,8)) out=np.zeros((480,640)) for y in range(0,480): for x in range(0,80): # 0 1 2 3 is green level # 4 5 is red level # 6 7 is blue level # GREEN arr[y][x][0]=(g[x*8+0,y]+g[x*8+1,y])/2 arr[y][x][1]=(g[x*8+2,y]+g[x*8+3,y])/2 arr[y][x][2]=(g[x*8+4,y]+g[x*8+5,y])/2 arr[y][x][3]=(g[x*8+6,y]+g[x*8+7,y])/2 # RED arr[y][x][4]=(r[x*8+0,y]+r[x*8+1,y]+r[x*8+2,y]+r[x*8+3,y])/4 arr[y][x][5]=(r[x*8+4,y]+r[x*8+5,y]+r[x*8+6,y]+r[x*8+7,y])/4 #BLUE arr[y][x][6]=(b[x*8+0,y]+b[x*8+1,y]+b[x*8+2,y]+b[x*8+3,y])/4 arr[y][x][7]=(b[x*8+4,y]+b[x*8+5,y]+b[x*8+6,y]+b[x*8+7,y])/4 for y in range(0,480): for x in range(0,80): for bit in range(0,8): arr[y][x][bit] = int(round(round(arr[y][x][bit])/255)) newfile=open("output.bin","wb") for y in range(0,480): for x in range(0,80): out[y][x] = int(arr[y][x][0] + arr[y][x][1]*2 + arr[y][x][2]*4 + arr[y][x][3]*8 + arr[y][x][4]*16 + arr[y][x][5]*32 + arr[y][x][6]*64 + arr[y][x][7]*128) newfile.write(out[y][x].astype(np.ubyte)) newfile.close()You should be left with a 38,400 byte file for either mono or multicolor mode.

**Download the hardware design, bitstreams, sources, and demo here.**

- C64 Programmer's Reference Guide, "https://archive.org/details/c64-p rogrammer-ref/page/n259/mode/2u
- Tokenized BASIC stub to start an assembly binary, https://harald.is t.org/howto/c64/acme-asm-template.html
- VGA timings, http://tinyvga.com/vga-timing
- Verilog compiling / simulation on the Mac, https://medium.com/@e.birivoutin/hardware-development-on-a-mac-osx-6ca03d3c0bf0 (kinda nice to not have to hop over to Windows to run the craptastic 1st party tool for these simple simulations) - Medium link, warning.
- Mouser and Digikey hosted data sheets.

Use the ACME assembler

; Multicolor mode demo for VG64 ; r. brooks ; New Version 1/19/2021 - for reg scheme ; Assumes token register at $DE00, EXROM high, and NO banking at $8000 ; Multicolor Demo and test code !to "mc2test.o", cbm ; BASIC stub to get a tokenized SYS command in. Grabbed from ; https://harald.ist.org/howto/c64/acme-asm-template.html *= $0801 ; Load point $0801 (BASIC START) _FSTART ; This binary must begin with the bytes ; representing the BASIC program: 0 SYS2061 BASIC_program !byte $0b,$08 ; $0801 Pointer to next line !byte $00,$00 ; $0803 Line number (0) !byte $9e ; $0805 SYS !byte 48+(entry_point/1000)%10 ; Decimal address of program entry point !byte 48+(entry_point/100)%10 !byte 48+(entry_point/10)%10 !byte 48+(entry_point/1)%10 !byte $00 ; $080a End of BASIC line !byte $00,$00 ; $080b End of BASIC program entry_point ;JMP boot ; $080d First byte after the BASIC program ;; defines chrout = $ffd2 chrin = $ffcf autotoken = $C0 ;Multicolor mode, screen on token = $de00 ; vg64 registers lsb = $de01 msb = $de02 operand = $de03 zp1 = $fd ; available zero page addrs zp2 = $fe ; pointer to source zpA = $fb ; framebuffer pointed in zp zpB = $fc ;; program !zone main boot cld lda #23 sta $d018 ; Switch to lower case lda #