-
Notifications
You must be signed in to change notification settings - Fork 6
/
vic.c
331 lines (271 loc) · 9.7 KB
/
vic.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/*
* Poor man's emulation of the C64's VIC-II (Video-Interface-Chip).
*
* WebSid (c) 2019 Jürgen Wothke
* version 0.93
*
* Different system versions:
* PAL: 312 rasters with 63 cycles, system clock: 985248Hz,
* frame rate: 50.124542Hz, 19656 cycles per frame
* NTSC: 263 rasters with 65 cyles, system clock: 1022727Hz,
* frame rate: 59.8260895Hz, 17095 cycles per frame
*
* 200 visible lines/25 text lines..
*
* trivia: "Normally the VIC always uses the 1st phase of each
* clock cycle (for bus access) while cpu uses the 2nd phase.
* During badline cycles VIC may completely take over 40-43 cycles
* and stun the CPU. Technically the CPU is told on its "RDY" pin
* (HIGH) if everything runs normal or if a badline mode is about
* to start. (The CPU then may still complete its write ops - for
* a maximum of 3 cycles before it pauses to let VIC take over the
* bus.) Within an affected raster line the "stun phase" starts at
* cycle 12 and lasts until cycle 55."
*
* useful links:
*
* - Christian Bauer's: "The MOS 6567/6569 video controller
* (VIC-II) and its application in the Commodore 64"
*
* LIMITATIONS: Enabled sprites (see D015) normally cause the same
* kind of "CPU stun" effect as the "bad line" but this
* effect has not been implemented here (it does not
* seem to be relevant for most songs).
*
* Terms of Use: This software is licensed under a CC BY-NC-SA
* (http://creativecommons.org/licenses/by-nc-sa/4.0/).
*/
#include "vic.h"
#include "memory.h"
#include "cpu.h"
static double _fps;
static uint8_t _cycles_per_raster;
static uint16_t _lines_per_screen;
static uint8_t _x; // in cycles
static uint16_t _y; // in rasters
static uint8_t _badline_den;
static uint32_t _raster_latch; // optimization: D012 + D011-bit 8 combined
static uint8_t (*_stunFunc)(uint8_t x, uint16_t y, uint8_t cpr);
// default impl
static uint8_t intBadlineStun(uint8_t x, uint16_t y, uint8_t cpr) {
if (_badline_den) {
// optimization?: the _y-pos and "& 0x7" checks could be cached
// - avoiding re-checks on each cycle - but at a cost for the
// vicClock() logic; better keep the extra cost limited to
// those few songs that use it
if ((y >= 0x30) && (y <= 0xf7) && ((memReadIO(0xd011) & 0x7) == (y & 0x7))) {
if ((x >= 11) && (x <= 53)) {
if ((x >= 14)) {
return 2; // stun completely
} else {
return 1; // stun on read
}
}
}
}
return 0;
}
void vicSetStunImpl(uint8_t (*f)(uint8_t x, uint16_t y, uint8_t cpr)) {
_stunFunc= f;
}
uint8_t (*vicGetStunImpl(void))(uint8_t, uint16_t, uint8_t) { // crappy C syntax
return _stunFunc;
}
double vicFramesPerSecond() {
return _fps;
}
void vicSetModel(uint8_t ntsc_mode) {
// emulation only supports new PAL & NTSC model (none of
// the special models)
_x= _y= 0;
// note: clocking is derived from the targetted video standard.
// 14.31818MHz (NTSC) respectively of 17.734475MHz (PAL) -
// divide by 4 for respective color subcarrier: 3.58Mhz NTSC,
// 4.43MHz PAL. the 8.18MHz (NTSC) respectively 7.88MHz (PAL)
// "dot clock" is derived from the above system clock is finally
// "dot clock" divided by 8
if(ntsc_mode) {
// NTSC
_fps= 59.8260895;
_cycles_per_raster= 65;
_lines_per_screen = 263; // with 520 pixels
} else {
// PAL
_fps= 50.124542;
_cycles_per_raster= 63;
_lines_per_screen = 312; // with 504 pixels
}
// init to very end so that next clock will create a raster 0 IRQ...
_x= _cycles_per_raster-1;
_y= _lines_per_screen-1;
// clocks per frame: NTSC: 17095 - PAL: 19656
}
static void checkIRQ() {
// "The test for reaching the interrupt raster line is done in
// cycle 0 of every line (for line 0, in cycle 1)."
if (_y == _raster_latch) {
// always signal (test case: Wally Beben songs that use
// CIA 1 timer for IRQ but check for this flag)
uint8_t latch= memReadIO(0xd019) | 0x1;
uint8_t interrupt_enable= memReadIO(0xd01a) & 0x1;
if (interrupt_enable) {
latch |= 0x80; // signal VIC interrupt
}
memWriteIO(0xd019, latch);
}
}
void vicClock() {
if (!_x && !_y) { // special case: in line 0 it is cycle 1
checkIRQ();
_badline_den= memReadIO(0xd011) & 0x10; // default for new frame
}
_x+= 1;
if (_x >= _cycles_per_raster) {
_x= 0;
_y+= 1;
if (_y >= _lines_per_screen) {
_y= 0;
}
if (_y) checkIRQ(); // normal case: check in cycle 0
}
}
uint8_t vicIRQ() {
return memReadIO(0xd019) & 0x80;
}
/*
"A Bad Line Condition is given at any arbitrary clock cycle, if at
the negative edge of ø0 at the beginning of the cycle RASTER >= $30
and RASTER <= $f7 and the lower three bits of RASTER are equal to
YSCROLL and if the DEN (display enable: $d011, bit 4) bit was set
during an arbitrary cycle of raster line $30."
During cycles 15-54(incl) the CPU is *always* completely blocked -
BUT it may be blocked up to 3 cycles earlier depending on the first
used sub-instruction "read-access", i.e. from cycle 12 the CPU is
stunned at its first "read-access" (i.e. the write-access of current
OP is allowed to complete.. usually at the end of OP - max 3
consecutive "write" cycles in any 6502 OP)
=> this means that "OPs in progress" might be stunned in the middle
of the execution, i.e. OP has just been started and then is stunned
before it can read the data that is needs.
KNOWN LIMITATION:
"displayed sprites" cause a similar effect of stunning the CPU -
"stealing" ~2 cycles for one sprite and up to ~19 cycles for all 8
sprites (if they are shown on the specific line). As for the
"badline" there is a 3 cycle "stun" period (during which more or less
cycles are lost depending on the current OP) before the bus is
completely blocked for the CPU. For more details see "Missing Cycles"
by Pasi 'Albert' Ojala & "The MOS 6567/6569 video controller (VIC-II)
and its application in the Commodore 64" by Christian Bauer.
Few songs actually depend on respective sprite timing and it isn't
implemented here: The limited benefits do not seem to be worth the
extra implementation and runtime cost. examples that actually
depend on it: Vicious_SID_2-15638Hz.sid, Fantasmolytic_tune_2).
*/
uint8_t vicStunCPU() {
return _stunFunc(_x, _y, _cycles_per_raster);
}
static void cacheRasterLatch() {
_raster_latch= memReadIO(0xd012) + (((uint16_t)memReadIO(0xd011)&0x80)<<1);
}
void vicReset(uint8_t is_rsid, uint8_t ntsc_mode) {
_stunFunc= &intBadlineStun;
vicSetModel(ntsc_mode);
// by default C64 is configured with CIA1 timer / not raster irq
// presumable "the machine" has been running for more than
// one frame before the prog is run (raster has already fired)
memWriteIO(0xd019, 0x01);
memWriteIO(0xd01a, 0x00); // raster irq not active
if (is_rsid) {
// RASTER default to 0x137 (see real HW)
memWriteIO(0xd011, 0x9B);
memWriteIO(0xd012, 0x37); // raster at line x
} else {
memWriteIO(0xd011, 0x1B);
memWriteIO(0xd012, 0x0); // raster must be below 0x100
}
_badline_den= 1; // see d011-defaults above
cacheRasterLatch();
}
void vicSetDefaultsPSID(uint8_t timerDrivenPSID) {
// NOTE: braindead SID File specs apparently allow PSID INIT to
// specifically DISABLE the IRQ trigger that their PLAY depends
// on (actually another one of those UNDEFINED features - that
// "need not to be documented")
if (timerDrivenPSID) {
// memWriteIO(0xd019, 0x81); // no need since not active before
} else {
memWriteIO(0xd01a, 0x81); // enable RASTER IRQ
}
}
// ------------------------ VIC I/O --------------------------------
static void writeD019(uint8_t value) {
// ackn vic interrupt, i.e. a setting a bit actually clears it
// note: :some players use "INC D019" (etc) to ackn the interrupt
// (all Read-Modify-Write instructions write the original value
// before they write the new value, i.e. the intermediate write
// does the clearing.. - see cpu.c emulation)
// "The bit 7 in the latch $d019 reflects the inverted state of
// the IRQ output of the VIC.", i.e. if the source conditions
// clear, so does the overall output.
// test-case: some songs only clear the "RASTER" but not the "IRQ"
// flag (e.g. Ozone.sid)
// BUG: Utopia_tune_6.sid uses the same ACKN as Ozone.sid but it
// "expects" the IRQ to be immediately retriggered.. what's the
// difference in this long running IRQ handler? XXX some special
// case does not seem to be handled correctly yet!
uint8_t v= memReadIO(0xd019);
v = v&(~value); // clear (source) flags directly
if (!(v & 0xf)) {
v &= 0x7f; // all sources are gone: IRQ flag should also be cleared
}
memWriteIO(0xd019, v);
}
static void writeD01A(uint8_t value) {
memWriteIO(0xd01A, value);
if (value & 0x1) {
// check if RASTER condition has already fired previously
uint8_t d= memReadIO(0xd019);
if (d & 0x1) {
memWriteIO(0xd019, d | 0x80); // signal VIC interrupt
}
}
}
void vicWriteMem(uint16_t addr, uint8_t value) {
switch (addr) {
case 0xd011: {
const uint8_t new_den= value & 0x10;
// badlineCondition: "..if the DEN bit was set during an
// arbitrary cycle of raster line $30 [for at least one cycle]"
if (_y < 0x2f) { // our 1st line is 0
_badline_den= new_den;
} else if (_y == 0x2f) {
// theoretically the "flag" could still be cleared in the
// very 1st cycle of the line (ignore this special case)
_badline_den|= new_den;
}
}
case 0xd012:
memWriteIO(addr, value);
cacheRasterLatch();
break;
case 0xd019:
writeD019(value);
break;
case 0xd01A:
writeD01A(value);
break;
default:
memWriteIO(addr, value);
}
}
uint8_t vicReadMem(uint16_t addr) {
switch (addr) {
case 0xd011:
return (memReadIO(0xd011) & 0x7f) | ((_y & 0x100) >> 1);
case 0xd012:
return _y & 0xff;
case 0xd019:
return memReadIO(0xd019);
}
return memReadIO(addr);
}