-
Notifications
You must be signed in to change notification settings - Fork 19
/
memory.go
289 lines (251 loc) · 9.18 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
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package memory
import (
"fmt"
"github.com/jetsetilly/gopher2600/environment"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
"github.com/jetsetilly/gopher2600/hardware/memory/cpubus"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
)
// DebugBus defines the meta-operations for all memory areas. Think of these
// functions as "debugging" functions, that is operations outside of the normal
// operation of the machine.
type DebugBus interface {
Read(address uint16) (uint8, uint8, error)
Write(address uint16, data uint8) error
Peek(address uint16) (uint8, error)
Poke(address uint16, value uint8) error
}
// Note that in many cases poking a register will not have the effect you might
// imagine. It is often better, therefore, to affect a "field" rather than a
// single address. This is because poking doesn't change the state of the
// hardware that leads to the value that is eventually put into the register.
//
// For hardware components where this is important the functions PeekField()
// and PokeField() are provided.
//
// The field argument and value type is component specific. The allowed values
// and types for each field will be provided in the documentation of the
// DebugFieldBus implemention.
//
// Note that unlike the functions in the DebugBus interface, these functions
// will not return an error. The functions should panic on any unexpected
// error.
type FieldBus interface {
PeekField(field string) interface{}
PokeField(field string, value interface{})
}
// Memory is the monolithic representation of the memory in 2600.
type Memory struct {
env *environment.Environment
RIOT *vcs.RIOTMemory
TIA *vcs.TIAMemory
RAM *vcs.RAM
Cart *cartridge.Cartridge
// the following are only used by the debugging interface
//
// . a note of the last literal memory address to be accessed
// . as above but the mapped address
// . the value that was written/read from the last address accessed
// . whether the last address accessed was written or read
//
// * the literal address is the address as it appears in the 6507 program.
// and therefore it might be more than 13 bits wide. as such it is not
// representative of what happens on the address bus
//
// it is sometimes useful to know what the literal address is, distinct
// from the mapped address, for debugging purposes.
LastCPUAddressLiteral uint16
LastCPUAddressMapped uint16
LastCPUData uint8
LastCPUWrite bool
// the actual values that have been put on the address and data buses.
AddressBus uint16
DataBus uint8
// not all pins of the databus are driven at all times. bits set in
// the DataBusDriven field indicate the pins that are being driven
DataBusDriven uint8
}
// NewMemory is the preferred method of initialisation for Memory.
func NewMemory(env *environment.Environment) *Memory {
mem := &Memory{
env: env,
RIOT: vcs.NewRIOTMemory(env),
TIA: vcs.NewTIAMemory(env),
RAM: vcs.NewRAM(env),
Cart: cartridge.NewCartridge(env),
}
mem.Reset()
return mem
}
func (mem *Memory) String() string {
return fmt.Sprintf("Address: %016b [%04x] Data: %08b [%04x]", mem.AddressBus, mem.AddressBus, mem.DataBus, mem.DataBus)
}
// Snapshot creates a copy of the current memory state.
func (mem *Memory) Snapshot() *Memory {
n := *mem
n.RIOT = mem.RIOT.Snapshot()
n.TIA = mem.TIA.Snapshot()
n.RAM = mem.RAM.Snapshot()
n.Cart = mem.Cart.Snapshot()
return &n
}
// Plumb makes sure everything is ship-shape after a rewind event.
//
// The fromDifferentEmulation indicates that the State has been created by a
// different VCS emulation than the one being plumbed into.
func (mem *Memory) Plumb(env *environment.Environment, fromDifferentEmulation bool) {
mem.env = env
mem.Cart.Plumb(env, fromDifferentEmulation)
}
// Reset contents of memory.
func (mem *Memory) Reset() {
mem.RIOT.Reset()
mem.TIA.Reset()
mem.RAM.Reset()
mem.Cart.Reset()
}
// GetArea returns the actual memory of the specified area type.
func (mem *Memory) GetArea(area memorymap.Area) DebugBus {
switch area {
case memorymap.TIA:
return mem.TIA
case memorymap.RAM:
return mem.RAM
case memorymap.RIOT:
return mem.RIOT
case memorymap.Cartridge:
return mem.Cart
}
panic("memory areas are not mapped correctly")
}
// Readt is an implementation of CPUBus. Address will be normalised and processed by the correct
// memory areas.
func (mem *Memory) Read(address uint16) (uint8, error) {
var err error
// the address bus value is the literal address masked to the 13 bits
// available to the 6507
addressBus := address & memorymap.Memtop
ma, ar := memorymap.MapAddress(addressBus, true)
area := mem.GetArea(ar)
// update address bus if it has changed
if mem.AddressBus != addressBus {
mem.AddressBus = addressBus
// if the address bus has changed then we indicate that to the cartridge
//
// note that at this point mem.DataBus has not yet been updated as a
// result of the read access, so we are effectively calling the function
// with the "old" data bus
mem.Cart.AccessPassive(mem.AddressBus, mem.DataBus)
}
// if the area is cartridge then we need to consider what happens to
// cartridge addresses that are volatile. ie. RAM locations and "hotspots".
//
// what is happening here is probably not an intentional act by the 6507
// program but it still needs to be accounted for
//
// note that we're using the previous value on the databus. this is because
// the 6507 is not driving the data bus
if ar == memorymap.Cartridge {
err = mem.Cart.Write(mem.AddressBus, mem.DataBus)
}
// read data from area. if there is an error, we can just ignore it until
// we get to the end of the function
var data uint8
data, mem.DataBusDriven, err = area.Read(ma)
// the data bus is not always completely driven. ie. some pins are not powered and are left
// floating
//
// a good example of this are the TIA addresses. see commentary for TIADriverPins for extensive
// explanation
if mem.DataBusDriven != 0xff {
// on a real superchip the pins are more indeterminate. for now,
// applying an addition random pattern is a good enough emulation for this
if mem.env != nil && mem.env.Prefs.RandomPins.Get().(bool) {
data |= uint8(mem.env.Random.Rewindable(0xff)) & (^mem.DataBusDriven)
} else {
// this pattern is good for replicating what we see on the pluscart
// this matches observations made by Al_Nafuur with the following
// binary:
//
// https://atariage.com/forums/topic/329888-indexed-read-page-crossing-and-sc-ram/
//
// a different bit pattern can be seen on the Harmony
//
// https://atariage.com/forums/topic/285759-stella-getting-into-details-help-wanted/
data |= mem.LastCPUData & ^mem.DataBusDriven
}
}
// we also need to consider what happens when a cartridge is forcefully
// driving the data bus
if stuff, ok := mem.Cart.BusStuff(); ok {
data = stuff
}
// update data bus
mem.DataBus = data
// update debugging information
mem.LastCPUAddressLiteral = address
mem.LastCPUAddressMapped = ma
mem.LastCPUWrite = false
mem.LastCPUData = data
return data, err
}
// Write is an implementation of CPUBus. Address will be normalised and processed by the correct
// memory areas.
func (mem *Memory) Write(address uint16, data uint8) error {
// the address bus value is the literal address masked to the 13 bits
// available to the 6507
addressBus := address & memorymap.Memtop
ma, ar := memorymap.MapAddress(addressBus, false)
area := mem.GetArea(ar)
// drive pins from cartridge
if stuff, ok := mem.Cart.BusStuff(); ok {
data = stuff
}
// update data bus
mem.DataBus = data
// service changes to address bus
if addressBus != mem.AddressBus {
mem.AddressBus = addressBus
err := mem.Cart.AccessPassive(mem.AddressBus, mem.DataBus)
if err != nil {
return err
}
}
// update debugging information
mem.LastCPUAddressLiteral = address
mem.LastCPUAddressMapped = ma
mem.LastCPUWrite = true
mem.LastCPUData = data
return area.Write(ma, data)
}
// Peek implements the DebugBus interface.
func (mem *Memory) Peek(address uint16) (uint8, error) {
ma, ar := memorymap.MapAddress(address, true)
if area, ok := mem.GetArea(ar).(DebugBus); ok {
return area.Peek(ma)
}
return 0, fmt.Errorf("%w: %04x", cpubus.AddressError, address)
}
// Poke implements the DebugBus interface.
func (mem *Memory) Poke(address uint16, data uint8) error {
ma, ar := memorymap.MapAddress(address, true)
if area, ok := mem.GetArea(ar).(DebugBus); ok {
return area.Poke(ma, data)
}
return fmt.Errorf("%w: %04x", cpubus.AddressError, address)
}