467 lines
9.8 KiB
Go
467 lines
9.8 KiB
Go
// Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type word uint32
|
|
const wordbits = 32
|
|
const wordbytes = 4
|
|
const wordmask = 0xFFFFFFFF
|
|
const hsbmask = 0x80000000
|
|
const estackDepth = 64
|
|
type CPU struct {
|
|
ESP word;
|
|
X word;
|
|
PC, FP,BP,RP word;
|
|
IR,IV word;
|
|
|
|
estack [estackDepth] word;
|
|
|
|
mem *Mem;
|
|
|
|
stopped bool
|
|
trace bool
|
|
singlestep bool
|
|
}
|
|
|
|
func sign_extend(bits word, wordbits int) int {
|
|
signmask := word(1 << (wordbits - 1))
|
|
signbit := (bits & signmask) != 0
|
|
// fmt.Printf("sign_extend %b %v signmask %08X signbit %v\n", bits, wordbits, signmask, signbit)
|
|
if signbit {
|
|
return int(bits & ^signmask) - int(signmask)
|
|
} else {
|
|
return int(bits)
|
|
}
|
|
}
|
|
|
|
func (c *CPU) initialize() {
|
|
c.ESP = 0
|
|
c.PC = 0
|
|
c.X = 0
|
|
c.FP = 65020 // these are the values set by the ROM monitor
|
|
c.RP = 65024 // for FP and RP
|
|
|
|
c.singlestep = false
|
|
}
|
|
|
|
func (c *CPU) printEstack() {
|
|
fmt.Printf("[")
|
|
for i := word(1); i <= c.ESP; i++ {
|
|
fmt.Printf(" %08X", c.estack[i])
|
|
}
|
|
fmt.Printf(" %08X ] (%v)\n", c.X, c.ESP)
|
|
}
|
|
|
|
func (c *CPU) showStep(desc string, operand int, opWidth int) {
|
|
if !c.trace { return }
|
|
|
|
var opStr string
|
|
if opWidth == 0 {
|
|
opStr = ""
|
|
} else {
|
|
opStr = fmt.Sprintf(" %04X", operand)
|
|
}
|
|
|
|
fmt.Printf("%08x %-10s%5s ", c.PC, desc, opStr)
|
|
c.printEstack()
|
|
}
|
|
|
|
func (c *CPU) getOperand(insWord word, bits int) int {
|
|
return int(insWord & word((1 << bits) - 1))
|
|
}
|
|
|
|
func (c *CPU) getSignedOperand(insWord word, bits int) int {
|
|
bitValue := (1 << (bits - 1))
|
|
signBit := insWord & word(bitValue)
|
|
bitMask := (bitValue << 1) - 1
|
|
//fmt.Printf("getSignedOperand bitValue: %v, bitMask: %v\n", bitValue, bitMask)
|
|
o := int(insWord & word(bitMask))
|
|
if signBit != 0 {
|
|
o = int(o) - (bitValue << 1)
|
|
}
|
|
//fmt.Printf("getSignedOperand: %v %v -> %d\n",insWord, bits, o)
|
|
return o
|
|
}
|
|
|
|
func (c *CPU) getBit(insWord word, bitnum int) int {
|
|
return int(insWord >> bitnum) & 1
|
|
}
|
|
|
|
func (c *CPU) getBits(insWord word, mask word, shiftCount int) word {
|
|
return (insWord & mask) >> shiftCount
|
|
}
|
|
|
|
func (c *CPU) getSignedBits(insWord word, mask word, shiftCount int) int {
|
|
result := (insWord & mask) >> shiftCount
|
|
signMask := ((mask >> shiftCount) + 1) >> 1
|
|
//fmt.Printf("getSignedBits %016b signMask %v signBit %v\n", insWord, signMask, insWord & signMask)
|
|
if result & signMask != 0 {
|
|
return - int(result & ^signMask)
|
|
} else {
|
|
return int(result)
|
|
}
|
|
}
|
|
|
|
func (c * CPU) addToWord(v word, offset int) word {
|
|
if offset < 0 {
|
|
v -= word(-offset)
|
|
} else {
|
|
v += word(offset)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (c *CPU) step() error {
|
|
nPC := c.PC
|
|
nFP := c.FP
|
|
nBP := c.BP
|
|
nRP := c.RP
|
|
nX := c.X
|
|
nESP := c.ESP
|
|
|
|
Y := c.estack[c.ESP]
|
|
|
|
insWord, err := c.mem.read(c.PC)
|
|
if err != nil { return err }
|
|
if c.PC % 4 == 0 {
|
|
insWord = insWord >> 16
|
|
} else {
|
|
insWord = insWord & 0xFFFF
|
|
}
|
|
|
|
baseIns := insWord >> 13
|
|
|
|
nPC += 2
|
|
|
|
x2y := false
|
|
deltaESP := 0
|
|
|
|
oplen := 0
|
|
|
|
switch baseIns {
|
|
// BRANCH
|
|
case 0b000:
|
|
operand := c.getSignedOperand(insWord, 13)
|
|
|
|
c.showStep("BRANCH", operand, 13)
|
|
|
|
nPC = word(int(c.PC) + operand)
|
|
|
|
if operand == 0 {
|
|
fmt.Printf("BRANCH 0 encountered - stopped at PC %08X\n", nPC)
|
|
c.stopped = true
|
|
}
|
|
// ALU
|
|
case 0b001:
|
|
aluop := c.getBits(insWord, 0b0001111000000000, 9)
|
|
deltaESP = c.getSignedBits(insWord, 0b0000000000110000, 4)
|
|
ext := c.getBit(insWord, 7) != 0
|
|
x2y = c.getBit(insWord, 6) != 0
|
|
operand := c.getOperand(insWord, 4)
|
|
// fmt.Printf("aluop %v %v %v %v %v\n", aluop, s, ext, x2y, operand)
|
|
name := "ALU"
|
|
switch aluop {
|
|
case 0:
|
|
name = "ADD"
|
|
nX = c.X + Y
|
|
case 1:
|
|
name = "SUB"
|
|
nX = Y - c.X
|
|
case 2:
|
|
name = "NOT"
|
|
nX = ^ c.X
|
|
case 3:
|
|
name = "AND"
|
|
nX = c.X & Y
|
|
case 4:
|
|
name = "OR"
|
|
nX = c.X | Y
|
|
case 5:
|
|
name = "XOR"
|
|
nX = c.X ^ Y
|
|
case 6:
|
|
name = "CMP"
|
|
oplen = 2
|
|
cmp_i := c.getBit(insWord,2) != 0
|
|
cmp_eq := c.getBit(insWord,1) != 0
|
|
cmp_lt := c.getBit(insWord,0) != 0
|
|
s_x := sign_extend(c.X, wordbits)
|
|
s_y := sign_extend(Y, wordbits)
|
|
result := (cmp_eq && (s_x == s_y)) || (cmp_lt && (s_y < s_x))
|
|
if cmp_i { result = !result }
|
|
if result { nX = 1 } else { nX = 0 }
|
|
case 7:
|
|
name = "Y"
|
|
if !x2y && (deltaESP == -1) { name = "DROP" }
|
|
if x2y && (deltaESP == 0) { name = "SWAP" }
|
|
nX = Y
|
|
case 8:
|
|
name = "SHR"
|
|
nX = c.X >> 1
|
|
if ext {
|
|
nX = nX | (c.X & hsbmask)
|
|
}
|
|
case 9:
|
|
name = "SHL"
|
|
nX = c.X << 1
|
|
if operand & 2 != 0 {
|
|
nX = nX << 1
|
|
}
|
|
oplen = 2
|
|
case 10:
|
|
name = "INC"
|
|
oplen = 4
|
|
if (operand == 0) && (deltaESP == 1) && x2y { name = "DUP" }
|
|
nX = c.X + word(operand)
|
|
case 11:
|
|
name = "DEC"
|
|
oplen = 4
|
|
nX = c.X - word(operand)
|
|
case 12:
|
|
name = "CMPU"
|
|
oplen = 2
|
|
cmp_i := c.getBit(insWord,2) != 0
|
|
cmp_eq := c.getBit(insWord,1) != 0
|
|
cmp_lt := c.getBit(insWord,0) != 0
|
|
result := (cmp_eq && (c.X == Y)) || (cmp_lt && (Y < c.X))
|
|
if cmp_i { result = !result }
|
|
if result { nX = 1 } else { nX = 0 }
|
|
case 13:
|
|
name = "BPLC"
|
|
nX = (c.X & 0xFF) << ((3 - (Y & 3)) * 8)
|
|
case 14:
|
|
name = "BROT"
|
|
nX = ((c.X & 0x00FFFFFF) << 8 ) | ((c.X & 0xFF000000) >> 24)
|
|
case 15:
|
|
name = "BSEL"
|
|
shift := (3 - (Y & 3)) * 8
|
|
nX = (c.X >> shift) & 0xFF
|
|
}
|
|
c.showStep(name, operand, oplen)
|
|
// STORE
|
|
case 0b010:
|
|
operand := c.getOperand(insWord, 13)
|
|
var ea word
|
|
var name string
|
|
if (insWord & 1) == 1 {
|
|
name = "STORE.B"
|
|
ea = c.BP + word(operand)
|
|
} else {
|
|
name = "STORE"
|
|
ea = c.FP + word(operand)
|
|
}
|
|
|
|
c.showStep(name, operand, oplen)
|
|
|
|
err = c.mem.write(c.X, ea)
|
|
if err != nil { return err }
|
|
|
|
deltaESP = -1
|
|
nX = Y
|
|
// XFER
|
|
case 0b011:
|
|
var name string
|
|
|
|
deltaRP := c.getSignedBits(insWord, 0b0000001100000000, 8)
|
|
deltaESP = c.getSignedBits(insWord, 0b0000000000110000, 4)
|
|
r2p := c.getBit(insWord, 7) != 0
|
|
p2r := c.getBit(insWord, 6) != 0
|
|
x2p := c.getBit(insWord, 0) != 0
|
|
|
|
if deltaRP >= 0 {
|
|
nRP = c.RP + word(deltaRP * wordbytes)
|
|
} else {
|
|
nRP = c.RP - word(-deltaRP * wordbytes)
|
|
}
|
|
|
|
if (deltaRP == 1) && (deltaESP == -1) && p2r && x2p {
|
|
name = "CALL"
|
|
} else
|
|
if (deltaRP == -1) && (deltaESP == 0) && r2p {
|
|
name = "RET"
|
|
} else
|
|
if (deltaRP == 0) && (deltaESP == -1) && x2p && !p2r {
|
|
name = "JUMP"
|
|
} else {
|
|
var b strings.Builder
|
|
b.WriteString("XFER")
|
|
if deltaRP == -1 { b.WriteString(".RSM1") }
|
|
if deltaRP == 1 { b.WriteString(".RS1") }
|
|
if deltaESP == -1 { b.WriteString(".SM1") }
|
|
if deltaESP == 1 { b.WriteString(".S1") }
|
|
if r2p { b.WriteString(".R2P") }
|
|
if p2r { b.WriteString(".P2R") }
|
|
if x2p { b.WriteString(".X2P") }
|
|
name = b.String()
|
|
}
|
|
|
|
c.showStep(name, 0, 0)
|
|
|
|
if r2p {
|
|
nPC, err = c.mem.read(c.RP)
|
|
if err != nil { return err }
|
|
}
|
|
if p2r {
|
|
err = c.mem.write(nPC, nRP)
|
|
if err != nil { return err }
|
|
}
|
|
if x2p {
|
|
nPC = c.X
|
|
nX = Y
|
|
}
|
|
|
|
// LOAD
|
|
case 0b100:
|
|
operand := c.getOperand(insWord, 13)
|
|
var ea word
|
|
var name string
|
|
if (insWord & 1) == 1 {
|
|
name = "LOAD.B"
|
|
operand &= ^1
|
|
ea = c.BP + word(operand)
|
|
} else {
|
|
name = "LOAD"
|
|
ea = c.FP + word(operand)
|
|
}
|
|
|
|
c.showStep(name, operand, oplen)
|
|
|
|
deltaESP = 1
|
|
x2y = true
|
|
|
|
nX, err = c.mem.read(ea)
|
|
if err != nil { return err }
|
|
// CBRANCH
|
|
case 0b101:
|
|
operand := c.getSignedOperand(insWord, 13)
|
|
var name string
|
|
invert := (operand & 1) == 0
|
|
operand = operand & -2 // clear bit 0
|
|
|
|
if invert { name = "CBRANCH.Z" } else { name = "CBRANCH" }
|
|
|
|
c.showStep(name, operand, 13)
|
|
|
|
deltaESP = -1
|
|
nX = Y
|
|
|
|
if (c.X != 0 && !invert) || (c.X == 0 && invert) {
|
|
nPC = word(int(c.PC) + operand)
|
|
}
|
|
// LOADC
|
|
case 0b110:
|
|
operand := c.getSignedOperand(insWord, 13)
|
|
oplen = 13
|
|
|
|
c.showStep("LOADC", operand, oplen)
|
|
|
|
deltaESP = 1
|
|
x2y = true
|
|
nX = word(operand)
|
|
// EXT
|
|
case 0b111:
|
|
extop := c.getBits(insWord, 0b0001111000000000, 10)
|
|
deltaESP = c.getSignedBits(insWord, 0b0000000000110000, 4)
|
|
writeFlag := c.getBit(insWord, 9) != 0
|
|
// signExtend := c.getBit(insWord,7) != 0
|
|
x2y = c.getBit(insWord, 6) != 0
|
|
operand := c.getOperand(insWord, 4)
|
|
|
|
var name string
|
|
|
|
switch extop {
|
|
// LOADREG/STOREREG
|
|
case 0:
|
|
oplen = 4
|
|
if writeFlag {
|
|
name = "STOREREG"
|
|
switch operand {
|
|
case 0: nFP = c.X
|
|
case 1: nBP = c.X
|
|
case 2: nRP = c.X
|
|
case 3: c.IV = c.X // should be nIV
|
|
case 4: c.IR = c.X // should be nIR
|
|
default: fmt.Errorf("Invalid STOREREG operand %v at %08X", operand, c.PC)
|
|
}
|
|
|
|
c.showStep(name, operand, oplen)
|
|
deltaESP = -1
|
|
x2y = false
|
|
nX = Y
|
|
} else {
|
|
name = "LOADREG"
|
|
switch operand {
|
|
case 0: nX = c.FP
|
|
case 1: nX = c.BP
|
|
case 2: nX = c.RP
|
|
case 3: nX = c.IV
|
|
case 4: nX = c.IR
|
|
case 5: nX = c.ESP
|
|
default: fmt.Errorf("Invalid LOADREG operand %v at %08X", operand, c.PC)
|
|
}
|
|
c.showStep(name, operand, oplen)
|
|
deltaESP = 1
|
|
x2y = true
|
|
}
|
|
// LOADI/STOREI
|
|
case 1:
|
|
if writeFlag { name = "STOREI" } else { name = "LOADI" }
|
|
c.showStep(name, operand, oplen)
|
|
|
|
if writeFlag {
|
|
oplen = 4
|
|
err = c.mem.write(c.X, Y)
|
|
if err != nil { return err }
|
|
nX = Y + word(operand)
|
|
} else {
|
|
nX, err = c.mem.read(c.X)
|
|
if err != nil { return err }
|
|
}
|
|
|
|
// FPADJ
|
|
case 3:
|
|
operand := c.getSignedOperand(insWord, 10)
|
|
oplen = 10
|
|
nFP = c.FP + word(operand)
|
|
deltaESP = 0
|
|
x2y = false
|
|
c.showStep("FPADJ", operand, oplen)
|
|
// LOADREL
|
|
case 5:
|
|
offset := c.getOperand(insWord, 10)
|
|
|
|
c.showStep("LOADREL", offset, 10)
|
|
|
|
nX, err = c.mem.read(c.PC + word(offset))
|
|
if err != nil { return err }
|
|
x2y = true
|
|
deltaESP = 1
|
|
default:
|
|
return fmt.Errorf("Invalid EXT instruction %v at %08X", extop, c.PC)
|
|
}
|
|
default:
|
|
return fmt.Errorf("Invalid instruction %04X at %08X", insWord, c.PC)
|
|
}
|
|
|
|
nESP = c.addToWord(nESP, deltaESP)
|
|
if nESP < 0 || nESP >= estackDepth {
|
|
return fmt.Errorf("estack overflow %v at %08X", nESP, c.PC)
|
|
}
|
|
|
|
if x2y {
|
|
c.estack[nESP] = c.X
|
|
}
|
|
|
|
c.PC = nPC
|
|
c.FP = nFP
|
|
c.BP = nBP
|
|
c.X = nX
|
|
c.RP = nRP
|
|
c.ESP = nESP
|
|
|
|
return nil
|
|
}
|