-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathC64Bus.cs
404 lines (326 loc) · 15.5 KB
/
C64Bus.cs
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
using Commodore64.Cartridge;
using Commodore64.Cia;
using Commodore64.Enums;
using Commodore64.Sid;
using Commodore64.Sid.Enums;
using Commodore64.Vic;
using Commodore64.Vic.Enums;
using Extensions.Byte;
using Extensions.Enums;
using Hardware.Memory;
using Hardware.Mos6526Cia;
using System;
using System.IO;
namespace Commodore64
{
public class C64Bus : MemoryBase<byte>
{
// The bank switching technique in the C64 also handles external ROM in the form of a
// cartridge plugged into the expansion port and is linked to the PLA by 2 lines: GAME and EXROM.
// These lines have been designed to allow a cartridge to map one or two 8 kByte banks of
// ROM into the system easily by wiring the ROM's enable pin to ROMH/ROML lines of the port.
// Extra logic on the cartridge can be used to implement more complex behaviour such as Freezers though.
// GAME (pin 8) is pulled logically high (set to 1) through internal pull-up resistor RP4.
// When a cartridge is attached and GAME is pulled logically low (cleared to 0),
// the interaction with the CPU Control Lines can enable cartridge ROM to be banked in between
// $8000-$BFFF and/or $E000-$EFFF. GAME can also have the effect of completely banking out all
// memory between $1000-$7FFF and $A000-$CFFF (Ultimax mode).
private bool _game => _cartridge == null ? true : _cartridge.ControlLineGame;
// EXROM (pin 9) is pulled logically high (set to 1) through internal pull-up resistor RP4.
// When a cartridge is attached and EXROM is pulled logically low (cleared to 0),
// the interaction with the CPU Control Lines can enable cartridge ROM to be banked in between
// $8000-$BFFF and/or $E000-$EFFF.
private bool _exRom => _cartridge == null ? true : _cartridge.ControlLineExRom;
private MemoryBase<byte> _romBasic;
public MemoryBase<byte> _romCharacter;
public MemoryBase<byte> _romKernal;
private Cia1 _cia;
private Cia2 _cia2;
private readonly VicIi _vic;
private readonly SidBase _sid;
private ICartridge _cartridge;
public C64Bus(Cia1 cia, Cia2 cia2, VicIi vic, SidBase sid) : base(0x10000)
{
//_memory.FillWithRandomData();
// Intialize processor addressing mode with default values
// http://sta.c64.org/cbm64mem.html
_memory[0] = C64MemoryValues.PROCESSOR_PORT_DIRECTION_REGISTER_DEFAULT;
_memory[1] = C64MemoryValues.PROCESSOR_PORT_REGISTER_DEFAULT;
_romBasic = new MemoryBase<byte>(File.ReadAllBytes("basic.rom")) { IsReadOnly = true };
_romCharacter = new MemoryBase<byte>(File.ReadAllBytes("char.rom")) { IsReadOnly = true };
_romKernal = new MemoryBase<byte>(File.ReadAllBytes("kernal.rom")) { IsReadOnly = true };
_cia = cia;
_cia2 = cia2;
_vic = vic;
_sid = sid;
}
public void InsertCartridge(ICartridge cartridge)
{
_cartridge = cartridge;
}
public override byte Read(int address)
{
// Handle the C64 memory map and the bank switching capabilities of the 6510
// http://sta.c64.org/cbm64mem.html
// https://www.c64-wiki.com/wiki/Bank_Switching
// Processor port. Configuration for memory areas. (Bits 0-2)
// Memory location 0x01 is hard wired for bank switching
var bankState = (BankMode)
((byte)(_memory[1] & 0b00000111))
.SetBit(BitFlag.BIT_3, _game)
.SetBit(BitFlag.BIT_4, _exRom);
// Always RAM (page 0-15)
if (address >= 0x0000 && address <= 0x0FFF)
{
return base.Read(address);
}
// Always RAM (page 16-127)
// Except when EXROM==1 and GAME==0
// Some exceptions for cartridge rom, not implemented yet
if (address >= 0x1000 && address <= 0x7FFF)
{
// UNMAPPED
if (_exRom && !_game)
{
throw new AccessViolationException();
}
// RAM
return base.Read(address);
}
// Page 128-159
// RAM OR CART ROM LO
if (address >= 0x8000 && address <= 0x9FFF)
{
switch (bankState)
{
// CART ROM LO
case BankMode.BANK_MODE_23:
case BankMode.BANK_MODE_22:
case BankMode.BANK_MODE_21:
case BankMode.BANK_MODE_20:
case BankMode.BANK_MODE_19:
case BankMode.BANK_MODE_18:
case BankMode.BANK_MODE_17:
case BankMode.BANK_MODE_16:
case BankMode.BANK_MODE_15:
case BankMode.BANK_MODE_11:
case BankMode.BANK_MODE_07:
case BankMode.BANK_MODE_03:
return _cartridge[address];
// RAM
default:
return base.Read(address);
}
}
// BASIC ROM, RAM or CARTRIDGE ROM (page 160-191)
if (address >= 0xA000 && address <= 0xBFFF)
{
// UNMAPPED
if (_exRom && !_game)
{
throw new AccessViolationException();
}
switch (bankState)
{
// BASIC
case BankMode.BANK_MODE_31:
case BankMode.BANK_MODE_27:
case BankMode.BANK_MODE_15:
case BankMode.BANK_MODE_11:
return _romBasic.Read(address - 0xA000);
// CART ROM HI
case BankMode.BANK_MODE_07:
case BankMode.BANK_MODE_06:
case BankMode.BANK_MODE_03:
case BankMode.BANK_MODE_02:
return _cartridge[address];
// RAM
default:
return base.Read(address);
}
}
// Always RAM (page 192-207)
if (address >= 0xC000 && address <= 0xCFFF)
{
// UNMAPPED
if (_exRom && !_game)
{
throw new AccessViolationException();
}
// RAM
return base.Read(address);
}
// I/O, RAM, CHAR ROM (page 208-223)
if (address >= 0xD000 && address <= 0xDFFF)
{
switch (bankState)
{
// IO
case BankMode.BANK_MODE_31:
case BankMode.BANK_MODE_30:
case BankMode.BANK_MODE_14:
case BankMode.BANK_MODE_29:
case BankMode.BANK_MODE_13:
case BankMode.BANK_MODE_23:
case BankMode.BANK_MODE_22:
case BankMode.BANK_MODE_21:
case BankMode.BANK_MODE_20:
case BankMode.BANK_MODE_19:
case BankMode.BANK_MODE_18:
case BankMode.BANK_MODE_17:
case BankMode.BANK_MODE_16:
case BankMode.BANK_MODE_15:
case BankMode.BANK_MODE_07:
case BankMode.BANK_MODE_06:
case BankMode.BANK_MODE_05:
// VIC-II (0xD000 - 0xD3FF, VIC-II register images repeated every $40, 64 bytes)
if (address >= 0xD000 && address <= 0xD3FF)
{
// The VIC-II class has its own indexer which makes it easy to map
// addresses into the VIC-II. The `% 0x40` makes sure that the
// registers available in the VIC-II are mirrored all the way up to
// 0xD3FF.
return _vic[(Register)((address - 0xD000) % 0x40)];
}
// SID (0xD400 - 0xD7FF, VIC-II register images repeated every $20, 32 bytes)
if (address >= 0xD400 && address <= 0xD7FF)
{
// The SID class has its own indexer which makes it easy to map
// addresses into the VIC-II. The `% 0x20` makes sure that the
// registers available in the VIC-II are mirrored all the way up to
// 0xD7FF.
return _sid[(SidRegister)((address - 0xD400) % 0x20)];
}
// CIA 1
if (address >= 0xDC00 && address <= 0xDCFF)
{
switch (address)
{
case 0xDC09:
return _cia.TimeOfDaySecondsBcd;
case 0xDC0A:
return _cia.TimeOfDayMinutesBcd;
case 0xDC0B:
return _cia.TimeOfDayHoursBcd;
default:
// The CIA class has its own indexer which makes it easy to map
// addresses into the CIA. The `% 0x10` makes sure that the
// 16 registers available in the CIA are mirrored all the way up to
// 0xDCFF.
return _cia[(address - 0xDC00) % 0x10];
}
}
// CIA 2
if (address >= 0xDD00 && address <= 0xDDFF)
{
return _cia2[(Cia.Enums.Register)((address - 0xDD00) % 0x10)];
//return base.Read(address);
}
//throw new AccessViolationException();
//Debug.WriteLine($"Trying to access I/O at address: 0x{address:X2}");
break;
// CHAR ROM
case BankMode.BANK_MODE_27:
case BankMode.BANK_MODE_26:
case BankMode.BANK_MODE_10:
case BankMode.BANK_MODE_25:
case BankMode.BANK_MODE_09:
case BankMode.BANK_MODE_11:
case BankMode.BANK_MODE_03:
case BankMode.BANK_MODE_02:
return _romCharacter.Read(address - 0xD000);
// RAM
default:
return base.Read(address);
}
}
// KERNAL ROM, RAM, CARTRIDGE ROM (page 224-255)
// Some exceptions for I/O and cartridge rom, not implemented yet
if (address >= 0xE000 && address <= 0xFFFF)
{
switch (bankState)
{
// KERNAL ROM
case BankMode.BANK_MODE_31:
case BankMode.BANK_MODE_30:
case BankMode.BANK_MODE_14:
case BankMode.BANK_MODE_27:
case BankMode.BANK_MODE_26:
case BankMode.BANK_MODE_10:
case BankMode.BANK_MODE_15:
case BankMode.BANK_MODE_11:
case BankMode.BANK_MODE_07:
case BankMode.BANK_MODE_06:
case BankMode.BANK_MODE_03:
case BankMode.BANK_MODE_02:
return _romKernal.Read(address - 0xE000);
// CART ROM HI
case BankMode.BANK_MODE_23:
case BankMode.BANK_MODE_22:
case BankMode.BANK_MODE_21:
case BankMode.BANK_MODE_20:
case BankMode.BANK_MODE_19:
case BankMode.BANK_MODE_18:
case BankMode.BANK_MODE_17:
case BankMode.BANK_MODE_16:
return _cartridge[address];
// RAM
default:
return base.Read(address);
}
}
// RAM
return base.Read(address);
}
public override void Write(int address, byte value)
{
var processorPortMemoryConfiguration = _memory[1] & 0b00000111;
if (address == 0x0001)
{
base.Write(address, (byte)(value | ((byte)(value & _memory[0x0000]))));
return;
}
// I/O
if (processorPortMemoryConfiguration == 0b101 || processorPortMemoryConfiguration == 0b111 || processorPortMemoryConfiguration == 0b110)
{
// VIC-II (0xD000 - 0xD3FF, VIC-II register images repeated every $40, 64 bytes)
if (address >= 0xD000 && address <= 0xD3FF)
{
// The VIC-II class has its own indexer which makes it easy to map
// addresses into the VIC-II. The `% 0x40` makes sure that the
// registers available in the VIC-II are mirrored all the way up to
// 0xD3FF.
_vic[(Register)((address - 0xD000) % 0x40)] = value;
}
// SID (0xD400 - 0xD7FF, VIC-II register images repeated every $20, 32 bytes)
if (address >= 0xD400 && address <= 0xD7FF)
{
// The SID class has its own indexer which makes it easy to map
// addresses into the VIC-II. The `% 0x20` makes sure that the
// registers available in the VIC-II are mirrored all the way up to
// 0xD7FF.
_sid[(SidRegister)((address - 0xD400) % 0x20)] = value;
}
// CIA 1
if (address >= 0xDC00 && address <= 0xDCFF)
{
// The CIA class has its own indexer which makes it easy to map
// addresses into the CIA. The `% 0x10` makes sure that the
// 16 registers available in the CIA are mirrored all the way up to
// 0xDCFF.
_cia[(address - 0xDC00) % 0x10] = value;
return;
}
// CIA 2
if (address >= 0xDD00 && address <= 0xDDFF)
{
//base.Write(address, value);
//Debug.WriteLine($"Unimplemented CIA2.Write (using base.Write) Address: 0x{address:X4}, Value: 0x{value:X2}");
_cia2[(Cia.Enums.Register)((address - 0xDD00) % 0x10)] = value;
return;
}
}
base.Write(address, value);
}
}
}