-
Notifications
You must be signed in to change notification settings - Fork 4
/
memory.go
470 lines (403 loc) · 12.8 KB
/
memory.go
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
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
package rcs
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"strings"
)
type MemoryEvent struct {
Read bool
Bank int
Addr int
Value uint8
}
/*
Memory represents an address space used to access RAM, ROM, IO ports,
and external devices.
To create a memory space with a 16 line address bus and a single bank, use:
mem := rcs.NewMemory(1, 0x10000)
This struct is just a container for the address space and has no actual
memory mapped to it yet. Reads or writes to an unmapped address emit a
warning through the standard logger.
Single values are mapped for read/write access using the MapRW method. This is
useful for mapping ports or registers of a device into the address space. In
this example from the Commodore 64, the X coordinate for sprite #0 is mapped to
0xd000 and the Y coordinate is mapped to 0xd001:
mem.MapRW(0xd000, &sprites[0].X)
mem.MapRW(0xd001, &sprites[0].Y)
Use MapRO for a read-only mapping and MapWO for a write-only mapping. In
this example from Pac-Man, the value of port IN0 is read through address
0x5000 but interrupts can be enabled or disabled by writing to address
0x5000:
mem.MapRO(0x5000, &portIN0)
mem.MapWO(0x5000, &irqEnable)
A range of addresses can map to the same value with:
for i := 0x50c0; i <= 0x50ff; i++ {
mem.MapWO(i, &watchdogReset)
}
Large blocks can be mapped by passing in a uint8 slice using MapRAM
for read/write access and MapROM for read-only access. The following example
maps a 16KB block of ROM to 0x0000 - 0x3fff and a 48KB block of RAM
to 0x4000 - 0xffff:
rom := []uint8{ ... 16KB data ... }
ram := make([]uint8, 48*1024, 48*1024)
mem.MapROM(0x0000, rom)
mem.MapRAM(0x4000, ram)
To "overlay" ROM on top of RAM, first use MapRAM and then MapROM which will
replace the read mappings while leaving the write mappings untouched. In
the following example, reads in the first 16KB come from the ROM but
writes go to the RAM:
rom := []uint8{ ... 16KB data ... }
ram := make([]uint8, 64*1024, 64*1024)
mem.MapRAM(0x0000, ram)
mem.MapROM(0x0000, rom)
To use banked memory, create memory with more than one bank:
mem := rcs.NewMemory(2, 0x10000)
All map, read, and write operations are performed on the selected bank
which by default is zero. The following example has a ROM overlay
in bank 0 and full access to the RAM in bank 1:
rom := []uint8{ ... 16KB data ... }
ram := make([]uint8, 64*1024, 64*1024)
mem.SetBank(0)
mem.MapRAM(0x0000, ram)
mem.MapROM(0x0000, rom)
mem.SetBank(1)
mem.MapRAM(0x0000, ram)
*/
type Memory struct {
Name string
MaxAddr int // maximum valid address
Callback func(MemoryEvent) // function called on watch events
NBank int // number of banks
// read and write functions for each bank
reads [][]Load8
writes [][]Store8
// previous read and write functions are stored here during watches
preads [][]Load8
pwrites [][]Store8
// selected bank index
bank int
// read and write functions for the selected bank
read []Load8
write []Store8
}
// NewMemory creates a memory space of uint8 values that are addressable
// from 0 to size - 1. This function only creates the address space;
// values must be mapped using the Map methods. To create banked memory, use a
// value greater than one for the banks argument.
func NewMemory(banks int, size int) *Memory {
if banks < 1 {
banks = 1
}
mem := &Memory{
Name: "mem",
MaxAddr: size - 1,
NBank: banks,
reads: make([][]Load8, banks, banks),
writes: make([][]Store8, banks, banks),
preads: make([][]Load8, banks, banks),
pwrites: make([][]Store8, banks, banks),
}
for b := 0; b < banks; b++ {
mem.reads[b] = make([]Load8, size, size)
mem.writes[b] = make([]Store8, size, size)
mem.preads[b] = make([]Load8, size, size)
mem.pwrites[b] = make([]Store8, size, size)
}
mem.read = mem.reads[0]
mem.write = mem.writes[0]
mem.Callback = func(MemoryEvent) {}
return mem
}
// Read returns the 8-bit value at the given address.
func (m *Memory) Read(addr int) uint8 {
if m.read[addr] == nil {
log.Printf("(!) %v: unmapped read, bank %v, addr %v", m.Name,
X(m.bank), X(addr))
return 0
}
v := m.read[addr]()
return v
}
// Write sets the 8-bit value at the given address.
func (m *Memory) Write(addr int, val uint8) {
if m.write[addr] == nil {
log.Printf("(!) %v: unmapped write, bank %v, addr %v, val %v",
m.Name, X(m.bank), X(addr), X8(val))
return
}
m.write[addr](val)
}
// WriteN sets multiple 8-bit values starting with the given address.
func (m *Memory) WriteN(addr int, values ...uint8) {
for i, val := range values {
m.write[addr+i](val)
}
}
// ReadLE returns the 16-bit value at addr and addr+1 stored in little endian
// byte order.
func (m *Memory) ReadLE(addr int) int {
lo := int(m.Read(addr))
hi := int(m.Read(addr + 1))
return hi<<8 + lo
}
// WriteLE puts a 16-bit value at addr and addr+1 stored in little endian
// byte order.
func (m *Memory) WriteLE(addr int, val int) {
hi := uint8(val >> 8)
lo := uint8(val)
m.Write(addr, lo)
m.Write(addr+1, hi)
}
// MapRAM adds read/write maps to all of the 8-bit values in ram starting at
// addr. Any existing read or write maps are replaced.
func (m *Memory) MapRAM(addr int, ram []uint8) {
for i := 0; i < len(ram); i++ {
j := i
m.read[addr+i] = func() uint8 { return ram[j] }
m.write[addr+i] = func(v uint8) { ram[j] = v }
}
}
// MapROM adds read maps to all of the 8-bit values in rom starting at
// addr. Any existing read maps are replaced but write maps are not altered.
// If the rom passed in is nil, no mappings are changed.
func (m *Memory) MapROM(addr int, rom []uint8) {
if rom == nil {
return
}
for i := 0; i < len(rom); i++ {
j := i
m.read[addr+i] = func() uint8 { return rom[j] }
}
}
// MapRW adds a read and write to the given 8-bit value at addr. Any existing
// mappings are replaced.
func (m *Memory) MapRW(addr int, b *uint8) {
m.MapRO(addr, b)
m.MapWO(addr, b)
}
// MapRO adds a read mapping to the given 8-bit value at addr. If there is
// already a read mapping, it is replaced. Write mappings are not altered.
func (m *Memory) MapRO(addr int, b *uint8) {
m.read[addr] = func() uint8 { return *b }
}
// MapWO adds a write mapping to the given 8-bit value at addr. If there is
// already a write mapping, it is replaced. Read mappings are not altered.
func (m *Memory) MapWO(addr int, b *uint8) {
m.write[addr] = func(v uint8) { *b = v }
}
// MapLoad adds a read mapping to the given function. When this address is
// read from, the function is invoked to get the value. If there is already a
// read mapping for this address, it is replaced. Write mappings are not
// altered.
func (m *Memory) MapLoad(addr int, load Load8) {
m.read[addr] = load
}
// MapStore adds a write mapping to the given function. When this address is
// written to, the function is invoked with the value to write. If there
// is already a write mapping for this address, it is replaced. Read mappings
// are not altered.
func (m *Memory) MapStore(addr int, store Store8) {
m.write[addr] = store
}
// Map maps the contents of another memory to this memory at the starting
// address. This copies the bindings in the other memory at call time;
// later updates to the map of the other memory will not be seen in this
// memory.
func (m *Memory) Map(startAddr int, m1 *Memory) {
endAddr := startAddr + len(m1.read)
for i, addr := 0, startAddr; addr < endAddr; i, addr = i+1, addr+1 {
m.read[addr] = m1.read[i]
m.write[addr] = m1.write[i]
}
}
// Unmap removes the read and write mappings at the address.
func (m *Memory) Unmap(addr int) {
m.read[addr] = nil
m.write[addr] = nil
}
// MapNil creates an empty read and write mapping at the address.
func (m *Memory) MapNil(addr int) {
m.read[addr] = func() uint8 { return 0 }
m.write[addr] = func(uint8) {}
}
// WatchRO creates a read watch on the address. When a value is read to that
// address, a MemoryEvent is sent to the Callback function.
func (m *Memory) WatchRO(addr int) {
if m.preads[m.bank][addr] != nil {
return
}
prev := m.reads[m.bank][addr]
m.read[addr] = func() uint8 {
value := prev()
m.Callback(MemoryEvent{
Read: true,
Bank: m.bank,
Addr: addr,
Value: value,
})
return value
}
m.preads[m.bank][addr] = prev
}
// WatchWO creates a write watch on the address. When a value is written to
// that address, a MemoryEvent is sent to the Callback function.
func (m *Memory) WatchWO(addr int) {
if m.pwrites[m.bank][addr] != nil {
return
}
prev := m.writes[m.bank][addr]
m.write[addr] = func(value uint8) {
prev(value)
m.Callback(MemoryEvent{
Read: false,
Bank: m.bank,
Addr: addr,
Value: value,
})
}
m.pwrites[m.bank][addr] = prev
}
// WatchRW creats a read and write watch on the address. When a value is
// read from or written to that address, a MemorYEvent is sent to the
// Callback function.
func (m *Memory) WatchRW(addr int) {
m.WatchRO(addr)
m.WatchWO(addr)
}
// Unwatch removes read nad write watches on the address.
func (m *Memory) Unwatch(addr int) {
if prev := m.pwrites[m.bank][addr]; prev != nil {
m.write[addr] = prev
m.pwrites[m.bank][addr] = nil
}
if prev := m.preads[m.bank][addr]; prev != nil {
m.read[addr] = prev
m.preads[m.bank][addr] = nil
}
}
// Bank returns the number of the selected bank. Banks are numbered starting
// with zero.
func (m *Memory) Bank() int {
return m.bank
}
// SetBank changes the selected bank. Banks are numbered starting with zero.
func (m *Memory) SetBank(bank int) {
m.bank = bank
m.read = m.reads[bank]
m.write = m.writes[bank]
}
// Pointer points to a location in memory.
type Pointer struct {
addr int // Current position.
Mask int // Address mask
Mem *Memory // Memory view
}
// NewPointer creates pointer at address zero on the provided memory.
func NewPointer(mem *Memory) *Pointer {
return &Pointer{Mem: mem, Mask: 0xffff}
}
func (p *Pointer) Addr() int {
return p.addr
}
func (p *Pointer) SetAddr(addr int) {
p.addr = addr & p.Mask
}
// Fetch returns the byte at current position as an 8-bit value and advances
// the pointer by one.
func (p *Pointer) Fetch() uint8 {
value := p.Mem.Read(p.addr)
p.addr = (p.addr + 1) & p.Mask
return value
}
// Peek returns the byte at the current position as an 8-bit value. The
// pointer is not moved.
func (p *Pointer) Peek() uint8 {
return p.Mem.Read(p.addr)
}
// FetchLE returns the next two bytes as a 16-bit value stored in little
// endian format and advances the pointer by two.
func (p *Pointer) FetchLE() int {
lo := int(p.Fetch())
hi := int(p.Fetch())
return hi<<8 + lo
}
// Put sets the value at the current address and advances the pointer by one.
func (p *Pointer) Put(value uint8) {
p.Mem.Write(p.addr, value)
p.addr = (p.addr + 1) & p.Mask
}
// PutN calls Put for each value.
func (p *Pointer) PutN(values ...uint8) {
for _, value := range values {
p.Put(value)
}
}
/*
ROM represents a dump of read-only memory data found on disk.
To load in a set of ROMs, define a slice of ROM definitions each with
the "name" of the ROM, the filename found on disk, and its SHA1
checksum as a string of hexadecimal values.
roms := []rcs.ROM{
NewROM("code" ", "chip1.rom ", "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
NewROM("code ", "chip2.rom ", "342d21fb707e89a6d407117c810795abcc481c52"),
NewROM("sprites", "chip3az.rom", "38dc6d5fe1a085f2e885748a00fbe5b7b3b8b1a6"),
}
Then call LoadROMs to return a map of names to byte slices:
data, err := LoadROMs("/path/to/roms", roms)
*/
type ROM struct {
Name string
File string
Checksum string
}
// NewROM creates a new ROM definition.
func NewROM(name string, file string, checksum string) ROM {
return ROM{
Name: strings.TrimSpace(name),
File: strings.TrimSpace(file),
Checksum: checksum,
}
}
var readFile = ioutil.ReadFile
/*
LoadROMs loads all ROMs in the given slice of definitions. An attempt will be
made to load each ROM. Any errors encountered during the attempts are be
combined into a single error.
The returned map will contain a byte slice for each ROM identified by its name.
ROMS that are given the same name are concatenated together. Extra whitespace
found at the beginning or ending of the name or filename are removed and
is useful for aligning the ROM definitions in the source code.
*/
func LoadROMs(dir string, roms []ROM) (map[string][]byte, error) {
buffers := make(map[string]bytes.Buffer)
e := make([]string, 0, 0)
for _, rom := range roms {
buf := buffers[rom.Name]
path := filepath.Join(dir, rom.File)
data, err := readFile(path)
if err != nil {
e = append(e, err.Error())
continue
}
checksum := fmt.Sprintf("%040x", sha1.Sum(data))
if checksum != rom.Checksum {
e = append(e, fmt.Sprintf("%v: invalid checksum", path))
continue
}
buf.Write(data)
buffers[rom.Name] = buf
}
if len(e) > 0 {
return nil, errors.New(strings.Join(e, "\n"))
}
chunks := make(map[string][]byte)
for name, buf := range buffers {
chunks[name] = buf.Bytes()
}
return chunks, nil
}