-
Notifications
You must be signed in to change notification settings - Fork 20
/
cycles.go
281 lines (239 loc) · 7.92 KB
/
cycles.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
// 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 arm
import (
"strings"
)
type cycleOrder struct {
queue [20]cycleType
idx int
}
func (q cycleOrder) String() string {
s := strings.Builder{}
for i := 0; i < q.idx; i++ {
s.WriteRune(rune(q.queue[i]))
s.WriteRune('+')
}
return strings.TrimRight(s.String(), "+")
}
func (q *cycleOrder) reset() {
q.idx = 0
}
func (q cycleOrder) len() int {
return q.idx
}
func (q *cycleOrder) add(c cycleType) {
q.queue[q.idx] = c
q.idx++
}
// BranchTrail indicates how the BrainTrail buffer was used for a cycle.
type BranchTrail int
// List of valid BranchTrail values.
const (
BranchTrailNotUsed BranchTrail = iota
BranchTrailUsed
BranchTrailFlushed
)
// the bus activity during a cycle.
type busAccess int
const (
prefetch busAccess = iota
branch
dataRead
dataWrite
)
// is bus access an instruction or data read. equivalent in ARM terms, to
// asking if Prot0 is 0 or 1.
func (bt busAccess) isDataAccess() bool {
return bt == dataRead || bt == dataWrite
}
// the type of cycle being executed.
type cycleType rune
const (
N cycleType = 'N'
I cycleType = 'I'
S cycleType = 'S'
)
// returns false if address isn't latched. this means theat the bus access is
// subject to latency.
//
// dows not handle the decision about whether the MAM latches should be
// checked. for example, if MAMCR is zero than don't call this function at all.
// see Scycle() and Ncycle() for those decisions.
func (arm *ARM) isLatched(cycle cycleType, bus busAccess, addr uint32) bool {
latch := addr & 0xffffff80
switch bus {
case prefetch:
if latch == arm.state.mam.prefectchLatch {
return true
}
arm.state.mam.prefectchLatch = latch
// we'll assume MAMTIM is set adequately
if cycle == S && !arm.state.mam.prefectchAborted {
return true
}
case branch:
if latch == arm.state.mam.branchLatch {
arm.state.branchTrail = BranchTrailUsed
return true
}
arm.state.mam.branchLatch = latch
arm.state.branchTrail = BranchTrailFlushed
case dataRead:
if latch == arm.state.mam.dataLatch {
return true
}
arm.state.mam.dataLatch = latch
case dataWrite:
// invalidate data latch
arm.state.mam.dataLatch = 0x0
}
return false
}
func (arm *ARM) iCycle() {
if arm.disasm != nil {
arm.state.cycleOrder.add(I)
}
arm.state.stretchedCycles++
arm.state.lastCycle = I
arm.state.mam.prefectchAborted = false
}
func (arm *ARM) sCycle(bus busAccess, addr uint32) {
arm.state.mam.prefectchAborted = bus.isDataAccess()
// "Merged I-S cycles
// Where possible, the ARM7TDMI-S processor performs an optimization on the bus to
// allow extra time for memory decode. When this happens, the address of the next
// memory cycle is broadcast during an internal cycle on this bus. This enables the
// memory controller to decode the address, but it must not initiate a memory access
// during this cycle. In a merged I-S cycle, the next cycle is a sequential cycle to the same
// memory location. This commits to the access, and the memory controller must initiate
// the memory access. This is shown in Figure 3-5 on page 3-9."
//
// page 3-8 of the "ARM7TDMI-S Technical Reference Manual r4p3"
if arm.state.lastCycle == I {
arm.state.stretchedCycles--
arm.state.mergedIS = true
}
if arm.disasm != nil {
arm.state.cycleOrder.add(S)
}
arm.state.lastCycle = S
if !arm.mmap.IsFlash(addr) {
arm.state.stretchedCycles++
return
}
switch arm.state.mam.mamcr {
default:
arm.state.stretchedCycles += arm.clklenFlash
case 0:
arm.state.stretchedCycles += arm.clklenFlash
case 1:
// for MAM-1, we go to flash memory only if it's a program access (ie. not a data access)
if bus.isDataAccess() {
arm.state.stretchedCycles += arm.clklenFlash
} else if arm.isLatched(S, bus, addr) {
arm.state.stretchedCycles++
} else {
arm.state.stretchedCycles += arm.clklenFlash
}
case 2:
if arm.isLatched(S, bus, addr) {
arm.state.stretchedCycles++
} else {
arm.state.stretchedCycles += arm.clklenFlash
}
}
}
func (arm *ARM) nCycle(bus busAccess, addr uint32) {
arm.state.mam.prefectchAborted = bus.isDataAccess()
// "3.3.1 Nonsequential cycles" in "ARM7TDMI-S Technical Reference Manual r4p3"
//
// "It is not uncommon for a memory system to require a longer access time
// (extending the clock cycle) for nonsequential accesses. This is to allow
// time for full address decoding or to latch both a row and column address
// into DRAM."
mclkFlash := 1.0
mclkNonFlash := 1.0
// "3.3.1 Nonsequential cycles" in "ARM7TDMI-S Technical Reference Manual r4p3"
//
// "The ARM7TDMI-S processor can perform back to back nonsequential memory cycles.
// This happens, for example, when an STR instruction is executed, as shown in Figure 3-3.
// If you are designing a memory controller for the ARM7TDMI-S processor, and your
// memory system is unable to cope with this case, you must use the CLKEN signal to
// extend the bus cycle to allow sufficient cycles for the memory system."
if arm.state.lastCycle == N {
mclkFlash = 1.3
mclkNonFlash = 1.8
}
// the use of a fractional number for MCLK modulation is at odds with the
// stretching required for flash access, which is a whole number. again, it
// isn't clear if this is possible but again, the technical reference
// points to the possibility of a difference. to be specific, there are two
// methods of stretching access times: MCLK modulation and the use of nWait
// to control bus cycles.
//
// on page 3-29 of r4p1 (but not in the equivalent section of r4p3,
// curiously), nWait is described as allowing bus cycles to be extended in
// "increments of complete MCLK cycles". MLCK itself meanwhile, is
// described as being free-running. while not conclusive, this to me
// suggests the modulation can be fractional.
if arm.disasm != nil {
arm.state.cycleOrder.add(N)
}
arm.state.lastCycle = N
if !arm.mmap.IsFlash(addr) {
arm.state.stretchedCycles += float32(mclkNonFlash)
return
}
switch arm.state.mam.mamcr {
default:
arm.state.stretchedCycles += arm.clklenFlash * float32(mclkFlash)
case 0:
arm.state.stretchedCycles += arm.clklenFlash * float32(mclkFlash)
case 1:
arm.state.stretchedCycles += arm.clklenFlash * float32(mclkFlash)
case 2:
if arm.isLatched(N, bus, addr) {
arm.state.stretchedCycles += float32(mclkNonFlash)
} else {
arm.state.stretchedCycles += arm.clklenFlash * float32(mclkFlash)
}
}
}
// called whenever PC changes unexpectedly (by a branch instruction for example).
func (arm *ARM) fillPipeline() {
arm.Ncycle(branch, arm.state.registers[rPC])
arm.Scycle(prefetch, arm.state.registers[rPC]+2)
}
// the cycle profile for store register type instructions is funky enough to
// need a specialist function.
func (arm *ARM) storeRegisterCycles(addr uint32) {
arm.Ncycle(dataWrite, addr)
arm.state.prefetchCycle = N
}
// add cycles accumulated during an BX to ARM code instruction. this is
// definitely only an estimate.
func (arm *ARM) armInterruptCycles(i ARMinterruptReturn) {
// we'll assume all writes are to flash memory
arm.state.stretchedCycles += float32(i.NumMemAccess) * arm.clklenFlash
arm.state.stretchedCycles += float32(i.NumAdditionalCycles)
}
// stub function for when the execution doesn't require cycle counting
func (arm *ARM) iCycleStub() {
}
func (arm *ARM) sCycleStub(bus busAccess, addr uint32) {
}
func (arm *ARM) nCycleStub(bus busAccess, addr uint32) {
}