/
main.go
352 lines (306 loc) · 9.56 KB
/
main.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
package main
import (
"flag"
"fmt"
"log"
"os"
"runtime"
"runtime/pprof"
"time"
)
func init() {
log.SetFlags(log.Lmicroseconds | log.LstdFlags)
}
func main() {
var (
cpuProfileFilePath = flag.String("cpuProfile", "", "write cpu profile to file")
memProfileFilePath = flag.String("memProfile", "", "write memory profile to file")
)
flag.Parse()
// CPU profile
if *cpuProfileFilePath != "" {
f, err := os.Create(*cpuProfileFilePath)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer func() { _ = f.Close() }() // error handling omitted intentionally
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
// Memory profile
if *memProfileFilePath != "" {
f, err := os.Create(*memProfileFilePath)
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer func() {
defer func() { _ = f.Close() }() // error handling omitted intentionally
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
}
}()
}
///////////////////////
// Start of the program
///////////////////////
var cpu CPU
AddressSpace := make(Memory, TotalMemorySize) // Allocate addressable memory space
// Load reset vector
AddressSpace[0xfffc] = 0x00 // lo byte address
AddressSpace[0xfffd] = 0x02 // hi byte address
// ROM program
RomBios := []Byte{
NOP.code, // No operation (pause 2 cycles)
// Test flag set/clear commands
SEC.code, // Set Carry flag
SEI.code, // Set InterruptDisabled flag
CLI.code, // Clear InterruptDisabled flag
CLC.code, // Clear Carry flag
// Test load into Accumulator
LDA.code, 0x42, // Load 0x42 -> A register (immediate mode)
// Test logical AND command
AND.code, 0x0, // AND value in A with 0x0 (side effect: set Zero flag)
// Pauses via NOP operations
NOP.code, // No operation (pause 2 cycles)
NOP.code, // No operation (pause 2 cycles)
NOP.code, // No operation (pause 2 cycles)
HLT.code, // Halt CPU
}
// Load program at addr 0x0200
copy(AddressSpace[0x0200:], RomBios)
// Reset processor
cpu.Reset()
cpu.DumpState()
// Start Fetch/Execute loop
cpu.FetchAndExecuteLoop(AddressSpace)
}
type Byte uint8
type Word uint16
const (
TotalMemorySize = 1 << 16 // Memory size: 64K 8-bit bytes
CycleTick = 250 * time.Millisecond // CPU frequency: 4Hz
//CycleTick = 1 * time.Microsecond // CPU frequency: 1MHz
)
type Memory = []Byte
type CPU struct {
cycleCounter uint64
// Control registers set
PC Word // Program counter
SP Word // Stack pointer
Flags Word // Status flags
// User register set
RegA Byte // Accumulator
RegX Byte // Index
RegY Byte // Output
}
func (cpu *CPU) FetchAndExecuteLoop(mem Memory) {
// Use fetch addr of the ROM program
lo := cpu.FetchNextInstruction(mem)
cpu.DumpState()
hi := cpu.FetchNextInstruction(mem)
cpu.DumpState()
// Start executing ROM program
cpu.PC = (Word(hi) << 8) | Word(lo)
log.Printf("Set PC to ROM address: 0x%04X", cpu.PC)
// Execute forever
for {
mCode := cpu.FetchNextInstruction(mem) // Get machine code
cmd := cpu.Decode(mCode) // Decode machine code instruction
cpu.Eval(cmd, mem) // Evaluate decoded instruction
cpu.DumpState() // Display CPU state
}
}
func (cpu *CPU) fetchByte(mem Memory, addr Word) Byte {
// Increment cycle count
cpu.cycleCounter++
time.Sleep(1 * CycleTick)
val := mem[int(addr)]
log.Printf("Fetched 0x%02X from 0x%04X [1 cycle]", val, addr)
return val
}
func (cpu *CPU) FetchNextInstruction(mem Memory) Byte {
instr := cpu.fetchByte(mem, cpu.PC)
cpu.PC++
return instr
}
func (cpu *CPU) Decode(instr Byte) OpCode {
// Use look-up table to figure out OpCode based on the instruction
if op, ok := ISATable[instr]; ok {
log.Printf("Decoded instruction 0x%02X as %v", instr, op.mnemonic)
return op
}
log.Printf("Failed to decode instruction: 0x%02X", instr)
return HLT
}
func (cpu *CPU) Eval(op OpCode, mem Memory) {
// Load and execute microcode
op.microcode(cpu, mem, op)
}
func (cpu *CPU) Reset() {
// Clear flags first
cpu.Flags = 0
// Set all general purpose registers to Zero (0)
cpu.RegX, cpu.RegY = 0, 0
cpu.setRegA(0)
// Set stack pointer
cpu.SP = 0x01ff
// Set address for the reset vector
cpu.PC = 0xfffc
}
func (cpu *CPU) setRegA(val Byte) {
cpu.RegA = val
if cpu.RegA == 0 {
cpu.SetFlag(Zero)
} else {
cpu.ClearFlag(Zero)
}
}
//goland:noinspection GoUnhandledErrorResult
func (cpu CPU) DumpState() {
clearScreen() // clear screen
fmt.Fprintf(os.Stderr, "===============================\n")
fmt.Fprintf(os.Stderr, "== CPU STATE ==\n")
fmt.Fprintf(os.Stderr, "== (0x%08X cycles) ==\n", cpu.cycleCounter)
fmt.Fprintf(os.Stderr, "===============================\n")
fmt.Fprintf(os.Stderr, "== Flags: C|Z|I|D|B|O|N ==\n")
fmt.Fprintf(os.Stderr, "== %c|%c|%c|%c|%c|%c|%c ==\n",
cpu.checkFlag(Carry), cpu.checkFlag(Zero), cpu.checkFlag(InterruptDisabled),
cpu.checkFlag(DecimalMode), cpu.checkFlag(BreakCommand), cpu.checkFlag(Overflow),
cpu.checkFlag(Negative))
fmt.Fprintf(os.Stderr, "== PC: 0x%04X ==\n", cpu.PC)
fmt.Fprintf(os.Stderr, "== SP: 0x%04X ==\n", cpu.SP)
fmt.Fprintf(os.Stderr, "== A: 0x%04X ==\n", cpu.RegA)
fmt.Fprintf(os.Stderr, "== X: 0x%04X ==\n", cpu.RegX)
fmt.Fprintf(os.Stderr, "== Y: 0x%04X ==\n", cpu.RegY)
fmt.Fprintf(os.Stderr, "===============================\n")
}
// clearScreen - clear screens using terminal commands.
func clearScreen() (int, error) {
return fmt.Print("\033[H\033[2J")
}
func (cpu CPU) checkFlag(flag Word) rune {
if (flag & cpu.Flags) > 0 {
return '◉'
}
return '○'
}
func (cpu *CPU) SetFlag(flag Word) {
cpu.Flags |= flag
}
func (cpu *CPU) ClearFlag(flag Word) {
cpu.Flags &^= flag
}
const (
Carry = 1 << iota
Zero
InterruptDisabled
DecimalMode
BreakCommand
Overflow
Negative
)
type OpCode struct {
code Byte
mode AddrMode
size Byte
cycles uint64
mnemonic string
microcode func(*CPU, Memory, OpCode)
}
type AddrMode int
const (
Implied AddrMode = iota
//Implicit
//Accumulator
Immediate
//ZeroPage
//ZeroPageX
//ZeroPageY
//Relative
//Absolute
//AbsoluteX
//AbsoluteY
//Indirect
//IndexedIndirect
//IndirectIndexed
)
// Known instructions
var (
HLT = OpCode{code: 0x00, mode: Implied, size: 1, cycles: 0, mnemonic: "HLT", microcode: func(_ *CPU, _ Memory, _ OpCode) {
log.Fatal("Halting CPU")
}}
NOP = OpCode{code: 0xea, mode: Implied, size: 1, cycles: 2, mnemonic: "NOP", microcode: func(cpu *CPU, mem Memory, op OpCode) {
cpu.cycleCounter += op.cycles
time.Sleep(time.Duration(op.cycles) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
// Carry flag ops
CLC = OpCode{code: 0x18, mode: Implied, size: 1, cycles: 2, mnemonic: "CLC", microcode: func(cpu *CPU, memory Memory, op OpCode) {
cpu.cycleCounter += op.cycles
cpu.ClearFlag(Carry)
time.Sleep(time.Duration(op.cycles) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
SEC = OpCode{code: 0x38, mode: Implied, size: 1, cycles: 2, mnemonic: "SEC", microcode: func(cpu *CPU, memory Memory, op OpCode) {
cpu.cycleCounter += op.cycles
cpu.SetFlag(Carry)
time.Sleep(time.Duration(op.cycles) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
// Interrupt Disable flag ops
CLI = OpCode{code: 0x58, mode: Implied, size: 1, cycles: 2, mnemonic: "CLI", microcode: func(cpu *CPU, memory Memory, op OpCode) {
cpu.cycleCounter += op.cycles
cpu.ClearFlag(InterruptDisabled)
time.Sleep(time.Duration(op.cycles) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
SEI = OpCode{code: 0x78, mode: Implied, size: 1, cycles: 2, mnemonic: "SEI", microcode: func(cpu *CPU, memory Memory, op OpCode) {
cpu.cycleCounter += op.cycles
cpu.SetFlag(InterruptDisabled)
time.Sleep(time.Duration(op.cycles) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
// LDA -- Immediate
LDA = OpCode{code: 0xa9, mode: Immediate, size: 2, cycles: 2, mnemonic: "LDA", microcode: func(cpu *CPU, memory Memory, op OpCode) {
// Load immediate value into A
cpu.setRegA(cpu.FetchNextInstruction(memory))
// Account for the rest of the cycles
delta := op.cycles - 1 // compensate for the memory fetch operation
cpu.cycleCounter += delta
time.Sleep(time.Duration(delta) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
// AND -- Immediate
AND = OpCode{code: 0x29, mode: Immediate, size: 2, cycles: 2, mnemonic: "AND", microcode: func(cpu *CPU, memory Memory, op OpCode) {
// AND immediate operand with content of A
cpu.setRegA(cpu.RegA & cpu.FetchNextInstruction(memory))
// Account for the rest of the cycles
delta := op.cycles - 1 // compensate for the memory fetch operation
cpu.cycleCounter += delta
time.Sleep(time.Duration(delta) * CycleTick)
log.Printf("Executed %v [%s]", op.mnemonic, plural(int(op.cycles), "cycle"))
}}
)
// ISATable - Instruction Set Architecture (ISA) lookup table
var ISATable = map[Byte]OpCode{
HLT.code: HLT,
NOP.code: NOP,
CLC.code: CLC,
SEC.code: SEC,
CLI.code: CLI,
SEI.code: SEI,
// LDA
LDA.code: LDA,
// AND
AND.code: AND,
}
func plural(n int, name string) string {
suf := ""
if n != 1 {
suf = "s"
}
return fmt.Sprintf("%d %s%s", n, name, suf)
}