-
Notifications
You must be signed in to change notification settings - Fork 19
/
callfn.go
149 lines (130 loc) · 5.12 KB
/
callfn.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
// 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 Callfn facilitates the ARM CALLFN process common to both DPC+ and
// CDF* cartridge mappers. It does not handle the ARM itself and cartridge
// mappers that use it should take care in particular to Run() and Step() the
// ARM when required.
package callfn
import (
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/harmony/arm7tdmi"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
)
// CallFn keeps track of the CallFn process common to both DPC+ and CDF*
// cartridge mappers.
type CallFn struct {
// number of outstanding arm cycles
remainingCycles float32
// on ARM program conclusion we JMP to the address after the CALLFN
ResumeAddr uint16
// the number of remaining bytes to push during resume sequence
resumeCount int
// phantom reads happen all the time but we don't normally care about them.
// the only place where it matters is at the moment the ARM processor is
// finishing.
//
// the problem is caused by the conclusion of the ARM program not being
// predictable (at least not the way we're doing it). this means it's
// possible that it finishes sometime between the placeholder NOP and the
// phantom read connected with that NOP (the phantom read always happens).
//
// what we don't want to happen is to send the JMP opcode in response to
// the phantom read. so, to prevent that we toggle the phantomOnResume
// flag on every read during the period the ARM is executing. then, at the
// precise moment the ARM processor is finishing (ie. armRemainingCyles <=
// 0 AND resumeCount > 0) we can discard the first read event if we're
// expecting a phantom read. for this to work correctly we should reset the
// flag when beginning the CALLFN process.
//
// this feels like a special condition but it's really just information
// about the state of the ARM that we aren't able to put anywhere else.
phantomOnResume bool
}
// IsActive returns true if ARM program is still running.
func (cf *CallFn) IsActive() bool {
return cf.remainingCycles > 0
}
const (
jmpAbsolute = 0x4c
nop = 0xea
)
// Check state of CallFn. Returns true if it is active and false if it is not.
// If CallFn is active then the the value to put on the data bus is also
// returned. If CallFn is not active then the data bus value should be
// determined in the normal way (most probably by reading the cartridge ROM).
func (cf *CallFn) Check(addr uint16) (uint8, bool) {
if cf.IsActive() {
cf.phantomOnResume = !cf.phantomOnResume
return nop, true
}
switch cf.resumeCount {
case 3:
if !cf.phantomOnResume {
cf.resumeCount--
}
cf.phantomOnResume = !cf.phantomOnResume
return jmpAbsolute, true
case 2:
cf.resumeCount--
return uint8(cf.ResumeAddr & 0x00ff), true
case 1:
cf.resumeCount--
return uint8(cf.ResumeAddr >> 8), true
}
// resume address after a CALLFN is the last read address (which will be
// the address at which the CALLFN trigger was read) plus one. we also need
// to add the cartridge bits because addresses are normalised by the
// cartridge layer before passing to the mappers.
//
// the problem with this is that the cartridge mirror specified by the
// address may be "wrong". not a problem from an execution point of view
// but it might seem odd to someone monitoring closely in the debugger.
cf.ResumeAddr = (addr | memorymap.OriginCartFxxxMirror) + 1
return 0, false
}
// Start the CallFn process.
func (cf *CallFn) Start(cycles float32) {
// if the number of cycles is zero then the ARM program didn't take any
// time at all and there is no need to account for the phantom reads.
// return immediately
if cycles == 0 {
return
}
// we are no longer capping the number of cycles executed here. this is now
// done entirely within in the arm7tdmi package.
//
// capping cycles here meant that the ARM program ran to completion, which
// is not correct because that means the ARM memory is updated as though the
// cap did not exist.
cf.remainingCycles = cycles
cf.resumeCount = 3
cf.phantomOnResume = false
}
// Step forward one clock. Returns true if the ARM program is running and false
// otherwise.
func (cf *CallFn) Step(immediate bool, vcsClock float32) bool {
if immediate {
cf.remainingCycles = 0
return false
}
// number of arm cycles consumed for every VCS cycle. assuming processor
// runs at 70Mhz for now.
armCycles := float32(arm7tdmi.Clk / vcsClock)
if cf.remainingCycles <= armCycles {
cf.remainingCycles = 0
return false
}
cf.remainingCycles -= armCycles
return true
}