@8bitscript/c64 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/native/6502/raster.s +121 -0
- package/package.json +396 -0
- package/src/bitmap.8bs +175 -0
- package/src/charset.8bs +142 -0
- package/src/geometry.8bs +116 -0
- package/src/index.8bs +427 -0
- package/src/input.8bs +269 -0
- package/src/joystick.8bs +78 -0
- package/src/keyboard.8bs +84 -0
- package/src/keys.8bs +103 -0
- package/src/mouse.8bs +265 -0
- package/src/pointer.8bs +183 -0
- package/src/random.8bs +81 -0
- package/src/raster.8bs +190 -0
- package/src/reu.8bs +210 -0
- package/src/screen.8bs +120 -0
- package/src/scroll.8bs +130 -0
- package/src/sid.8bs +211 -0
- package/src/sprites.8bs +197 -0
- package/src/text.8bs +174 -0
package/src/index.8bs
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// @8bitscript/c64 — C64 target support: the hardware underneath.
|
|
2
|
+
//
|
|
3
|
+
// The module entry point named by the "8bitscript".entry field in this
|
|
4
|
+
// package's package.json, resolved and linked for:
|
|
5
|
+
//
|
|
6
|
+
// import { borderColor, backgroundColor, memoryPointer } from "@8bitscript/c64";
|
|
7
|
+
// import { spriteEnable, spritePositions, cia1PortA } from "@8bitscript/c64";
|
|
8
|
+
// import { reuStatus, reuCommand } from "@8bitscript/c64"; // --profile reu*
|
|
9
|
+
//
|
|
10
|
+
// The surface is the hardware, one register at a time — the VIC-II, the
|
|
11
|
+
// SID, the two CIAs, the 6510's own port — under the names llvm-mos-sdk's
|
|
12
|
+
// c64.h gives the chips (`VIC`, `SID`, `CIA1`, `CIA2`) and the fields its
|
|
13
|
+
// _vic2.h/_sid.h/_6526.h give the registers. What a program usually wants
|
|
14
|
+
// sits one layer up, in this package's own implementations of the portable
|
|
15
|
+
// capability packages — `./src/screen.8bs` behind @8bitscript/screen and
|
|
16
|
+
// `./src/text.8bs` behind @8bitscript/text, named by the "8bitscript".exports
|
|
17
|
+
// map in package.json — and in the C64-only subpaths beside them:
|
|
18
|
+
// `@8bitscript/c64/sprites`, `/raster`, `/bitmap`, `/charset`, `/scroll`,
|
|
19
|
+
// `/keyboard` + `/keys`, `/joystick`, `/sid`, `/reu`, `/mouse`. All of
|
|
20
|
+
// them import these registers and are built on nothing else. A
|
|
21
|
+
// project that imports THIS package has declared itself C64 specific.
|
|
22
|
+
//
|
|
23
|
+
// Every fact stated here was read in a primary source or seen on screen
|
|
24
|
+
// under x64sc; packages/c64/AGENTS.md says which, and holds what was not
|
|
25
|
+
// checked. Where the VIC-20's VIC packs border, background, and video mode
|
|
26
|
+
// into one register, the C64's VIC-II gives each its own; both take the low
|
|
27
|
+
// four bits, and the VIC-II has sixteen colours everywhere.
|
|
28
|
+
import { Video } from "./geometry.8bs";
|
|
29
|
+
|
|
30
|
+
// ---- the 6510's port: what the CPU sees at $A000, $D000, $E000 -------------
|
|
31
|
+
//
|
|
32
|
+
// $0001, the processor port. Bits 0-2 are LORAM, HIRAM and CHAREN: which of
|
|
33
|
+
// BASIC ROM, KERNAL ROM and the I/O area / character ROM the CPU sees over
|
|
34
|
+
// the RAM that is always underneath. The SDK's start-up (unmap-basic.o's
|
|
35
|
+
// .init section: `ldx #$2f / stx $00 / ldx #$3e / stx $01`) leaves it at
|
|
36
|
+
// $3E — BASIC out, KERNAL in, I/O in — so $A000-$BFFF is program RAM. Bits
|
|
37
|
+
// 3-5 are the cassette lines. Change it by reading, masking and writing
|
|
38
|
+
// back, never with a literal: the three functions at the end of this file
|
|
39
|
+
// are the only places this package does.
|
|
40
|
+
//
|
|
41
|
+
// The low three bits, and what the CPU sees (c64-wiki, "Bank Switching"):
|
|
42
|
+
// %111 $37 BASIC, KERNAL, I/O the machine's boot state
|
|
43
|
+
// %110 BASIC, KERNAL, character ROM
|
|
44
|
+
// %011 $3B RAM at $A000, KERNAL, I/O the SDK's state (as $3E)
|
|
45
|
+
// %010 RAM at $A000, KERNAL, character ROM the copy below
|
|
46
|
+
// %101 $35 RAM at $A000 and $E000, I/O this package's state (as $3D)
|
|
47
|
+
// %100, %00x RAM everywhere, no I/O writes under I/O (as $3C)
|
|
48
|
+
// HIRAM (bit 1) low hides the character ROM as well as the KERNAL: from
|
|
49
|
+
// %101, clearing CHAREN gives RAM everywhere, not the ROM, so a copy of
|
|
50
|
+
// the ROM sets %010 first and %101 after.
|
|
51
|
+
@address(0x0001)
|
|
52
|
+
export let processorPort: volatile<u8>;
|
|
53
|
+
|
|
54
|
+
// ---- VIC-II ($D000-$D02E) ------------------------------------------------
|
|
55
|
+
|
|
56
|
+
// $D000-$D00F: sprite 0's X, sprite 0's Y, sprite 1's X, ... — `2 * n` is
|
|
57
|
+
// sprite n's X and `2 * n + 1` its Y (`spr_pos[n].x/.y` in _vic2.h). X is
|
|
58
|
+
// nine bits wide: the ninth of each sprite is a bit of $D010.
|
|
59
|
+
@address(0xD000)
|
|
60
|
+
export let spritePositions: array<u8, 16>;
|
|
61
|
+
|
|
62
|
+
// $D010, bit n = bit 8 of sprite n's X coordinate (`spr_hi_x`).
|
|
63
|
+
@address(0xD010)
|
|
64
|
+
export let spriteXHigh: volatile<u8>;
|
|
65
|
+
|
|
66
|
+
// $D011, control register 1 (`ctrl1`): bit 7 RST8 (bit 8 of the raster
|
|
67
|
+
// line, read; of the raster compare, write), 6 ECM, 5 BMM, 4 DEN (display
|
|
68
|
+
// enable), 3 RSEL (25 rows / 24), 0-2 YSCROLL. The frame runtime reads
|
|
69
|
+
// bit 7 (FRAME_SYNC.c64 in packages/backend-6502).
|
|
70
|
+
@address(0xD011)
|
|
71
|
+
export let control1: volatile<u8>;
|
|
72
|
+
|
|
73
|
+
// $D012, the raster line's low eight bits (read) / raster compare (write).
|
|
74
|
+
@address(0xD012)
|
|
75
|
+
export let raster: volatile<u8>;
|
|
76
|
+
|
|
77
|
+
// $D015, one enable bit per sprite (`spr_ena`).
|
|
78
|
+
@address(0xD015)
|
|
79
|
+
export let spriteEnable: volatile<u8>;
|
|
80
|
+
|
|
81
|
+
// $D016, control register 2 (`ctrl2`): bit 4 MCM (multicolour text/bitmap),
|
|
82
|
+
// 3 CSEL (40 columns / 38), 0-2 XSCROLL.
|
|
83
|
+
@address(0xD016)
|
|
84
|
+
export let control2: volatile<u8>;
|
|
85
|
+
|
|
86
|
+
// $D017 / $D01D, one bit per sprite: drawn twice as tall / twice as wide.
|
|
87
|
+
@address(0xD017)
|
|
88
|
+
export let spriteExpandY: volatile<u8>;
|
|
89
|
+
@address(0xD01D)
|
|
90
|
+
export let spriteExpandX: volatile<u8>;
|
|
91
|
+
|
|
92
|
+
// $D018, the VIC-II's memory pointer (`addr` in _vic2.h): bits 4-7 the
|
|
93
|
+
// screen matrix's position in the bank in 1K steps, bits 1-3 the character
|
|
94
|
+
// set's in 2K steps (bit 3 alone for a bitmap). $15 is the KERNAL's boot
|
|
95
|
+
// value (screen $0400, ROM upper-case set at $1000, POKE 53272,21 in the
|
|
96
|
+
// Programmer's Reference Guide); this package's screen is at $E000 with
|
|
97
|
+
// its own copy of the ROM at $D000, which in bank 3 is $84 — see
|
|
98
|
+
// Video.MEMORY_POINTER_UPPERCASE in ./geometry.8bs.
|
|
99
|
+
@address(0xD018)
|
|
100
|
+
export let memoryPointer: volatile<u8>;
|
|
101
|
+
|
|
102
|
+
// $D019 / $D01A, interrupt status and mask (`irr`/`imr`): bit 0 raster,
|
|
103
|
+
// 1 sprite-background collision, 2 sprite-sprite collision, 3 light pen;
|
|
104
|
+
// status bit 7 = any. Writing a 1 to a status bit acknowledges it. The
|
|
105
|
+
// program runs with interrupts off (see FRAME_SYNC.c64) and polls; the
|
|
106
|
+
// one interrupt this package ever enables is the raster compare, for
|
|
107
|
+
// @8bitscript/c64/raster's write list, whose handler acknowledges it.
|
|
108
|
+
@address(0xD019)
|
|
109
|
+
export let interruptStatus: volatile<u8>;
|
|
110
|
+
@address(0xD01A)
|
|
111
|
+
export let interruptMask: volatile<u8>;
|
|
112
|
+
|
|
113
|
+
// $D01B, one bit per sprite: 1 = the sprite is drawn BEHIND the
|
|
114
|
+
// background's foreground pixels (`spr_bg_prio`). Sprite-to-sprite order
|
|
115
|
+
// is fixed: a lower-numbered sprite is always in front.
|
|
116
|
+
@address(0xD01B)
|
|
117
|
+
export let spritePriority: volatile<u8>;
|
|
118
|
+
|
|
119
|
+
// $D01C, one bit per sprite: multicolour (12x21 double-width pixels, three
|
|
120
|
+
// colours plus transparent: the sprite's own and the two shared ones).
|
|
121
|
+
@address(0xD01C)
|
|
122
|
+
export let spriteMulticolor: volatile<u8>;
|
|
123
|
+
|
|
124
|
+
// $D01E / $D01F: which sprites touched another sprite / the background's
|
|
125
|
+
// foreground since last read. Both CLEAR WHEN READ (and cannot be
|
|
126
|
+
// written), so read each once a frame and keep the value.
|
|
127
|
+
@address(0xD01E)
|
|
128
|
+
export let spriteCollision: volatile<u8>;
|
|
129
|
+
@address(0xD01F)
|
|
130
|
+
export let spriteBackgroundCollision: volatile<u8>;
|
|
131
|
+
|
|
132
|
+
// $D020, the border colour; $D021-$D024, background colours 0-3 (only 0
|
|
133
|
+
// shows in the standard text mode; 1-3 are for extended-colour and
|
|
134
|
+
// multicolour modes); $D025 / $D026, the two colours every multicolour
|
|
135
|
+
// sprite shares; $D027-$D02E, each sprite's own colour. Low four bits.
|
|
136
|
+
@address(0xD020)
|
|
137
|
+
export let borderColor: volatile<u8>;
|
|
138
|
+
@address(0xD021)
|
|
139
|
+
export let backgroundColor: volatile<u8>;
|
|
140
|
+
@address(0xD022)
|
|
141
|
+
export let backgroundColor1: volatile<u8>;
|
|
142
|
+
@address(0xD023)
|
|
143
|
+
export let backgroundColor2: volatile<u8>;
|
|
144
|
+
@address(0xD024)
|
|
145
|
+
export let backgroundColor3: volatile<u8>;
|
|
146
|
+
@address(0xD025)
|
|
147
|
+
export let spriteSharedColor0: volatile<u8>;
|
|
148
|
+
@address(0xD026)
|
|
149
|
+
export let spriteSharedColor1: volatile<u8>;
|
|
150
|
+
@address(0xD027)
|
|
151
|
+
export let spriteColors: array<u8, 8>;
|
|
152
|
+
|
|
153
|
+
// ---- SID ($D400-$D41C) ------------------------------------------------------
|
|
154
|
+
//
|
|
155
|
+
// Twenty-five registers, seven per voice then the filter and volume. The
|
|
156
|
+
// write-only ones read back as nothing useful — the SID has no readable
|
|
157
|
+
// state below $D419 — so @8bitscript/c64/sid keeps its own copy of what it
|
|
158
|
+
// wrote. Voice n's registers start at 7 * n: 0-1 frequency (low, high),
|
|
159
|
+
// 2-3 pulse width (low, high nybble), 4 control (bit 0 gate, 1 sync, 2
|
|
160
|
+
// ring, 3 test, 4 triangle, 5 sawtooth, 6 pulse, 7 noise), 5 attack/decay
|
|
161
|
+
// nybbles, 6 sustain/release nybbles. 21-22 filter cutoff (low three bits,
|
|
162
|
+
// high eight), 23 resonance nybble / which voices are filtered, 24 = $D418:
|
|
163
|
+
// filter mode bits 4-6, voice 3 off bit 7, master volume in the low nybble.
|
|
164
|
+
@address(0xD400)
|
|
165
|
+
export let sidRegisters: array<u8, 25>;
|
|
166
|
+
|
|
167
|
+
// $D419 / $D41A, the two paddle A/D converters (`ad1`/`ad2`); which port's
|
|
168
|
+
// paddles is CIA1 port A bits 6-7's choice. $D41B / $D41C, voice 3's
|
|
169
|
+
// oscillator and envelope, readable — the oscillator with a noise waveform
|
|
170
|
+
// is the hardware entropy the root AGENTS.md wants kept out of the
|
|
171
|
+
// deterministic PRNG.
|
|
172
|
+
@address(0xD419)
|
|
173
|
+
export let paddleX: volatile<u8>;
|
|
174
|
+
@address(0xD41A)
|
|
175
|
+
export let paddleY: volatile<u8>;
|
|
176
|
+
@address(0xD41B)
|
|
177
|
+
export let voice3Oscillator: volatile<u8>;
|
|
178
|
+
@address(0xD41C)
|
|
179
|
+
export let voice3Envelope: volatile<u8>;
|
|
180
|
+
|
|
181
|
+
// ---- CIA1 ($DC00): keyboard, joysticks, the KERNAL's IRQ timer ---------------
|
|
182
|
+
//
|
|
183
|
+
// Port A ($DC00) drives the keyboard's eight columns (a 0 bit selects a
|
|
184
|
+
// column) and reads joystick port 2 on bits 0-4; port B ($DC01) reads the
|
|
185
|
+
// eight rows of the selected column, a 0 bit where a key is down, and
|
|
186
|
+
// joystick port 1 on the same bits 0-4. The joystick lines sit in parallel
|
|
187
|
+
// with the matrix — port 1's pull the rows, port 2's pull the columns —
|
|
188
|
+
// which is why a joystick "types" and why @8bitscript/c64/keyboard and
|
|
189
|
+
// /joystick read the ports in a fixed order once a frame. The KERNAL sets
|
|
190
|
+
// the directions (A out, B in) at boot; the packages set them again.
|
|
191
|
+
// Timer A ($DC04-$DC05) is the KERNAL's 60 Hz IRQ source, silenced by the
|
|
192
|
+
// `sei` the frame runtime starts with; $DC0D is its interrupt control.
|
|
193
|
+
@address(0xDC00)
|
|
194
|
+
export let cia1PortA: volatile<u8>;
|
|
195
|
+
@address(0xDC01)
|
|
196
|
+
export let cia1PortB: volatile<u8>;
|
|
197
|
+
@address(0xDC02)
|
|
198
|
+
export let cia1DirectionA: volatile<u8>;
|
|
199
|
+
@address(0xDC03)
|
|
200
|
+
export let cia1DirectionB: volatile<u8>;
|
|
201
|
+
@address(0xDC0D)
|
|
202
|
+
export let cia1InterruptControl: volatile<u8>;
|
|
203
|
+
|
|
204
|
+
// ---- CIA2 ($DD00): the VIC bank, the serial bus, the user port ---------------
|
|
205
|
+
//
|
|
206
|
+
// Port A bits 0-1 choose the VIC's 16K bank, INVERTED: %11 = bank 0
|
|
207
|
+
// ($0000, the boot state), %10 = bank 1 ($4000), %01 = bank 2 ($8000),
|
|
208
|
+
// %00 = bank 3 ($C000, this package). Bits 3-5 are the serial bus (ATN,
|
|
209
|
+
// CLOCK and DATA out) and bits 6-7 its inputs, so the bank is changed by
|
|
210
|
+
// masking, never by storing a whole byte. $DD02 is the direction register:
|
|
211
|
+
// bits 0-1 must be outputs (the KERNAL sets $3F). $DD0D is CIA2's
|
|
212
|
+
// interrupt control: its interrupts are the NMI line (the RESTORE key is
|
|
213
|
+
// the other NMI source), and @8bitscript/c64/raster masks them.
|
|
214
|
+
@address(0xDD00)
|
|
215
|
+
export let cia2PortA: volatile<u8>;
|
|
216
|
+
@address(0xDD02)
|
|
217
|
+
export let cia2DirectionA: volatile<u8>;
|
|
218
|
+
@address(0xDD0D)
|
|
219
|
+
export let cia2InterruptControl: volatile<u8>;
|
|
220
|
+
|
|
221
|
+
// ---- the screen's memory --------------------------------------------------
|
|
222
|
+
//
|
|
223
|
+
// `screenRam`, `colorRam`, `spritePointers` and `spriteShapes` — the arrays
|
|
224
|
+
// over the RAM the VIC reads — are declared in ./geometry.8bs beside the
|
|
225
|
+
// addresses they are built from, and imported from there.
|
|
226
|
+
|
|
227
|
+
// ---- the machine's state: which mode, and whether interrupts are on --------
|
|
228
|
+
//
|
|
229
|
+
// What the VIC is showing — `VideoMode.TEXT` (the screen at $E000 over the
|
|
230
|
+
// character set at $D000, the state setupVideo() leaves) or
|
|
231
|
+
// `VideoMode.BITMAP` (@8bitscript/c64/bitmap: the bitmap at $E000, its
|
|
232
|
+
// colour matrix under the I/O area). The sprite layer reads it, because
|
|
233
|
+
// the sprite pointers move with the screen matrix; text's prepare() reads
|
|
234
|
+
// it, because its $D018 write is the text mode's.
|
|
235
|
+
export namespace VideoMode {
|
|
236
|
+
const TEXT: utinyint = 0;
|
|
237
|
+
const BITMAP: utinyint = 1;
|
|
238
|
+
}
|
|
239
|
+
export let videoMode: utinyint = 0;
|
|
240
|
+
|
|
241
|
+
// True once @8bitscript/c64/raster has enabled the raster interrupt. The
|
|
242
|
+
// one thing it changes here: a window with the I/O area banked out
|
|
243
|
+
// (bankIoOut/bankIoIn below) has to be an interrupt-free window, because
|
|
244
|
+
// an interrupt handler that fires inside it acknowledges $D019 into RAM
|
|
245
|
+
// and fires again forever — the hang the copy below documents.
|
|
246
|
+
export let interruptsOn: bool = false;
|
|
247
|
+
|
|
248
|
+
// ---- video set-up: the bank, the character set, the pointer -----------------
|
|
249
|
+
//
|
|
250
|
+
// Once, before the first thing is drawn: copy the character ROM into the
|
|
251
|
+
// RAM under it, point the VIC at bank 3, set $D018, and bank the KERNAL
|
|
252
|
+
// out. Every surface in this package calls it first (screen.blank(),
|
|
253
|
+
// text's prepare(), the sprites' show()), and the guard makes every call
|
|
254
|
+
// after the first cost a load and a branch.
|
|
255
|
+
//
|
|
256
|
+
// The copy (copyCharacterRom): with CHAREN ($01 bit 2) clear and HIRAM
|
|
257
|
+
// set, the CPU READS the character ROM at $D000-$DFFF, and its WRITES
|
|
258
|
+
// there still land in the RAM underneath — so reading each byte and
|
|
259
|
+
// storing it back to the same address copies the ROM into RAM in place.
|
|
260
|
+
// The I/O area is gone for the length of the loop (about four frames at
|
|
261
|
+
// 1 MHz), so nothing here touches a register inside it, and interrupts are
|
|
262
|
+
// off around it: the KERNAL's IRQ handler ends by reading CIA1's interrupt
|
|
263
|
+
// register to acknowledge, which with I/O banked out reads the ROM
|
|
264
|
+
// instead and never acknowledges — the interrupt fires again on return,
|
|
265
|
+
// forever. Seen here: the first version of this copy, run before the
|
|
266
|
+
// frame runtime's own `sei`, hung the machine exactly so. `sei` here as
|
|
267
|
+
// well, so a program that draws before its first waitFrame() — or never
|
|
268
|
+
// calls it — is safe too; from this point on the program owns the machine
|
|
269
|
+
// (packages/c64/AGENTS.md).
|
|
270
|
+
//
|
|
271
|
+
// The KERNAL goes out ($01 low bits %101) at the end, and stays out. Two
|
|
272
|
+
// reasons, both structural. The screen is at $E000, and with the KERNAL
|
|
273
|
+
// ROM mapped the CPU's writes there land in RAM but its READS return ROM:
|
|
274
|
+
// `screenRam[cell]` read back the KERNAL, a coarse scroll copied it, a
|
|
275
|
+
// bitmap plot would read-modify-write it. With the ROM out, $E000-$FFFF
|
|
276
|
+
// reads as the RAM the VIC shows. And the CPU's interrupt vectors at
|
|
277
|
+
// $FFFA-$FFFF are then RAM too, which is how @8bitscript/c64/raster owns
|
|
278
|
+
// the IRQ without the KERNAL's handler in the way: this package's native
|
|
279
|
+
// start-up code (native/6502/raster.s, an .init section, so it runs before
|
|
280
|
+
// main() in every program that links this package) has already pointed
|
|
281
|
+
// both the NMI and the IRQ vector at a bare `rti`, so a RESTORE key press
|
|
282
|
+
// with the ROM out does nothing at all. Nothing in an 8bitscript program
|
|
283
|
+
// calls the KERNAL after this point — main() never returns to it either.
|
|
284
|
+
let videoReady: bool = false;
|
|
285
|
+
|
|
286
|
+
export function setupVideo(): void {
|
|
287
|
+
if (videoReady) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
videoReady = true;
|
|
291
|
+
asm6502 {
|
|
292
|
+
sei
|
|
293
|
+
}
|
|
294
|
+
copyCharacterRom();
|
|
295
|
+
cia2DirectionA = cia2DirectionA | 0x03;
|
|
296
|
+
cia2PortA = cia2PortA & 0xFC; // bank 3
|
|
297
|
+
memoryPointer = Video.MEMORY_POINTER_UPPERCASE;
|
|
298
|
+
spriteEnable = 0; // no sprite until a program asks; the pointers are whatever RAM held
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// The character ROM's 4K into the RAM under it, in place, and the port left
|
|
302
|
+
// at %101 (KERNAL out, I/O in). Safe from any port state, and callable
|
|
303
|
+
// again — @8bitscript/c64/charset's restore() is a second copy over a
|
|
304
|
+
// program's own glyphs. Interrupts are off for the copy and back on after
|
|
305
|
+
// it only if the raster interrupt had them on.
|
|
306
|
+
export function copyCharacterRom(): void {
|
|
307
|
+
asm6502 {
|
|
308
|
+
sei
|
|
309
|
+
}
|
|
310
|
+
processorPort = (processorPort & 0xF8) | 0x02; // %010: KERNAL in, character ROM at $D000, I/O out
|
|
311
|
+
for (let i: usmallint = 0; i < 4096; i++) {
|
|
312
|
+
memory.write(Video.CHARSET + i, memory.read(Video.CHARSET + i));
|
|
313
|
+
}
|
|
314
|
+
processorPort = (processorPort & 0xF8) | 0x05; // %101: KERNAL out, I/O in
|
|
315
|
+
if (interruptsOn) {
|
|
316
|
+
asm6502 {
|
|
317
|
+
cli
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---- the region, at run time ------------------------------------------------
|
|
323
|
+
//
|
|
324
|
+
// PAL or NTSC is not a build fact: one .prg runs on both, and the frame
|
|
325
|
+
// runtime finds out for itself at start-up (FRAME_SYNC.c64's probe,
|
|
326
|
+
// packages/backend-6502) — but keeps the answer to itself. So this is the
|
|
327
|
+
// same probe, for a program that needs it: @8bitscript/c64/sid's note
|
|
328
|
+
// tables (the SID's pitch is the CPU clock's), a program timing something
|
|
329
|
+
// in raster lines. Sync to the top of a frame, then watch one whole frame
|
|
330
|
+
// go by: only a PAL raster (312 lines) ever reaches line 288 — bit 8 set
|
|
331
|
+
// in $D011 with $D012 at 32 or more — where an NTSC frame stops at 262.
|
|
332
|
+
// Two frames, once; keep the answer. A cross-machine builtin that
|
|
333
|
+
// publishes the runtime's own answer is the design this waits for.
|
|
334
|
+
export namespace Region {
|
|
335
|
+
const PAL: utinyint = 0;
|
|
336
|
+
const NTSC: utinyint = 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function rasterInTopHalf(): bool {
|
|
340
|
+
// $D012 first, then $D011 bit 8 (FRAME_SYNC.c64 says why the order).
|
|
341
|
+
return raster < 128 && (control1 & 0x80) == 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function detectRegion(): utinyint {
|
|
345
|
+
let pal: bool = false;
|
|
346
|
+
while (rasterInTopHalf()) {}
|
|
347
|
+
while (!rasterInTopHalf()) {} // the wrap to line 0
|
|
348
|
+
while (rasterInTopHalf()) {}
|
|
349
|
+
while (!rasterInTopHalf()) {
|
|
350
|
+
if ((control1 & 0x80) != 0 && raster >= 32) {
|
|
351
|
+
pal = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (pal) {
|
|
355
|
+
return Region.PAL;
|
|
356
|
+
}
|
|
357
|
+
return Region.NTSC;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---- writes under the I/O area --------------------------------------------
|
|
361
|
+
//
|
|
362
|
+
// $D000-$DFFF is RAM the VIC reads (the character set, and in bitmap mode
|
|
363
|
+
// the colour matrix and the sprite pointers) that the CPU can only reach
|
|
364
|
+
// with the I/O area banked out — so every write there is a window: `sei`,
|
|
365
|
+
// CHAREN clear (%100 from %101: RAM everywhere), the writes, I/O back,
|
|
366
|
+
// `cli` if the raster interrupt is on. Nothing between bankIoOut() and
|
|
367
|
+
// bankIoIn() may touch a register, and the window is kept short: the
|
|
368
|
+
// raster interrupt, if enabled, is late by the window's length.
|
|
369
|
+
export function bankIoOut(): void {
|
|
370
|
+
asm6502 {
|
|
371
|
+
sei
|
|
372
|
+
}
|
|
373
|
+
processorPort = processorPort & 0xFB;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function bankIoIn(): void {
|
|
377
|
+
processorPort = processorPort | 0x04;
|
|
378
|
+
if (interruptsOn) {
|
|
379
|
+
asm6502 {
|
|
380
|
+
cli
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// One byte into the RAM under the I/O area, in its own window.
|
|
386
|
+
export function writeUnderIo(address: usmallint, value: utinyint): void {
|
|
387
|
+
bankIoOut();
|
|
388
|
+
memory.write(address, value);
|
|
389
|
+
bankIoIn();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// One byte from the RAM under the I/O area.
|
|
393
|
+
export function readUnderIo(address: usmallint): utinyint {
|
|
394
|
+
bankIoOut();
|
|
395
|
+
let value: utinyint = memory.read(address);
|
|
396
|
+
bankIoIn();
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ---- REU (RAM Expansion Unit) --------------------------------------------
|
|
401
|
+
//
|
|
402
|
+
// $DF00-$DF0A, the standard Commodore 1764/1750-compatible REU DMA
|
|
403
|
+
// controller register map — confirmed against VICE's own `-reu`/`-reusize`
|
|
404
|
+
// support (x64sc -help) and the register layout every REU-compatible
|
|
405
|
+
// expansion since the 1700 has shared. These addresses exist in the SDK for
|
|
406
|
+
// every c64 program (the same reasoning @8bitscript/pet's screen.8bs
|
|
407
|
+
// documents for `setColors` on a machine without colour): they only do
|
|
408
|
+
// something when the program is actually built and run against a `reu*`
|
|
409
|
+
// hardware profile (--profile in packages/cli/src/build.mjs and run.mjs's
|
|
410
|
+
// `-reu -reusize`) — on the stock C64 these are ordinary, if unusual,
|
|
411
|
+
// memory-mapped I/O addresses, not a real DMA controller. This is raw
|
|
412
|
+
// register access, not a transfer driver: a caller sequences the registers
|
|
413
|
+
// itself (base addresses, length, then a command byte — bit 7 set to
|
|
414
|
+
// execute, bits 0-1 for the transfer direction: 00 stash C64→REU, 01 fetch
|
|
415
|
+
// REU→C64, 10 swap, 11 verify), the same level @8bitscript/vic20's
|
|
416
|
+
// `vicColor` and this package's own `borderColor` above operate at.
|
|
417
|
+
@address(0xDF00) export let reuStatus: volatile<u8>;
|
|
418
|
+
@address(0xDF01) export let reuCommand: volatile<u8>;
|
|
419
|
+
@address(0xDF02) export let reuC64AddressLow: volatile<u8>;
|
|
420
|
+
@address(0xDF03) export let reuC64AddressHigh: volatile<u8>;
|
|
421
|
+
@address(0xDF04) export let reuAddressLow: volatile<u8>;
|
|
422
|
+
@address(0xDF05) export let reuAddressHigh: volatile<u8>;
|
|
423
|
+
@address(0xDF06) export let reuBank: volatile<u8>;
|
|
424
|
+
@address(0xDF07) export let reuLengthLow: volatile<u8>;
|
|
425
|
+
@address(0xDF08) export let reuLengthHigh: volatile<u8>;
|
|
426
|
+
@address(0xDF09) export let reuInterruptMask: volatile<u8>;
|
|
427
|
+
@address(0xDF0A) export let reuAddressControl: volatile<u8>;
|