Compare commits

..

No commits in common. "master" and "v1.0.1" have entirely different histories.

26 changed files with 32210 additions and 290 deletions

1
.gitignore vendored
View File

@ -2,6 +2,5 @@
*.lst
*.tmp
*.sh
*.log
.idea/
src/.idea/

Binary file not shown.

View File

@ -1,5 +1,5 @@
logFile: okemu.log
logLevel: debug
logLevel: info
monitorFile: rom/MON_r8_9c6c6546.mon
cpmFile: rom/CPM_r8_bc0695e4.cpm
fdc:

View File

@ -3,9 +3,9 @@ package debug
import (
"fmt"
"okemu/debug/breakpoint"
"okemu/z80"
"okemu/z80/dis"
"github.com/romychs/z80go"
"github.com/romychs/z80go/dis"
log "github.com/sirupsen/logrus"
)
@ -23,7 +23,7 @@ type Debugger struct {
cpuHistoryEnabled bool
cpuHistoryStarted bool
cpuHistoryMaxSize int
cpuHistory []*z80go.CPU
cpuHistory []*z80.CPU
memBreakpoints [65536]byte
}
@ -38,7 +38,7 @@ func NewDebugger() *Debugger {
cpuHistoryEnabled: false,
cpuHistoryStarted: false,
cpuHistoryMaxSize: 0,
cpuHistory: []*z80go.CPU{},
cpuHistory: []*z80.CPU{},
}
return &d
}
@ -85,14 +85,14 @@ func (d *Debugger) SetCpuHistoryMaxSize(size int) {
}
func (d *Debugger) CpuHistoryClear() {
d.cpuHistory = make([]*z80go.CPU, 0)
d.cpuHistory = make([]*z80.CPU, 0)
}
func (d *Debugger) CpuHistorySize() int {
return len(d.cpuHistory)
}
func (d *Debugger) CpuHistory(index int) *z80go.CPU {
func (d *Debugger) CpuHistory(index int) *z80.CPU {
if index >= 0 && index < len(d.cpuHistory) {
return d.cpuHistory[index]
}
@ -108,9 +108,9 @@ func (d *Debugger) SetCpuHistoryStarted(started bool) {
d.cpuHistoryStarted = started
}
func (d *Debugger) SaveHistory(state *z80go.CPU) {
func (d *Debugger) SaveHistory(state *z80.CPU) {
if d.cpuHistoryEnabled && d.cpuHistoryMaxSize > 0 && d.cpuHistoryStarted {
d.cpuHistory = append([]*z80go.CPU{state}, d.cpuHistory...)
d.cpuHistory = append([]*z80.CPU{state}, d.cpuHistory...)
if len(d.cpuHistory) > d.cpuHistoryMaxSize {
d.cpuHistory = d.cpuHistory[0 : d.cpuHistoryMaxSize-1]
}

View File

@ -5,20 +5,19 @@ import (
"errors"
"fmt"
"io"
"maps"
"net"
"okemu/config"
"okemu/debug"
"okemu/debug/breakpoint"
"okemu/okean240"
"okemu/z80"
"okemu/z80/dis"
"os"
"runtime"
"slices"
"strconv"
"strings"
//"okemu/logger"
"strconv"
"github.com/romychs/z80go"
"github.com/romychs/z80go/dis"
log "github.com/sirupsen/logrus"
)
@ -76,7 +75,6 @@ var commandHandlers = map[string]CommandHandler{
"hexdump": {(*ZRCP).handleHexDump, "Dumps memory at address, showing hex and ascii"},
"load-binary": {(*ZRCP).handleLoadBinary, "Load binary file \"file\" at address \"addr\" with length \"len\", on the current memory zone"},
"quit": {(*ZRCP).handleEmptyHandler, "Closes connection"},
"reset-cpu": {(*ZRCP).handleResetCPU, "Resets CPU"},
"read-memory": {(*ZRCP).handleReadMemory, "Dumps memory at address"},
"reset-tstates-partial": {(*ZRCP).handleResetTStatesPartial, "Resets the t-states partial counter"},
"run": {(*ZRCP).handleRun, "Run cpu when on cpu step mode"},
@ -339,8 +337,6 @@ func (p *ZRCP) handleCPUHistory() (string, error) {
return p.stateResponse(history), nil
}
return "", errors.New("ERROR: index out of range")
case "get-size":
return strconv.Itoa(p.debugger.CpuHistorySize()), nil
case "ignrephalt", "ignrepldxr":
// ignore
default:
@ -391,24 +387,24 @@ func (p *ZRCP) handleLoadBinary() (string, error) {
// registersResponse Build string
// PC=%4x SP=%4x AF=%4x BC=%4x HL=%4x DE=%4x IX=%4x IY=%4x AF'=%4x BC'=%4x HL'=%4x DE'=%4x I=%2x
// R=%2x F=%s F'=%s MEMPTR=%4x IM0 IFF-- VPS: 0 MMU=00000000000000000000000000000000
func (p *ZRCP) registersResponse(state *z80go.CPU) string {
func (p *ZRCP) registersResponse(state *z80.CPU) string {
resp := fmt.Sprintf(getRegistersResponse,
state.PC,
state.SP,
toW(state.A, state.Flags.AsByte()),
toW(state.A, state.Flags.GetFlags()),
toW(state.B, state.C),
toW(state.H, state.L),
toW(state.D, state.E),
state.IX,
state.IY,
toW(state.AAlt, state.FlagsAlt.AsByte()),
toW(state.AAlt, state.FlagsAlt.GetFlags()),
toW(state.BAlt, state.CAlt),
toW(state.HAlt, state.LAlt),
toW(state.DAlt, state.EAlt),
state.I,
state.R,
state.Flags.String(),
state.FlagsAlt.String(),
state.Flags.GetFlagsStr(),
state.FlagsAlt.GetFlagsStr(),
state.MemPtr,
iifStr(state.Iff1, state.Iff2),
p.getMMU(),
@ -430,17 +426,17 @@ func (p *ZRCP) getNBytes(addr uint16, n int) string {
// stateResponse build string, represent history state
// PC=003a SP=ff46 AF=005c BC=174b HL=107f DE=0006 IX=ffff IY=5c3a AF'=0044 BC'=ffff HL'=ffff DE'=5cb9 I=3f R=78
// IM0 IFF-- (PC)=2a785c23 (SP)=107f MMU=00000000000000000000000000000000
func (p *ZRCP) stateResponse(state *z80go.CPU) string {
func (p *ZRCP) stateResponse(state *z80.CPU) string {
resp := fmt.Sprintf(getStateResponse,
state.PC,
state.SP,
toW(state.A, state.Flags.AsByte()),
toW(state.A, state.Flags.GetFlags()),
toW(state.B, state.C),
toW(state.H, state.L),
toW(state.D, state.E),
state.IX,
state.IY,
toW(state.AAlt, state.FlagsAlt.AsByte()),
toW(state.AAlt, state.FlagsAlt.GetFlags()),
toW(state.BAlt, state.CAlt),
toW(state.HAlt, state.LAlt),
toW(state.DAlt, state.EAlt),
@ -583,14 +579,9 @@ func (p *ZRCP) getExtendedStack() (string, error) {
var resp strings.Builder
spEnd := sp - size*2
es, err := p.computer.ExtendedStack()
if err == nil {
for i := sp; i > spEnd; i -= 2 {
pvt, ok := es[i]
if !ok {
pvt = z80go.PushValueTypeDefault
}
resp.WriteString(fmt.Sprintf("%04XH %s\n", p.computer.MemRead(i), PushValueTypeName[pvt]))
resp.WriteString(fmt.Sprintf("%04XH %s\n", p.computer.MemRead(i), PushValueTypeName[es[i]]))
}
}
log.Tracef("extended-stack get: %s", resp)
@ -642,7 +633,6 @@ func (p *ZRCP) handleSetBreakpointPassCount() (string, error) {
func (p *ZRCP) handleDisassemble() (string, error) {
var addr uint16
var size uint64
if len(p.params) == 0 {
addr = p.computer.CPUState().PC
} else {
@ -651,17 +641,9 @@ func (p *ZRCP) handleDisassemble() (string, error) {
if e != nil {
return "", fmt.Errorf("error, illegal address: %s", p.params[0])
}
if len(p.params) == 2 {
size, e = parseUint64(p.params[1])
if e != nil {
return "", fmt.Errorf("error, illegal size: %s", p.params[1])
}
} else {
size = 1
}
}
res := p.disassembler.Disassm(addr)
log.Tracef("DISASSM[0x%04X, %d]: %s", addr, size, res)
log.Trace(res)
return res, nil
}
@ -713,11 +695,6 @@ func (p *ZRCP) handleGetRegisters() (string, error) {
}
func (p *ZRCP) handleHardResetCPU() (string, error) {
p.computer.SetPendingCpuReset(true)
return "", nil
}
func (p *ZRCP) handleResetCPU() (string, error) {
p.computer.Reset()
return "", nil
}
@ -955,10 +932,8 @@ func (p *ZRCP) handleGetMemBreakpoints() (string, error) {
func (p *ZRCP) handleHelp() (string, error) {
var res strings.Builder
res.WriteString("Available commands:\n")
commands := slices.Collect(maps.Keys(commandHandlers))
slices.Sort(commands)
for _, cmd := range commands {
res.WriteString(fmt.Sprintf("%-*s%s\n", 24, cmd, commandHandlers[cmd].desc))
for k, v := range commandHandlers {
res.WriteString(fmt.Sprintf("%-*s%s\n", 24, k, v.desc))
}
res.WriteString("\nTotal commands: " + strconv.Itoa(len(commandHandlers)) + "\n")
return res.String(), nil

View File

@ -6,7 +6,6 @@ import (
"okemu/config"
"okemu/okean240"
"okemu/okean240/fdc"
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
@ -15,14 +14,13 @@ import (
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func NewMainWindow(computer *okean240.ComputerType, config *config.OkEmuConfig, title string) (*fyne.Window, *canvas.Raster, *widget.Label) {
emulatorApp := app.NewWithID("oceanemu-0001-0000-0001-8f33aba1fc0e")
w := emulatorApp.NewWindow(title)
func NewMainWindow(computer *okean240.ComputerType, config *config.OkEmuConfig) (*fyne.Window, *canvas.Raster, *widget.Label) {
emulatorApp := app.New()
w := emulatorApp.NewWindow("Океан 240.2")
// Handle all keys typed in main window canvas
w.Canvas().SetOnTypedKey(
@ -73,57 +71,24 @@ func NewMainWindow(computer *okean240.ComputerType, config *config.OkEmuConfig,
return &w, raster, label
}
var floppyDriveExt = []string{".okd", ".dat", ".bin", ".raw"}
func newOkdOpenDialog(drive byte, c *okean240.ComputerType, w fyne.Window, config *config.OkEmuConfig) *dialog.FileDialog {
fod := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
// Read file list error occurred
if err != nil {
dialog.ShowError(err, w)
return
}
// User Cancelled
if reader == nil {
return
}
// User select Open button, load data
err = c.LoadFloppyData(drive, reader)
if err != nil {
dialog.ShowError(err, w)
}
_ = reader.Close()
}, w)
fod.SetFileName(config.FDC[drive].FloppyFile)
cwd, e := os.Getwd()
if e == nil {
uri, e := storage.ListerForURI(storage.NewFileURI(cwd))
if e == nil {
fod.SetLocation(uri)
}
}
fod.SetTitleText(fmt.Sprintf("Load floppy %s: image", string(rune(int(drive+66)))))
fod.SetFilter(storage.NewExtensionFileFilter(floppyDriveExt))
fod.Resize(fyne.NewSize(580, 500))
return fod
}
func newToolbar(c *okean240.ComputerType, w fyne.Window, a fyne.App, config *config.OkEmuConfig) *fyne.Container {
hBox := container.NewHBox()
for d := 0; d < fdc.TotalDrives; d++ {
hBox.Add(widget.NewLabel(string(rune(66+d)) + ":"))
hBox.Add(widget.NewToolbar(
// Button Open
widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
newOkdOpenDialog(byte(d), c, w, config).Show()
}),
// Button Save
widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
err := c.SaveFloppy(byte(d))
if err != nil {
dialog.ShowError(err, w)
}
}),
//widget.NewToolbarSpacer(),
widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
err := c.LoadFloppy(byte(d))
if err != nil {
dialog.ShowError(err, w)
}
}),
))
hBox.Add(widget.NewSeparator())
}
@ -174,7 +139,7 @@ func newToolbar(c *okean240.ComputerType, w fyne.Window, a fyne.App, config *con
}))
hBox.Add(layout.NewSpacer())
hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() {
c.SetPendingHardReset(true)
c.SetPendingReset(true)
//computer.Reset(conf)
}))
return hBox

View File

@ -37,7 +37,6 @@ require (
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/romychs/z80go v1.0.1 // indirect
github.com/rymdport/portal v0.4.2 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect

View File

@ -70,10 +70,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/romychs/z80go v1.0.0 h1:umlM6U/JPt/qSAUhxu51wSK5a98gJZwhTu34Xj+5Ml8=
github.com/romychs/z80go v1.0.0/go.mod h1:bah4U8xv8qWfRJsrioP643bri+LDzKyC03E718YS9wI=
github.com/romychs/z80go v1.0.1 h1:rBYxvvdWHGsitZMXTheZGk6lB9VIepUUVWMxPVWimPY=
github.com/romychs/z80go v1.0.1/go.mod h1:bah4U8xv8qWfRJsrioP643bri+LDzKyC03E718YS9wI=
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=

View File

@ -46,7 +46,10 @@ func InitLogging() {
// FlushLogs Flush logs if logging to file
func FlushLogs() {
if logBuffer != nil {
_ = logBuffer.Flush()
err := logBuffer.Flush()
if err != nil {
return
}
}
}

View File

@ -11,6 +11,7 @@ import (
"okemu/forms"
"okemu/logger"
"okemu/okean240"
"okemu/z80/dis"
"runtime"
"sync/atomic"
"time"
@ -19,20 +20,13 @@ import (
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
"github.com/loov/hrtime"
"github.com/romychs/z80go/dis"
)
var Version = "v1.0.3"
var BuildTime = "2026-04-08"
var Version = "v1.0.1"
var BuildTime = "2026-04-01"
const defaultTimerClkPeriod = 433
const defaultCpuClkPeriod = 310
const windowsTimerClkPeriod = 400
const windowsCpuClkPeriod = 300
const maxDelta = 3
const diffScale = 15.0
const defaultTimerClkPeriod = 430
const defaultCpuClkPeriod = 311
////go:embed hex/m80.hex
//var serialBytes []byte
@ -40,9 +34,27 @@ const diffScale = 15.0
////go:embed bin/jack.com
//var ramBytes []byte
var needReset = false
func main() {
fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime)
//f, err := os.Create("okemu.prof")
//if err != nil {
// log.Warn("Can not create prof file", err)
//}
//defer func(f *os.File) {
// err := f.Close()
// if err != nil {
// log.Warn("Can not close prof file", err)
// }
//}(f)
//if err := pprof.StartCPUProfile(f); err != nil {
// log.Warn("Can not start CPU profiling", err)
//}
//defer pprof.StopCPUProfile()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -55,24 +67,18 @@ func main() {
conf := config.GetConfig()
// Reconfigure logging by config values
logger.ReconfigureLogging(conf)
if runtime.GOOS == "windows" {
cpuClkPeriod.Store(windowsCpuClkPeriod)
timerClkPeriod.Store(windowsTimerClkPeriod)
} else {
cpuClkPeriod.Store(defaultCpuClkPeriod)
timerClkPeriod.Store(defaultTimerClkPeriod)
}
// logger.ReconfigureLogging(conf)
debugger := debug.NewDebugger()
computer := okean240.NewComputer(conf, debugger)
//computer.SetSerialBytes(serialBytes)
computer.AutoLoadFloppy()
disassm := dis.NewDisassembler(computer)
w, raster, label := forms.NewMainWindow(computer, conf, "Океан 240.2 "+Version)
w, raster, label := forms.NewMainWindow(computer, conf)
//dezog := dzrp.NewDZRP(conf, debugger, disassm, computer)
dezog := zrcp.NewZRCP(conf, debugger, disassm, computer)
@ -87,7 +93,6 @@ func main() {
(*w).ShowAndRun()
computer.AutoSaveFloppy()
logger.CloseLogs()
}
func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) {
@ -108,20 +113,19 @@ func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas
// redraw screen here
fyne.Do(func() {
// status for every 50 frames
if frame%10 == 0 {
if frame%50 == 0 {
timeElapsed := hrtime.Since(timeStart)
period := float64(timeElapsed.Nanoseconds()) / 1_000_000.0
//cpuFreq = math.Round(float64(computer.Cycles()-pre)/period) / 1000.0
cpuFreq = math.Round(float64(cpuTicks.Load()-pre)/period) / 1000.0
timerFreq = math.Round(float64(timerTicks.Load()-preTim)/period) / 1000.0
label.SetText(formatLabel(computer, cpuFreq, timerFreq))
adjustPeriods(computer, cpuFreq, timerFreq)
if frame%50 == 0 {
label.SetText(formatLabel(computer, cpuFreq, timerFreq))
//log.Debugf("Cpu clk period: %d, Timer clock period: %d, frame time: %1.3fms", cpuClkPeriod.Load(), timerClkPeriod.Load(), period/5.0)
//logger.FlushLogs()
}
//log.Debugf("Cpu clk period: %d, Timer clock period: %d, period: %1.3f", cpuClkPeriod.Load(), timerClkPeriod.Load(), period)
//pre = computer.Cycles()
pre = cpuTicks.Load()
preTim = timerTicks.Load()
timeStart = hrtime.Now()
@ -137,46 +141,32 @@ func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas
// adjustPeriods Adjust periods for CPU and Timer clock frequencies
func adjustPeriods(c *okean240.ComputerType, cpuFreq float64, timerFreq float64) {
// adjust cpu clock if not full speed
if !c.FullSpeed() {
calcPeriod(cpuFreq, okean240.CPUFrequency, okean240.CPUFrequencyHi, okean240.CPUFrequencyLow, &cpuClkPeriod)
}
// adjust timerClock clock
calcPeriod(timerFreq, okean240.TimerFrequency, okean240.TimerFrequencyHi, okean240.TimerFrequencyLow, &timerClkPeriod)
}
// calcPeriod calc new value period to adjust frequency of timer or CPU
func calcPeriod(curFreq float64, destFreq float64, hiLimit float64, loLimit float64, period *atomic.Int64) {
if curFreq > hiLimit && period.Load() < 2000 {
period.Add(calcDelta(curFreq, destFreq))
} else if curFreq < loLimit && period.Load() > 0 {
period.Add(-calcDelta(curFreq, destFreq))
if period.Load() < 0 {
period.Store(0)
if c.FullSpeed() {
if cpuFreq > okean240.CPUFrequencyHi && cpuClkPeriod.Load() < defaultCpuClkPeriod+defaultCpuClkPeriod/2 {
cpuClkPeriod.Add(1)
} else if cpuFreq < okean240.CPUFrequencyLow && cpuClkPeriod.Load() > 3 {
cpuClkPeriod.Add(-1)
}
}
}
// calcDelta calculate step to change period
func calcDelta(currentFreq float64, destFreq float64) int64 {
delta := int64(math.Round(math.Abs(destFreq-currentFreq) * diffScale))
if delta < 1 {
return 1
} else if delta > maxDelta {
return maxDelta
// adjust timerClock clock
if timerFreq > okean240.TimerFrequencyHi && timerClkPeriod.Load() < defaultTimerClkPeriod+defaultTimerClkPeriod/2 {
timerClkPeriod.Add(1)
} else if timerFreq < okean240.TimerFrequencyLow && timerClkPeriod.Load() > 3 {
timerClkPeriod.Add(-1)
}
return delta
}
func formatLabel(computer *okean240.ComputerType, freq float64, freqTim float64) string {
return fmt.Sprintf("Screen size: %dx%d | Fcpu: %1.3fMHz | Ftmr: %1.3fMHz | Debugger: %s", // CP:%d TP:%d",
computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
return fmt.Sprintf("Screen size: %dx%d | Fcpu: %1.3fMHz | Ftmr: %1.3fMHz | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
}
var timerTicks atomic.Uint64
var timerClkPeriod atomic.Int64 // period in nanos for 1.5MHz frequency
var cpuClkPeriod atomic.Int64 // period in nanos for 2.5MHz frequency
func timerClock(computer *okean240.ComputerType) {
timerClkPeriod.Store(defaultTimerClkPeriod)
timeStart := hrtime.Now()
for {
elapsed := hrtime.Since(timeStart)
@ -193,6 +183,7 @@ func timerClock(computer *okean240.ComputerType) {
var cpuTicks atomic.Uint64
func cpuClock(computer *okean240.ComputerType, dezog debug.DEZOG) {
cpuClkPeriod.Store(defaultCpuClkPeriod)
cpuTicks.Store(0)
nextTick := uint64(0)
@ -203,54 +194,32 @@ func cpuClock(computer *okean240.ComputerType, dezog debug.DEZOG) {
for {
elapsed := hrtime.Since(timeStart)
if computer.FullSpeed() || int64(elapsed) >= cpuClkPeriod.Load() {
if int64(elapsed) >= cpuClkPeriod.Load() {
timeStart = hrtime.Now()
bp = 0
bpType = 0
// 2.5MHz frequency
cpuTicks.Add(1)
if !computer.PendingHardReset() && !computer.PendingCpuReset() {
if computer.FullSpeed() {
// Max frequency
_, bp, bpType = computer.Do()
nextTick = cpuTicks.Load()
} else if cpuTicks.Load() >= nextTick {
var t uint32
t, bp, bpType = computer.Do()
nextTick = cpuTicks.Load() + uint64(t)
runtime.Gosched()
}
if computer.FullSpeed() {
// Max frequency
_, bp, bpType = computer.Do()
} else if cpuTicks.Load() >= nextTick {
var t uint32
t, bp, bpType = computer.Do()
nextTick = cpuTicks.Load() + uint64(t)
runtime.Gosched()
}
// Breakpoint hit
if bp > 0 || bpType != 0 {
dezog.BreakpointHit(bp, bpType)
}
} else {
if computer.PendingHardReset() {
computer.HardReset()
} else {
computer.Reset()
}
// Breakpoint hit
if bp > 0 || bpType != 0 {
dezog.BreakpointHit(bp, bpType)
}
if needReset {
computer.Reset()
needReset = false
}
}
}
}
//func initPerf() {
// f, err := os.Create("okemu.prof")
// if err != nil {
// log.Warn("Can not create prof file", err)
// }
// defer func(f *os.File) {
// err := f.Close()
// if err != nil {
// log.Warn("Can not close prof file", err)
// }
// }(f)
// if err := pprof.StartCPUProfile(f); err != nil {
// log.Warn("Can not start CPU profiling", err)
// }
// defer pprof.StopCPUProfile()
//}

View File

@ -15,25 +15,24 @@ import (
"okemu/okean240/pic"
"okemu/okean240/pit"
"okemu/okean240/usart"
"okemu/z80"
"os"
"strconv"
"sync/atomic"
"fyne.io/fyne/v2"
"github.com/romychs/z80go"
"okemu/z80/c99"
log "github.com/sirupsen/logrus"
)
const DefaultCPUFrequency = 2_500_000
const CPUFrequencyLow float64 = 2.48
const CPUFrequency float64 = 2.50
const CPUFrequencyHi float64 = 2.52
const CPUFrequencyLow float64 = 2.47
const CPUFrequencyHi float64 = 2.53
const TimerFrequencyLow float64 = 1.47
const TimerFrequency float64 = 1.50
const TimerFrequencyHi float64 = 1.53
type ComputerType struct {
cpu *z80go.CPU
cpu *c99.Z80
memory Memory
ioPorts [256]byte
cycles uint64
@ -54,17 +53,16 @@ type ComputerType struct {
hShift byte
cpuFrequency uint32
//
debugger *debug.Debugger
config *config.OkEmuConfig
kbAck atomic.Bool
fullSpeed atomic.Bool
pendingCpuReset atomic.Bool
pendingHardReset atomic.Bool
debugger *debug.Debugger
config *config.OkEmuConfig
kbAck atomic.Bool
fullSpeed atomic.Bool
pendingReset atomic.Bool
}
type Snapshot struct {
CPU *z80go.CPU `json:"cpu,omitempty"`
Memory string `json:"memory,omitempty"`
CPU *z80.CPU `json:"cpu,omitempty"`
Memory string `json:"memory,omitempty"`
}
const VRAMBlock0 = 3
@ -87,11 +85,11 @@ const VidColorBit = 0x40
// SetCPUState(state *z80.CPU)
//}
func (c *ComputerType) CPUState() *z80go.CPU {
func (c *ComputerType) CPUState() *z80.CPU {
return c.cpu.GetState()
}
func (c *ComputerType) SetCPUState(state *z80go.CPU) {
func (c *ComputerType) SetCPUState(state *z80.CPU) {
c.cpu.SetState(state)
}
@ -111,28 +109,10 @@ func (c *ComputerType) MemWrite(addr uint16, val byte) {
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
c := ComputerType{}
c.config = cfg
c.cpu = z80go.NewCPU(&c)
c.debugger = deb
c.HardReset()
return &c
}
// Reset Only CPU reset.
func (c *ComputerType) Reset() {
// CPU
c.cpu.Reset()
c.cycles = 0
c.tstatesPartial = 0
c.pendingCpuReset.Store(false)
}
// HardReset full computer reset, Use SetPendingHardReset flag to set flag before call this method
func (c *ComputerType) HardReset() {
c.cpu.Reset()
c.memory = Memory{}
c.memory.Init(c.config.MonitorFile, c.config.CPMFile)
c.memory.Init(cfg.MonitorFile, cfg.CPMFile)
c.cpu = c99.New(&c)
c.cycles = 0
c.tstatesPartial = 0
@ -145,18 +125,24 @@ func (c *ComputerType) HardReset() {
c.vShift = 0
c.hShift = 0
//c.aOffset = 0x100
c.pit = pit.New()
c.kbAck.Store(false)
c.usart = usart.New()
c.pic = pic.NewI8259()
c.fdc = fdc.NewFDC(c.config)
c.fdc = fdc.NewFDC(cfg)
c.cpuFrequency = DefaultCPUFrequency
c.debugger = deb
c.fullSpeed.Store(false)
c.pendingHardReset.Store(false)
c.pendingCpuReset.Store(false)
c.pendingReset.Store(false)
return &c
}
func (c *ComputerType) Reset() {
c.cpu.Reset()
c.cycles = 0
c.tstatesPartial = 0
}
func (c *ComputerType) getContext() map[string]interface{} {
@ -191,7 +177,7 @@ func (c *ComputerType) getContext() map[string]interface{} {
ctx["BC"] = uint16(s.B)<<8 | uint16(s.C)
ctx["DE"] = uint16(s.D)<<8 | uint16(s.E)
ctx["HL"] = uint16(s.H)<<8 | uint16(s.L)
ctx["AF"] = uint16(s.A)<<8 | uint16(s.Flags.AsByte())
ctx["AF"] = uint16(s.A)<<8 | uint16(s.Flags.GetFlags())
ctx["MEM"] = c.memory.MemRead
ctx["IO"] = c.IORead
return ctx
@ -332,6 +318,10 @@ func (c *ComputerType) TimerClk() {
}
}
func (c *ComputerType) LoadFloppy(drive byte) error {
return c.fdc.LoadFloppy(drive)
}
func (c *ComputerType) SaveFloppy(drive byte) error {
return c.fdc.SaveFloppy(drive)
}
@ -468,7 +458,7 @@ func (c *ComputerType) AutoSaveFloppy() {
func (c *ComputerType) AutoLoadFloppy() {
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
if c.config.FDC[drv].AutoLoad {
e := c.fdc.LoadFloppyFile(drv, c.config.FDC[drv].FloppyFile)
e := c.fdc.LoadFloppy(drv)
if e != nil {
log.Error(e)
}
@ -492,7 +482,7 @@ func (c *ComputerType) SetExtendedStack(enabled bool) {
c.cpu.SetExtendedStack(enabled)
}
func (c *ComputerType) ExtendedStack() (map[uint16]z80go.PushValueType, error) {
func (c *ComputerType) ExtendedStack() ([]byte, error) {
return c.cpu.ExtendedStack()
}
@ -536,22 +526,10 @@ func (c *ComputerType) FullSpeed() bool {
return c.fullSpeed.Load()
}
func (c *ComputerType) SetPendingHardReset(pending bool) {
c.pendingHardReset.Store(pending)
func (c *ComputerType) SetPendingReset(pending bool) {
c.pendingReset.Store(pending)
}
func (c *ComputerType) PendingHardReset() bool {
return c.pendingHardReset.Load()
}
func (c *ComputerType) SetPendingCpuReset(pending bool) {
c.pendingCpuReset.Store(pending)
}
func (c *ComputerType) PendingCpuReset() bool {
return c.pendingCpuReset.Load()
}
func (c *ComputerType) LoadFloppyData(drive byte, reader fyne.URIReadCloser) error {
return c.fdc.LoadFloppyData(drive, reader)
func (c *ComputerType) PendingReset() bool {
return c.pendingReset.Load()
}

View File

@ -11,7 +11,6 @@ import (
"bytes"
"encoding/binary"
"errors"
"io"
"okemu/config"
"os"
"slices"
@ -331,12 +330,12 @@ func (f *FloppyDriveController) Drq() byte {
return f.drq
}
//func (f *FloppyDriveController) LoadFloppy(drive byte) error {
// if drive < TotalDrives {
// return f.LoadConfiguredFloppy(drive)
// }
// return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
//}
func (f *FloppyDriveController) LoadFloppy(drive byte) error {
if drive < TotalDrives {
return loadFloppy(&f.sectors[drive], f.config.FDC[drive].FloppyFile)
}
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
}
func (f *FloppyDriveController) SaveFloppy(drive byte) error {
if drive < TotalDrives {
@ -402,28 +401,8 @@ func (f *FloppyDriveController) Sector() byte {
return f.sectorNo
}
func (f *FloppyDriveController) LoadFloppyData(drive byte, reader io.ReadCloser) error {
if drive >= TotalDrives {
return errors.New("Drive " + strconv.Itoa(int(drive+65)) + " out of range")
}
var err error
for sector := 0; sector < SizeInSectors; sector++ {
var n int
n, err = reader.Read(f.sectors[drive][sector])
if n != SectorSize {
log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize)
}
if err != nil {
log.Error("Load floppy content failed:", err)
return err
}
}
return nil
}
// LoadFloppyFile load floppy image to sector buffer from file
func (f *FloppyDriveController) LoadFloppyFile(drive byte, fileName string) error {
//fileName := f.config.FDC[drive].FloppyFile
// loadFloppy load floppy image to sector buffer from file
func loadFloppy(sectors *[SizeInSectors]SectorType, fileName string) error {
log.Debugf("Load Floppy content from file %s.", fileName)
file, err := os.Open(fileName)
if err != nil {
@ -437,7 +416,19 @@ func (f *FloppyDriveController) LoadFloppyFile(drive byte, fileName string) erro
log.Error(err)
}
}(file)
return f.LoadFloppyData(drive, file)
for sector := 0; sector < SizeInSectors; sector++ {
var n int
n, err = file.Read(sectors[sector])
if n != SectorSize {
log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize)
}
if err != nil {
log.Error("Load floppy content failed:", err)
return err
}
}
return nil
}
// saveFloppy Save specified sectors to file with name fileName

View File

@ -17,7 +17,7 @@ func (c *ComputerType) IORead(port uint16) uint8 {
irr := c.pic.IRR()
// if irq from keyboard and no ACK applied, re-fire
if irr&RstKbdMask != 0 && !c.kbAck.Load() {
log.Tracef("KBD IRQ REFIRE PC=%04X", c.cpu.PC)
log.Tracef("KBD IRQ REFIRE PC=%04X", c.cpu.PC())
c.pic.SetIRQ(RstKbdNo)
}
return irr
@ -31,7 +31,7 @@ func (c *ComputerType) IORead(port uint16) uint8 {
return c.usart.Receive()
case KbdDd78pa:
// Keyboard data
log.Tracef("KBD RD: %d, PC=%04X", c.ioPorts[KbdDd78pa], c.cpu.PC)
log.Tracef("KBD RD: %d, PC=%04X", c.ioPorts[KbdDd78pa], c.cpu.PC())
return c.ioPorts[KbdDd78pa]
case KbdDd78pb:
return c.ioPorts[KbdDd78pb]

58
src/z80/c99/constants.go Normal file
View File

@ -0,0 +1,58 @@
package c99
var cycles00 = [256]byte{
4, 10, 7, 6, 4, 4, 7, 4, 4, 11, 7, 6, 4, 4, 7, 4,
8, 10, 7, 6, 4, 4, 7, 4, 12, 11, 7, 6, 4, 4, 7, 4,
7, 10, 16, 6, 4, 4, 7, 4, 7, 11, 16, 6, 4, 4, 7, 4,
7, 10, 13, 6, 11, 11, 10, 4, 7, 11, 13, 6, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
5, 10, 10, 10, 10, 11, 7, 11, 5, 10, 10, 0, 10, 17, 7, 11,
5, 10, 10, 11, 10, 11, 7, 11, 5, 4, 10, 11, 10, 0, 7, 11,
5, 10, 10, 19, 10, 11, 7, 11, 5, 4, 10, 4, 10, 0, 7, 11,
5, 10, 10, 4, 10, 11, 7, 11, 5, 6, 10, 4, 10, 0, 7, 11,
}
var cyclesED = [256]byte{
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9,
12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9,
12, 12, 15, 20, 8, 14, 8, 18, 12, 12, 15, 20, 8, 14, 8, 18,
12, 12, 15, 20, 8, 14, 8, 8, 12, 12, 15, 20, 8, 14, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
16, 16, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 8, 8, 8, 8,
16, 16, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
}
var cyclesDDFD = [256]byte{
4, 4, 4, 4, 4, 4, 4, 4, 4, 15, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 15, 4, 4, 4, 4, 4, 4,
4, 14, 20, 10, 8, 8, 11, 4, 4, 15, 20, 10, 8, 8, 11, 4,
4, 4, 4, 4, 23, 23, 19, 4, 4, 15, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
8, 8, 8, 8, 8, 8, 19, 8, 8, 8, 8, 8, 8, 8, 19, 8,
19, 19, 19, 19, 19, 19, 4, 19, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 14, 4, 23, 4, 15, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 10, 4, 4, 4, 4, 4, 4,
}

277
src/z80/c99/cpu.go Normal file
View File

@ -0,0 +1,277 @@
package c99
import (
"errors"
"okemu/z80"
)
const (
PushValueTypeDefault = iota
PushValueTypeCall
PushValueTypeRst
PushValueTypePush
PushValueTypeMaskableInt
PushValueTypeNonMaskableInt
)
type Z80 struct {
// cycle count (t-states)
cycleCount uint32
// special purpose registers
pc, sp, ix, iy uint16
// "wz" register
memPtr uint16
// main registers
a, b, c, d, e, h, l byte
// alternate registers
a_, b_, c_, d_, e_, h_, l_, f_ byte
// interrupt vector, memory refresh
i, r byte
// flags: sign, zero, yf, half-carry, xf, parity/overflow, negative, carry
sf, zf, yf, hf, xf, pf, nf, cf bool
iffDelay byte
interruptMode byte
intData byte
iff1 bool
iff2 bool
isHalted bool
intPending bool
nmiPending bool
core z80.MemIoRW
memAccess map[uint16]byte
codeCoverageEnabled bool
codeCoverage map[uint16]bool
extendedStackEnabled bool
extendedStack [65536]uint8
}
const (
MemAccessRead = 1
MemAccessWrite = 2
)
// New initializes a Z80 instance and return pointer to it
func New(core z80.MemIoRW) *Z80 {
z := Z80{}
z.core = core
z.cycleCount = 0
z.pc = 0
z.sp = 0xFFFF
z.ix = 0
z.iy = 0
z.memPtr = 0
z.a = 0xFF
z.b = 0
z.c = 0
z.d = 0
z.e = 0
z.h = 0
z.l = 0
z.a_ = 0
z.b_ = 0
z.c_ = 0
z.d_ = 0
z.e_ = 0
z.h_ = 0
z.l_ = 0
z.f_ = 0
z.i = 0
z.r = 0
z.sf = true
z.zf = true
z.yf = true
z.hf = true
z.xf = true
z.pf = true
z.nf = true
z.cf = true
z.iffDelay = 0
z.interruptMode = 0
z.iff1 = false
z.iff2 = false
z.isHalted = false
z.intPending = false
z.nmiPending = false
z.intData = 0
z.codeCoverageEnabled = false
z.codeCoverage = make(map[uint16]bool)
return &z
}
// RunInstruction executes the next instruction in memory + handles interrupts
func (z *Z80) RunInstruction() (uint32, *map[uint16]byte) {
z.memAccess = map[uint16]byte{}
if z.codeCoverageEnabled {
z.codeCoverage[z.pc] = true
}
pre := z.cycleCount
if z.isHalted {
z.execOpcode(0x00)
} else {
opcode := z.nextB()
z.execOpcode(opcode)
}
z.processInterrupts()
return z.cycleCount - pre, &z.memAccess
}
func (z *Z80) SetState(state *z80.CPU) {
z.cycleCount = 0
z.a = state.A
z.b = state.B
z.c = state.C
z.d = state.D
z.e = state.E
z.h = state.H
z.l = state.L
z.a_ = state.AAlt
z.b_ = state.BAlt
z.c_ = state.CAlt
z.d_ = state.DAlt
z.e_ = state.EAlt
z.h_ = state.HAlt
z.l_ = state.LAlt
z.pc = state.PC
z.sp = state.SP
z.ix = state.IX
z.iy = state.IY
z.i = state.I
z.r = state.R
z.memPtr = state.MemPtr
z.sf = state.Flags.S
z.zf = state.Flags.Z
z.yf = state.Flags.Y
z.hf = state.Flags.H
z.xf = state.Flags.X
z.pf = state.Flags.P
z.nf = state.Flags.N
z.cf = state.Flags.C
z.f_ = state.FlagsAlt.GetFlags()
//z.iff_delay = 0
z.interruptMode = state.IMode
z.iff1 = state.Iff1
z.iff2 = state.Iff2
z.isHalted = state.Halted
z.intPending = state.InterruptOccurred
z.nmiPending = false
z.intData = 0
}
func (z *Z80) GetState() *z80.CPU {
return &z80.CPU{
A: z.a,
B: z.b,
C: z.c,
D: z.d,
E: z.e,
H: z.h,
L: z.l,
AAlt: z.a_,
BAlt: z.b_,
CAlt: z.c_,
DAlt: z.d_,
EAlt: z.e_,
HAlt: z.h_,
LAlt: z.l_,
IX: z.ix,
IY: z.iy,
I: z.i,
R: z.r,
SP: z.sp,
PC: z.pc,
Flags: z.getFlags(),
FlagsAlt: z.getAltFlags(),
IMode: z.interruptMode,
Iff1: z.iff1,
Iff2: z.iff2,
Halted: z.isHalted,
DoDelayedDI: z.intPending,
DoDelayedEI: z.intPending,
CycleCount: z.cycleCount,
InterruptOccurred: z.intPending,
MemPtr: z.memPtr,
}
}
func (z *Z80) getFlags() z80.FlagsType {
return z80.FlagsType{
S: z.sf,
Z: z.zf,
Y: z.yf,
H: z.hf,
X: z.xf,
P: z.pf,
N: z.nf,
C: z.cf,
}
}
func (z *Z80) getAltFlags() z80.FlagsType {
return z80.FlagsType{
S: z.f_&0x80 != 0,
Z: z.f_&0x40 != 0,
Y: z.f_&0x20 != 0,
H: z.f_&0x10 != 0,
X: z.f_&0x08 != 0,
P: z.f_&0x04 != 0,
N: z.f_&0x02 != 0,
C: z.f_&0x01 != 0,
}
}
func (z *Z80) PC() uint16 {
return z.pc
}
func (z *Z80) ClearCodeCoverage() {
clear(z.codeCoverage)
}
func (z *Z80) SetCodeCoverage(enabled bool) {
z.codeCoverageEnabled = enabled
if !enabled {
clear(z.codeCoverage)
}
}
func (z *Z80) CodeCoverage() map[uint16]bool {
return z.codeCoverage
}
func (z *Z80) SetExtendedStack(enabled bool) {
z.extendedStackEnabled = enabled
if enabled {
for addr := 0; addr < 65536; addr++ {
z.extendedStack[addr] = PushValueTypeDefault
}
}
}
func (z *Z80) ExtendedStack() ([]byte, error) {
var err error
if !z.extendedStackEnabled {
err = errors.New("error, z80: ExtendedStack disabled")
}
return z.extendedStack[:], err
}

207
src/z80/c99/helper.go Normal file
View File

@ -0,0 +1,207 @@
package c99
import log "github.com/sirupsen/logrus"
func (z *Z80) rb(addr uint16) byte {
z.memAccess[addr] = MemAccessRead
return z.core.MemRead(addr)
}
func (z *Z80) wb(addr uint16, val byte) {
z.memAccess[addr] = MemAccessWrite
z.core.MemWrite(addr, val)
}
func (z *Z80) rw(addr uint16) uint16 {
z.memAccess[addr] = MemAccessRead
z.memAccess[addr+1] = MemAccessRead
return (uint16(z.core.MemRead(addr+1)) << 8) | uint16(z.core.MemRead(addr))
}
func (z *Z80) ww(addr uint16, val uint16) {
z.memAccess[addr] = MemAccessWrite
z.memAccess[addr+1] = MemAccessWrite
z.core.MemWrite(addr, byte(val))
z.core.MemWrite(addr+1, byte(val>>8))
}
func (z *Z80) pushW(val uint16) {
z.sp -= 2
z.ww(z.sp, val)
z.extendedStack[z.sp] = PushValueTypePush
}
func (z *Z80) popW() uint16 {
z.sp += 2
return z.rw(z.sp - 2)
}
func (z *Z80) nextB() byte {
b := z.core.MemRead(z.pc)
z.pc++
return b
}
func (z *Z80) nextW() uint16 {
w := (uint16(z.core.MemRead(z.pc+1)) << 8) | uint16(z.core.MemRead(z.pc))
z.pc += 2
return w
}
func (z *Z80) bc() uint16 {
return (uint16(z.b) << 8) | uint16(z.c)
}
func (z *Z80) de() uint16 {
return (uint16(z.d) << 8) | uint16(z.e)
}
func (z *Z80) hl() uint16 {
return (uint16(z.h) << 8) | uint16(z.l)
}
func (z *Z80) setBC(val uint16) {
z.b = byte(val >> 8)
z.c = byte(val)
}
func (z *Z80) setDE(val uint16) {
z.d = byte(val >> 8)
z.e = byte(val)
}
func (z *Z80) setHL(val uint16) {
z.h = byte(val >> 8)
z.l = byte(val)
}
func (z *Z80) f() byte {
val := byte(0)
if z.cf {
val |= 0x01
}
if z.nf {
val |= 0x02
}
if z.pf {
val |= 0x04
}
if z.xf {
val |= 0x08
}
if z.hf {
val |= 0x10
}
if z.yf {
val |= 0x20
}
if z.zf {
val |= 0x40
}
if z.sf {
val |= 0x80
}
return val
}
func (z *Z80) setF(val byte) {
z.cf = val&1 != 0
z.nf = (val>>1)&1 != 0
z.pf = (val>>2)&1 != 0
z.xf = (val>>3)&1 != 0
z.hf = (val>>4)&1 != 0
z.yf = (val>>5)&1 != 0
z.zf = (val>>6)&1 != 0
z.sf = (val>>7)&1 != 0
}
// increments R, keeping the highest byte intact
func (z *Z80) incR() {
z.r = (z.r & 0x80) | ((z.r + 1) & 0x7f)
}
func boolToInt32(b bool) int32 {
if b {
return 1
}
return 0
}
// returns if there was a carry between bit "bit_no" and "bit_no - 1" when
// executing "a + b + cy"
func carry(bitNo int, a uint16, b uint16, cy bool) bool {
result := int32(a) + int32(b) + boolToInt32(cy)
carry := result ^ int32(a) ^ int32(b)
return (carry & (1 << bitNo)) != 0
}
// parity returns the parity of byte: 0 if odd, else 1
func parity(val byte) bool {
ones := byte(0)
for i := 0; i < 8; i++ {
ones += (val >> i) & 1
}
return (ones & 1) == 0
}
// updateXY set undocumented 3rd (X) and 5th (Y) flags
func (z *Z80) updateXY(result byte) {
z.yf = result&0x20 != 0
z.xf = result&0x08 != 0
}
func (z *Z80) DebugOutput() {
log.Debugf("PC: %04X, AF: %04X, BC: %04X, DE: %04X, HL: %04X, SP: %04X, IX: %04X, IY: %04X, I: %02X, R: %02X",
z.pc, (uint16(z.a)<<8)|uint16(z.f()), z.bc(), z.de(), z.hl(), z.sp,
z.ix, z.iy, z.i, z.r)
log.Debugf("\t(%02X %02X %02X %02X), cycleCount: %d\n", z.rb(z.pc), z.rb(z.pc+1),
z.rb(z.pc+2), z.rb(z.pc+3), z.cycleCount)
}
func (z *Z80) Reset() {
z.cycleCount = 0
z.pc = 0
z.sp = 0xFFFF
z.ix = 0
z.iy = 0
z.memPtr = 0
z.a = 0xFF
z.b = 0
z.c = 0
z.d = 0
z.e = 0
z.h = 0
z.l = 0
z.a_ = 0
z.b_ = 0
z.c_ = 0
z.d_ = 0
z.e_ = 0
z.h_ = 0
z.l_ = 0
z.f_ = 0
z.i = 0
z.r = 0
z.sf = true
z.zf = true
z.yf = true
z.hf = true
z.xf = true
z.pf = true
z.nf = true
z.cf = true
z.iffDelay = 0
z.interruptMode = 0
z.iff1 = false
z.iff2 = false
z.isHalted = false
z.intPending = false
z.nmiPending = false
z.intData = 0
}

1255
src/z80/c99/opcodes.go Normal file

File diff suppressed because it is too large Load Diff

162
src/z80/c99/opcodesCB.go Normal file
View File

@ -0,0 +1,162 @@
package c99
import log "github.com/sirupsen/logrus"
// executes a CB opcode
func (z *Z80) execOpcodeCB(opcode byte) {
z.cycleCount += 8
z.incR()
// decoding instructions from http://z80.info/decoding.htm#cb
x_ := (opcode >> 6) & 3 // 0b11
y_ := (opcode >> 3) & 7 // 0b111
z_ := opcode & 7 // 0b111
var hl byte
v := byte(0)
reg := &v
switch z_ {
case 0:
reg = &z.b
case 1:
reg = &z.c
case 2:
reg = &z.d
case 3:
reg = &z.e
case 4:
reg = &z.h
case 5:
reg = &z.l
case 6:
hl = z.rb(z.hl())
reg = &hl
case 7:
reg = &z.a
}
switch x_ {
case 0:
// rot[y] r[z]
switch y_ {
case 0:
*reg = z.cbRlc(*reg)
case 1:
*reg = z.cbRrc(*reg)
case 2:
*reg = z.cbRl(*reg)
case 3:
*reg = z.cbRr(*reg)
case 4:
*reg = z.cbSla(*reg)
case 5:
*reg = z.cbSra(*reg)
case 6:
*reg = z.cbSll(*reg)
case 7:
*reg = z.cbSrl(*reg)
}
case 1:
// BIT y, r[z]
z.cbBit(*reg, y_)
// in bit (hl), x/y flags are handled differently:
if z_ == 6 {
z.updateXY(byte(z.memPtr >> 8))
z.cycleCount += 4
}
case 2:
*reg &= ^(1 << y_) // RES y, r[z]
case 3:
*reg |= 1 << y_ // SET y, r[z]
}
if (x_ == 0 || x_ == 2 || x_ == 3) && z_ == 6 {
z.cycleCount += 7
}
if reg == &hl {
z.wb(z.hl(), hl)
}
}
// execOpcodeDcb executes a displaced CB opcode (DDCB or FDCB)
func (z *Z80) execOpcodeDcb(opcode byte, addr uint16) {
val := z.rb(addr)
result := byte(0)
// decoding instructions from http://z80.info/decoding.htm#ddcb
x_ := (opcode >> 6) & 3 // 0b11
y_ := (opcode >> 3) & 7 // 0b111
z_ := opcode & 7 // 0b111
switch x_ {
case 0:
// rot[y] (iz+d)
switch y_ {
case 0:
result = z.cbRlc(val)
case 1:
result = z.cbRrc(val)
case 2:
result = z.cbRl(val)
case 3:
result = z.cbRr(val)
case 4:
result = z.cbSla(val)
case 5:
result = z.cbSra(val)
case 6:
result = z.cbSll(val)
case 7:
result = z.cbSrl(val)
}
case 1:
// bit y,(iz+d)
result = z.cbBit(val, y_)
z.updateXY(byte(addr >> 8))
case 2:
result = val & ^(1 << y_) // res y, (iz+d)
case 3:
result = val | (1 << y_) // set y, (iz+d)
default:
log.Errorf("Unknown XYCB opcode: %02X\n", opcode)
}
// ld r[z], rot[y] (iz+d)
// ld r[z], res y,(iz+d)
// ld r[z], set y,(iz+d)
if x_ != 1 && z_ != 6 {
switch z_ {
case 0:
z.b = result
case 1:
z.c = result
case 2:
z.d = result
case 3:
z.e = result
case 4:
z.h = result
case 5:
z.l = result
// always false
//case 6:
// z.wb(z.hl(), result)
case 7:
z.a = result
}
}
if x_ == 1 {
// bit instructions take 20 cycles, others take 23
z.cycleCount += 20
} else {
z.wb(addr, result)
z.cycleCount += 23
}
}

206
src/z80/c99/opcodesDDFD.go Normal file
View File

@ -0,0 +1,206 @@
package c99
// executes a DD/FD opcode (IZ = IX or IY)
func (z *Z80) execOpcodeDDFD(opcode byte, iz *uint16) {
z.cycleCount += uint32(cyclesDDFD[opcode])
z.incR()
switch opcode {
case 0xE1:
*iz = z.popW() // pop iz
case 0xE5:
z.pushW(*iz) // push iz
case 0xE9:
// jp iz
z.pc = *iz
//z.jump(*iz)
case 0x09:
z.addIZ(iz, z.bc()) // add iz,bc
case 0x19:
z.addIZ(iz, z.de()) // add iz,de
case 0x29:
z.addIZ(iz, *iz) // add iz,iz
case 0x39:
z.addIZ(iz, z.sp) // add iz,sp
case 0x84:
z.a = z.addB(z.a, byte(*iz>>8), false) // add a,izh
case 0x85:
z.a = z.addB(z.a, byte(*iz), false) // add a,izl
case 0x8C:
z.a = z.addB(z.a, byte(*iz>>8), z.cf) // adc a,izh
case 0x8D:
z.a = z.addB(z.a, byte(*iz), z.cf) // adc a,izl
case 0x86:
z.a = z.addB(z.a, z.rb(z.displace(*iz, z.nextB())), false) // add a,(iz+*)
case 0x8E:
z.a = z.addB(z.a, z.rb(z.displace(*iz, z.nextB())), z.cf) // adc a,(iz+*)
case 0x96:
z.a = z.subB(z.a, z.rb(z.displace(*iz, z.nextB())), false) // sub (iz+*)
case 0x9E:
z.a = z.subB(z.a, z.rb(z.displace(*iz, z.nextB())), z.cf) // sbc (iz+*)
case 0x94:
z.a = z.subB(z.a, byte(*iz>>8), false) // sub izh
case 0x95:
z.a = z.subB(z.a, byte(*iz), false) // sub izl
case 0x9C:
z.a = z.subB(z.a, byte(*iz>>8), z.cf) // sbc izh
case 0x9D:
z.a = z.subB(z.a, byte(*iz), z.cf) // sbc izl
case 0xA6:
z.lAnd(z.rb(z.displace(*iz, z.nextB()))) // and (iz+*)
case 0xA4:
z.lAnd(byte(*iz >> 8)) // and izh
case 0xA5:
z.lAnd(byte(*iz)) // and izl
case 0xAE:
z.lXor(z.rb(z.displace(*iz, z.nextB()))) // xor (iz+*)
case 0xAC:
z.lXor(byte(*iz >> 8)) // xor izh
case 0xAD:
z.lXor(byte(*iz)) // xor izl
case 0xB6:
z.lOr(z.rb(z.displace(*iz, z.nextB()))) // or (iz+*)
case 0xB4:
z.lOr(byte(*iz >> 8)) // or izh
case 0xB5:
z.lOr(byte(*iz)) // or izl
case 0xBE:
z.cp(z.rb(z.displace(*iz, z.nextB()))) // cp (iz+*)
case 0xBC:
z.cp(byte(*iz >> 8)) // cp izh
case 0xBD:
z.cp(byte(*iz)) // cp izl
case 0x23:
*iz += 1 // inc iz
case 0x2B:
*iz -= 1 // dec iz
case 0x34:
// inc (iz+*)
addr := z.displace(*iz, z.nextB())
z.wb(addr, z.inc(z.rb(addr)))
case 0x35:
// dec (iz+*)
addr := z.displace(*iz, z.nextB())
z.wb(addr, z.dec(z.rb(addr)))
case 0x24:
*iz = (*iz & 0x00ff) | (uint16(z.inc(byte(*iz>>8))) << 8) // inc izh
case 0x25:
*iz = (*iz & 0x00ff) | (uint16(z.dec(byte(*iz>>8))) << 8) // dec izh
case 0x2C:
*iz = (*iz & 0xff00) | uint16(z.inc(byte(*iz))) // inc izl
case 0x2D:
*iz = (*iz & 0xff00) | uint16(z.dec(byte(*iz))) // dec izl
case 0x2A:
addr := z.nextW()
*iz = z.rw(addr) // ld iz,(**)
z.memPtr = addr + 1
case 0x22:
addr := z.nextW()
z.ww(addr, *iz) // ld (**),iz
z.memPtr = addr + 1
case 0x21:
*iz = z.nextW() // ld iz,**
case 0x36:
// ld (iz+*),*
addr := z.displace(*iz, z.nextB())
z.wb(addr, z.nextB())
case 0x70:
z.wb(z.displace(*iz, z.nextB()), z.b) // ld (iz+*),b
case 0x71:
z.wb(z.displace(*iz, z.nextB()), z.c) // ld (iz+*),c
case 0x72:
z.wb(z.displace(*iz, z.nextB()), z.d) // ld (iz+*),d
case 0x73:
z.wb(z.displace(*iz, z.nextB()), z.e) // ld (iz+*),e
case 0x74:
z.wb(z.displace(*iz, z.nextB()), z.h) // ld (iz+*),h
case 0x75:
z.wb(z.displace(*iz, z.nextB()), z.l) // ld (iz+*),l
case 0x77:
z.wb(z.displace(*iz, z.nextB()), z.a) // ld (iz+*),a
case 0x46:
z.b = z.rb(z.displace(*iz, z.nextB())) // ld b,(iz+*)
case 0x4E:
z.c = z.rb(z.displace(*iz, z.nextB())) // ld c,(iz+*)
case 0x56:
z.d = z.rb(z.displace(*iz, z.nextB())) // ld d,(iz+*)
case 0x5E:
z.e = z.rb(z.displace(*iz, z.nextB())) // ld e,(iz+*)
case 0x66:
z.h = z.rb(z.displace(*iz, z.nextB())) // ld h,(iz+*)
case 0x6E:
z.l = z.rb(z.displace(*iz, z.nextB())) // ld l,(iz+*)
case 0x7E:
z.a = z.rb(z.displace(*iz, z.nextB())) // ld a,(iz+*)
case 0x44:
z.b = byte(*iz >> 8) // ld b,izh
case 0x4C:
z.c = byte(*iz >> 8) // ld c,izh
case 0x54:
z.d = byte(*iz >> 8) // ld d,izh
case 0x5C:
z.e = byte(*iz >> 8) // ld e,izh
case 0x7C:
z.a = byte(*iz >> 8) // ld a,izh
case 0x45:
z.b = byte(*iz) // ld b,izl
case 0x4D:
z.c = byte(*iz) // ld c,izl
case 0x55:
z.d = byte(*iz) // ld d,izl
case 0x5D:
z.e = byte(*iz) // ld e,izl
case 0x7D:
z.a = byte(*iz) // ld a,izl
case 0x60:
*iz = (*iz & 0x00ff) | (uint16(z.b) << 8) // ld izh,b
case 0x61:
*iz = (*iz & 0x00ff) | (uint16(z.c) << 8) // ld izh,c
case 0x62:
*iz = (*iz & 0x00ff) | (uint16(z.d) << 8) // ld izh,d
case 0x63:
*iz = (*iz & 0x00ff) | (uint16(z.e) << 8) // ld izh,e
case 0x64: // ld izh,izh
case 0x65:
*iz = ((*iz & 0x00ff) << 8) | (*iz & 0x00ff) // ld izh,izl
case 0x67:
*iz = (uint16(z.a) << 8) | (*iz & 0x00ff) // ld izh,a
case 0x26:
*iz = (uint16(z.nextB()) << 8) | (*iz & 0x00ff) // ld izh,*
case 0x68:
*iz = (*iz & 0xff00) | uint16(z.b) // ld izl,b
case 0x69:
*iz = (*iz & 0xff00) | uint16(z.c) // ld izl,c
case 0x6A:
*iz = (*iz & 0xff00) | uint16(z.d) // ld izl,d
case 0x6B:
*iz = (*iz & 0xff00) | uint16(z.e) // ld izl,e
case 0x6C:
*iz = (*iz & 0xff00) | (*iz >> 8) // ld izl,izh
case 0x6D: // ld izl,izl
case 0x6F:
*iz = (*iz & 0xff00) | uint16(z.a) // ld izl,a
case 0x2E:
*iz = (*iz & 0xff00) | uint16(z.nextB()) // ld izl,*
case 0xF9:
z.sp = *iz // ld sp,iz
case 0xE3:
// ex (sp),iz
val := z.rw(z.sp)
z.ww(z.sp, *iz)
*iz = val
z.memPtr = val
case 0xCB:
addr := z.displace(*iz, z.nextB())
op := z.nextB()
z.execOpcodeDcb(op, addr)
default:
// any other FD/DD opcode behaves as a non-prefixed opcode:
z.execOpcode(opcode)
// R should not be incremented twice:
z.incR()
}
}

281
src/z80/c99/opcodesED.go Normal file
View File

@ -0,0 +1,281 @@
package c99
import log "github.com/sirupsen/logrus"
// executes a ED opcode
func (z *Z80) execOpcodeED(opcode byte) {
z.cycleCount += uint32(cyclesED[opcode])
z.incR()
switch opcode {
case 0x47:
z.i = z.a // ld i,a
case 0x4F:
z.r = z.a // ld r,a
case 0x57:
// ld a,i
z.a = z.i
z.sf = z.a&0x80 != 0
z.zf = z.a == 0
z.hf = false
z.nf = false
z.pf = z.iff2
z.updateXY(z.a)
case 0x5F:
// ld a,r
z.a = z.r
z.sf = z.a&0x80 != 0
z.zf = z.a == 0
z.hf = false
z.nf = false
z.pf = z.iff2
case 0x45, 0x55, 0x5D, 0x65, 0x6D, 0x75, 0x7D:
// retn
z.iff1 = z.iff2
z.ret()
case 0x4D:
z.ret() // reti
case 0xA0:
z.ldi() // ldi
case 0xB0:
{
z.ldi()
if z.bc() != 0 {
z.pc -= 2
z.cycleCount += 5
z.memPtr = z.pc + 1
}
} // ldir
case 0xA8:
z.ldd() // ldd
case 0xB8:
{
z.ldd()
if z.bc() != 0 {
z.pc -= 2
z.cycleCount += 5
z.memPtr = z.pc + 1
}
} // lddr
case 0xA1:
z.cpi() // cpi
case 0xA9:
z.cpd() // cpd
case 0xB1:
// cpir
z.cpi()
if z.bc() != 0 && !z.zf {
z.pc -= 2
z.cycleCount += 5
z.memPtr = z.pc + 1
} else {
//z.mem_ptr++
}
//z.cpir()
case 0xB9:
// cpdr
z.cpd()
if z.bc() != 0 && !z.zf {
z.pc -= 2
z.cycleCount += 5
z.memPtr = z.pc + 1
} else {
//z.mem_ptr++
}
case 0x40:
z.inRC(&z.b) // in b, (c)
z.memPtr = z.bc() + 1
case 0x48:
z.memPtr = z.bc() + 1
z.inRC(&z.c) // in c, (c)
z.updateXY(z.c)
//case 0x4e:
// ld c,(iy+dd)
case 0x50:
z.inRC(&z.d) // in d, (c)
z.memPtr = z.bc() + 1
case 0x58:
// in e, (c)
z.inRC(&z.e)
z.memPtr = z.bc() + 1
z.updateXY(z.e)
case 0x60:
z.inRC(&z.h) // in h, (c)
z.memPtr = z.bc() + 1
case 0x68:
z.inRC(&z.l) // in l, (c)
z.memPtr = z.bc() + 1
z.updateXY(z.l)
case 0x70:
// in (c)
var val byte
z.inRC(&val)
z.memPtr = z.bc() + 1
case 0x78:
// in a, (c)
z.inRC(&z.a)
z.memPtr = z.bc() + 1
z.updateXY(z.a)
case 0xA2:
z.ini() // ini
case 0xB2:
// inir
z.ini()
if z.b > 0 {
z.pc -= 2
z.cycleCount += 5
}
case 0xAA:
// ind
z.ind()
case 0xBA:
// indr
z.ind()
if z.b > 0 {
z.pc -= 2
z.cycleCount += 5
}
case 0x41:
z.core.IOWrite(z.bc(), z.b) // out (c), b
z.memPtr = z.bc() + 1
case 0x49:
z.core.IOWrite(z.bc(), z.c) // out (c), c
z.memPtr = z.bc() + 1
case 0x51:
z.core.IOWrite(z.bc(), z.d) // out (c), d
z.memPtr = z.bc() + 1
case 0x59:
z.core.IOWrite(z.bc(), z.e) // out (c), e
z.memPtr = z.bc() + 1
case 0x61:
z.core.IOWrite(z.bc(), z.h) // out (c), h
z.memPtr = z.bc() + 1
case 0x69:
z.core.IOWrite(z.bc(), z.l) // out (c), l
z.memPtr = z.bc() + 1
case 0x71:
z.core.IOWrite(z.bc(), 0) // out (c), 0
z.memPtr = z.bc() + 1
case 0x79:
// out (c), a
z.core.IOWrite(z.bc(), z.a)
z.memPtr = z.bc() + 1
case 0xA3:
z.outi() // outi
case 0xB3:
// otir
z.outi()
if z.b > 0 {
z.pc -= 2
z.cycleCount += 5
}
case 0xAB:
z.outd() // outd
case 0xBB:
// otdr
z.outd()
if z.b > 0 {
z.cycleCount += 5
z.pc -= 2
}
case 0x42:
z.sbcHL(z.bc()) // sbc hl,bc
case 0x52:
z.sbcHL(z.de()) // sbc hl,de
case 0x62:
z.sbcHL(z.hl()) // sbc hl,hl
case 0x72:
z.sbcHL(z.sp) // sbc hl,sp
case 0x4A:
z.adcHL(z.bc()) // adc hl,bc
case 0x5A:
z.adcHL(z.de()) // adc hl,de
case 0x6A:
z.adcHL(z.hl()) // adc hl,hl
case 0x7A:
z.adcHL(z.sp) // adc hl,sp
case 0x43:
// ld (**), bc
addr := z.nextW()
z.ww(addr, z.bc())
z.memPtr = addr + 1
case 0x53:
// ld (**), de
addr := z.nextW()
z.ww(addr, z.de())
z.memPtr = addr + 1
case 0x63:
// ld (**), hl
addr := z.nextW()
z.ww(addr, z.hl())
z.memPtr = addr + 1
case 0x73:
// ld (**), hl
addr := z.nextW()
z.ww(addr, z.sp)
z.memPtr = addr + 1
case 0x4B:
// ld bc, (**)
addr := z.nextW()
z.setBC(z.rw(addr))
z.memPtr = addr + 1
case 0x5B:
// ld de, (**)
addr := z.nextW()
z.setDE(z.rw(addr))
z.memPtr = addr + 1
case 0x6B:
// ld hl, (**)
addr := z.nextW()
z.setHL(z.rw(addr))
z.memPtr = addr + 1
case 0x7B:
// ld sp,(**)
addr := z.nextW()
z.sp = z.rw(addr)
z.memPtr = addr + 1
case 0x44, 0x54, 0x64, 0x74, 0x4C, 0x5C, 0x6C, 0x7C:
z.a = z.subB(0, z.a, false) // neg
case 0x46, 0x4e, 0x66, 0x6e:
z.interruptMode = 0 // im 0
case 0x56, 0x76:
z.interruptMode = 1 // im 1
case 0x5E, 0x7E:
z.interruptMode = 2 // im 2
case 0x67:
// rrd
a := z.a
val := z.rb(z.hl())
z.a = (a & 0xF0) | (val & 0xF)
z.wb(z.hl(), (val>>4)|(a<<4))
z.nf = false
z.hf = false
z.updateXY(z.a)
z.zf = z.a == 0
z.sf = z.a&0x80 != 0
z.pf = parity(z.a)
z.memPtr = z.hl() + 1
case 0x6F:
// rld
a := z.a
val := z.rb(z.hl())
z.a = (a & 0xF0) | (val >> 4)
z.wb(z.hl(), (val<<4)|(a&0xF))
z.nf = false
z.hf = false
z.updateXY(z.a)
z.zf = z.a == 0
z.sf = z.a&0x80 != 0
z.pf = parity(z.a)
z.memPtr = z.hl() + 1
default:
log.Errorf("Unknown ED opcode: %02X\n", opcode)
}
}

172
src/z80/cpu.go Normal file
View File

@ -0,0 +1,172 @@
package z80
type MemIoRW interface {
// M1MemRead Read byte from memory for specified address @ M1 cycle
M1MemRead(addr uint16) byte
// MemRead Read byte from memory for specified address
MemRead(addr uint16) byte
// MemWrite Write byte to memory to specified address
MemWrite(addr uint16, val byte)
// IORead Read byte from specified port
IORead(port uint16) byte
// IOWrite Write byte to specified port
IOWrite(port uint16, val byte)
}
type CPUInterface interface {
// Reset CPU to initial state
Reset()
// RunInstruction Run single instruction, return number of CPU cycles
RunInstruction() uint32
// GetState Get current CPU state
GetState() *CPU
// SetState Set current CPU state
SetState(state *CPU)
// DebugOutput out current CPU state
DebugOutput()
}
// FlagsType - Processor flags
type FlagsType struct {
S bool `json:"s,omitempty"`
Z bool `json:"z,omitempty"`
Y bool `json:"y,omitempty"`
H bool `json:"h,omitempty"`
X bool `json:"x,omitempty"`
P bool `json:"p,omitempty"`
N bool `json:"n,omitempty"`
C bool `json:"c,omitempty"`
}
// Z80CPU - Processor state
type CPU struct {
A byte `json:"a,omitempty"`
B byte `json:"b,omitempty"`
C byte `json:"c,omitempty"`
D byte `json:"d,omitempty"`
E byte `json:"e,omitempty"`
H byte `json:"h,omitempty"`
L byte `json:"l,omitempty"`
AAlt byte `json:"AAlt,omitempty"`
BAlt byte `json:"BAlt,omitempty"`
CAlt byte `json:"CAlt,omitempty"`
DAlt byte `json:"DAlt,omitempty"`
EAlt byte `json:"EAlt,omitempty"`
HAlt byte `json:"HAlt,omitempty"`
LAlt byte `json:"LAlt,omitempty"`
IX uint16 `json:"IX,omitempty"`
IY uint16 `json:"IY,omitempty"`
I byte `json:"i,omitempty"`
R byte `json:"r,omitempty"`
SP uint16 `json:"SP,omitempty"`
PC uint16 `json:"PC,omitempty"`
Flags FlagsType `json:"flags"`
FlagsAlt FlagsType `json:"flagsAlt"`
IMode byte `json:"IMode,omitempty"`
Iff1 bool `json:"iff1,omitempty"`
Iff2 bool `json:"iff2,omitempty"`
Halted bool `json:"halted,omitempty"`
DoDelayedDI bool `json:"doDelayedDI,omitempty"`
DoDelayedEI bool `json:"doDelayedEI,omitempty"`
CycleCount uint32 `json:"cycleCount,omitempty"`
InterruptOccurred bool `json:"interruptOccurred,omitempty"`
MemPtr uint16 `json:"memPtr,omitempty"`
//core MemIoRW
}
func (f *FlagsType) GetFlags() byte {
var flags byte = 0
if f.S {
flags |= 0x80
}
if f.Z {
flags |= 0x40
}
if f.Y {
flags |= 0x20
}
if f.H {
flags |= 0x10
}
if f.X {
flags |= 0x08
}
if f.P {
flags |= 0x04
}
if f.N {
flags |= 0x02
}
if f.C {
flags |= 0x01
}
return flags
}
func (f *FlagsType) GetFlagsStr() string {
flags := []byte{'-', '-', '-', '-', '-', '-', '-', '-'}
if f.S {
flags[0] = 'S'
}
if f.Z {
flags[1] = 'Z'
}
if f.Y {
flags[2] = '5'
}
if f.H {
flags[3] = 'H'
}
if f.X {
flags[4] = '3'
}
if f.P {
flags[5] = 'P'
}
if f.N {
flags[6] = 'N'
}
if f.C {
flags[7] = 'C'
}
return string(flags)
}
func (z *CPU) IIFStr() string {
flags := []byte{'-', '-'}
if z.Iff1 {
flags[0] = '1'
}
if z.Iff2 {
flags[1] = '2'
}
return string(flags)
}
func GetFlags(f byte) FlagsType {
return FlagsType{
S: f&0x80 != 0,
Z: f&0x40 != 0,
Y: f&0x20 != 0,
H: f&0x10 != 0,
X: f&0x08 != 0,
P: f&0x04 != 0,
N: f&0x02 != 0,
C: f&0x01 != 0,
}
}
func (f *FlagsType) SetFlags(flags byte) {
f.S = flags&0x80 != 0
f.Z = flags&0x40 != 0
f.Y = flags&0x20 != 0
f.H = flags&0x10 != 0
f.X = flags&0x08 != 0
f.P = flags&0x04 != 0
f.N = flags&0x02 != 0
f.C = flags&0x01 != 0
}
func (z *CPU) GetPC() uint16 {
return z.PC
}

598
src/z80/cpu_test.go Normal file
View File

@ -0,0 +1,598 @@
package z80_test
import (
"bufio"
"bytes"
_ "embed"
"okemu/z80"
"okemu/z80/c99"
"strconv"
"strings"
"testing"
log "github.com/sirupsen/logrus"
)
const (
ScanNone int = iota
ScanDesc
ScanEvent
ScanRegs
ScanState
ScanMem
ScanEnd
)
type Registers struct {
AF uint16
BC uint16
DE uint16
HL uint16
AFa uint16
BCa uint16
DEa uint16
HLa uint16
IX uint16
IY uint16
SP uint16
PC uint16
MemPtr uint16
}
type State struct {
I byte
R byte
IFF1 bool
IFF2 bool
IM byte
isHalted bool
tStates uint16
}
type MemorySetup struct {
addr uint16
values []byte
}
type Event struct {
time uint16
typ string
addr uint16
data byte
}
type Z80TestIn struct {
// description string
registers Registers
state State
memorySetup []MemorySetup
}
type Expect struct {
events []Event
registers Registers
state State
memory []MemorySetup
}
//go:embed tests.in
var testIn []byte
//go:embed tests.expected
var testExpected []byte
type Computer struct {
cpu *c99.Z80
memory [65536]byte
ports [256]byte
}
var z80TestsIn map[string]Z80TestIn
var z80TestsExpected map[string]Expect
var computer Computer
var testNames []string
//var z80 *c99.Z80
func init() {
z80TestsIn = make(map[string]Z80TestIn)
z80TestsExpected = make(map[string]Expect)
parseTestIn()
parseTestExpected()
for addr := 0; addr < 65535; addr++ {
computer.memory[addr] = 0x00
}
for addr := 0; addr < 255; addr++ {
computer.ports[addr] = 0
}
computer.cpu = c99.New(&computer)
}
func (c *Computer) M1MemRead(addr uint16) byte {
return c.memory[addr]
}
func (c *Computer) MemRead(addr uint16) byte {
return c.memory[addr]
}
func (c *Computer) MemWrite(addr uint16, val byte) {
c.memory[addr] = val
}
func (c *Computer) IOWrite(port uint16, val byte) {
c.ports[port&0x00ff] = val
}
func (c *Computer) IORead(port uint16) byte {
return byte(port >> 8) //c.ports[port&0x00ff]
}
func parseTestExpected() {
exScanner := bufio.NewScanner(bytes.NewReader(testExpected))
scanState := ScanNone
testName := ""
var events []Event
registers := Registers{}
state := State{}
var memorySetup []MemorySetup
for exScanner.Scan() {
line := exScanner.Text()
if len(strings.TrimSpace(line)) == 0 {
if scanState == ScanMem {
scanState = ScanEnd
} else {
scanState = ScanNone
continue
}
}
if ScanNone == scanState {
scanState = ScanDesc
} else if len(line) > 0 && line[0] == ' ' {
scanState = ScanEvent
}
//else {
// if scanState == ScanEvent {
// scanState = ScanRegs
// }
//}
switch scanState {
case ScanDesc:
testName = line
scanState = ScanRegs
case ScanEvent:
events = append([]Event{}, *parseEvent(line))
scanState = ScanRegs
case ScanRegs:
registers = *parseRegisters(line)
scanState = ScanState
case ScanState:
state = *parseState(line)
scanState = ScanMem
case ScanMem:
memorySetup = append(memorySetup, *parseMemory(line))
//scanState = ScanMem
case ScanEnd:
z80TestsExpected[testName] = Expect{
events: events,
registers: registers,
state: state,
memory: memorySetup,
}
events = []Event{}
memorySetup = []MemorySetup{}
scanState = ScanNone
default:
panic("unhandled default case")
}
}
}
func parseTestIn() {
inScanner := bufio.NewScanner(bytes.NewReader(testIn))
scanState := ScanNone
testName := ""
registers := Registers{}
state := State{}
var memorySetup []MemorySetup
for inScanner.Scan() {
line := inScanner.Text()
if len(line) == 0 || strings.TrimSpace(line) == "" {
scanState = ScanNone
continue
}
if ScanNone == scanState {
scanState = ScanDesc
} else if line == "-1" {
scanState = ScanEnd
}
switch scanState {
case ScanDesc:
testName = line
scanState = ScanRegs
case ScanRegs:
registers = *parseRegisters(line)
scanState = ScanState
case ScanState:
state = *parseState(line)
scanState = ScanMem
case ScanMem:
memorySetup = append(memorySetup, *parseMemory(line))
//scanState = ScanMem
case ScanEnd:
test := Z80TestIn{
registers: registers,
state: state,
memorySetup: memorySetup,
}
testNames = append(testNames, testName)
z80TestsIn[testName] = test
scanState = ScanNone
default:
panic("unhandled default case")
}
}
}
func parseEvent(event string) *Event {
e := Event{}
line := strings.TrimSpace(event)
//4 MR 0000 00
//012345678
if len(line) < 9 {
log.Errorf("Invalid event: %s", line)
return nil
}
sp := strings.Index(line, " ")
if sp == -1 {
log.Errorf("Invalid event: %s", line)
return nil
}
e.time = parseDecW(line[:sp])
e.typ = line[sp+1 : sp+3]
e.addr = parseHexW(line[sp+4 : sp+8])
if len(line) > sp+9 {
//println("Event: ", line)
e.data = parseHexB(line[sp+9:])
}
return &e
}
func parseMemory(line string) *MemorySetup {
m := MemorySetup{}
//0000 00 -1
//0123456789
m.addr = parseHexW(line[0:4])
mem := line[5:]
for {
st := mem[:2]
if st == "-1" {
break
}
m.values = append(m.values, parseHexB(st))
mem = strings.TrimSpace(mem[2:])
}
return &m
}
func parseRegisters(line string) *Registers {
r := Registers{}
line = strings.TrimSpace(line)
if len(line) != 64 {
log.Errorf("Invalid register line: %s", line)
} else {
// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
for ctr := 0; ctr < 13; ctr++ {
hexString := line[ctr*5 : ctr*5+4]
v, err := strconv.ParseUint(hexString, 16, 16)
if err != nil {
log.Errorf("Invalid register value: %s in line %s", hexString, line)
break
}
switch ctr {
case 0:
r.AF = uint16(v)
case 1:
r.BC = uint16(v)
case 2:
r.DE = uint16(v)
case 3:
r.HL = uint16(v)
case 4:
r.AFa = uint16(v)
case 5:
r.BCa = uint16(v)
case 6:
r.DEa = uint16(v)
case 7:
r.HLa = uint16(v)
case 8:
r.IX = uint16(v)
case 9:
r.IY = uint16(v)
case 10:
r.SP = uint16(v)
case 11:
r.PC = uint16(v)
case 12:
r.MemPtr = uint16(v)
}
}
}
return &r
}
func parseHexB(line string) byte {
v, err := strconv.ParseUint(line, 16, 8)
if err != nil {
log.Errorf("Invalid HexB value: %s", line)
}
return byte(v)
}
func parseHexW(line string) uint16 {
v, err := strconv.ParseUint(line, 16, 16)
if err != nil {
log.Errorf("Invalid HexW value: %s", line)
}
return uint16(v)
}
func parseDecB(line string) byte {
v, err := strconv.ParseUint(line, 10, 8)
if err != nil {
log.Errorf("Invalid B value: %s", line)
}
return byte(v)
}
func parseDecW(line string) uint16 {
v, err := strconv.ParseUint(line, 10, 16)
if err != nil {
log.Errorf("Invalid W value: %s", line)
}
return uint16(v)
}
func parseBoolB(line string) bool {
v, err := strconv.ParseUint(line, 10, 8)
if err != nil {
log.Errorf("Invalid state I value: %s", line)
}
return v != 0
}
func parseState(line string) *State {
s := State{}
line = strings.TrimSpace(line)
if len(line) < 15 {
log.Errorf("Invalid state line: %s", line)
} else {
//00 00 0 0 0 0 1
//0123456789012345678
s.I = parseHexB(line[0:2])
s.R = parseHexB(line[3:5])
s.IFF1 = parseBoolB(line[6:7])
s.IFF2 = parseBoolB(line[8:9])
s.IM = parseDecB(line[10:11])
s.isHalted = parseBoolB(line[12:13])
s.tStates = parseDecW(strings.TrimSpace(line[13:]))
}
return &s
}
func TestZ80Fuse(t *testing.T) {
t.Logf("Fuse-type Z80 emulator test")
computer.cpu.Reset()
for _, name := range testNames {
setComputerState(z80TestsIn[name])
exp, exists := z80TestsExpected[name]
if !exists {
t.Errorf("Expected values for test %s not exists!", name)
return
}
cy := uint32(0)
for {
cy += computer.cpu.RunInstruction()
if cy >= uint32(exp.state.tStates) {
break
}
}
checkComputerState(t, name)
}
}
func setComputerState(test Z80TestIn) {
state := z80.CPU{
A: byte(test.registers.AF >> 8),
B: byte(test.registers.BC >> 8),
C: byte(test.registers.BC),
D: byte(test.registers.DE >> 8),
E: byte(test.registers.DE),
H: byte(test.registers.HL >> 8),
L: byte(test.registers.HL),
AAlt: byte(test.registers.AFa >> 8),
BAlt: byte(test.registers.BCa >> 8),
CAlt: byte(test.registers.BCa),
DAlt: byte(test.registers.DEa >> 8),
EAlt: byte(test.registers.DEa),
HAlt: byte(test.registers.HLa >> 8),
LAlt: byte(test.registers.HLa),
IX: test.registers.IX,
IY: test.registers.IY,
I: test.state.I,
R: test.state.R,
SP: test.registers.SP,
PC: test.registers.PC,
Flags: z80.GetFlags(byte(test.registers.AF)),
FlagsAlt: z80.GetFlags(byte(test.registers.AFa)),
IMode: test.state.IM,
Iff1: test.state.IFF1,
Iff2: test.state.IFF2,
Halted: test.state.isHalted,
DoDelayedDI: false,
DoDelayedEI: false,
CycleCount: 0,
InterruptOccurred: false,
MemPtr: test.registers.MemPtr,
}
// Setup CPU
computer.cpu.SetState(&state)
// Setup Memory
for _, ms := range test.memorySetup {
addr := ms.addr
for _, b := range ms.values {
computer.memory[addr] = b
addr++
}
}
}
func lo(w uint16) byte {
return byte(w)
}
func hi(w uint16) byte {
return byte(w >> 8)
}
func checkComputerState(t *testing.T, name string) {
state := computer.cpu.GetState()
exp, exists := z80TestsExpected[name]
if !exists {
t.Errorf("Expected values for test %s not exists!", name)
return
}
// A,B,C,D,E,H,L
if hi(exp.registers.AF) != state.A {
t.Errorf("%s: Expected A to be %x, got %x", name, hi(exp.registers.AF), state.A)
}
if hi(exp.registers.BC) != state.B {
t.Errorf("%s: Expected B to be %x, got %x", name, hi(exp.registers.BC), state.B)
computer.cpu.DebugOutput()
}
if lo(exp.registers.BC) != state.C {
t.Errorf("%s: Expected C to be %x, got %x", name, lo(exp.registers.BC), state.C)
}
if hi(exp.registers.DE) != state.D {
t.Errorf("%s: Expected D to be %x, got %x", name, hi(exp.registers.DE), state.D)
}
if lo(exp.registers.DE) != state.E {
t.Errorf("%s: Expected E to be %x, got %x", name, lo(exp.registers.DE), state.E)
}
if hi(exp.registers.HL) != state.H {
t.Errorf("%s: Expected H to be %x, got %x", name, hi(exp.registers.HL), state.H)
}
if lo(exp.registers.HL) != state.L {
t.Errorf("%s: Expected L to be %x, got %x", name, lo(exp.registers.BC), state.L)
}
// Alt A,B,C,D,E,H,L
if hi(exp.registers.AFa) != state.AAlt {
t.Errorf("%s: Expected A' to be %x, got %x", name, hi(exp.registers.AFa), state.AAlt)
}
if hi(exp.registers.BCa) != state.BAlt {
t.Errorf("%s: Expected B' to be %x, got %x", name, hi(exp.registers.BCa), state.BAlt)
}
if lo(exp.registers.BCa) != state.CAlt {
t.Errorf("%s: Expected C' to be %x, got %x", name, lo(exp.registers.BCa), state.CAlt)
}
if hi(exp.registers.DEa) != state.DAlt {
t.Errorf("%s: Expected D' to be %x, got %x", name, hi(exp.registers.DEa), state.DAlt)
}
if lo(exp.registers.DEa) != state.EAlt {
t.Errorf("%s: Expected E' to be %x, got %x", name, lo(exp.registers.DEa), state.EAlt)
}
if hi(exp.registers.HLa) != state.HAlt {
t.Errorf("%s: Expected H' to be %x, got %x", name, hi(exp.registers.HLa), state.HAlt)
}
if lo(exp.registers.HLa) != state.LAlt {
t.Errorf("%s: Expected L' to be %x, got %x", name, lo(exp.registers.BCa), state.LAlt)
}
// 16b regs PC, SP, Meme, R, I
if exp.registers.IX != state.IX {
t.Errorf("%s: Expected IX to be %x, got %x", name, exp.registers.IX, state.IX)
}
if exp.registers.IY != state.IY {
t.Errorf("%s: Expected IX to be %x, got %x", name, exp.registers.IX, state.IX)
}
if exp.registers.PC != state.PC {
t.Errorf("%s: Expected PC to be %x, got %x", name, exp.registers.PC, state.PC)
}
if exp.registers.SP != state.SP {
t.Errorf("%s: Expected SP to be %x, got %x", name, exp.registers.SP, state.SP)
}
if exp.registers.MemPtr != state.MemPtr {
t.Errorf("%s: Expected MemPtr to be %x, got %x", name, exp.registers.MemPtr, state.MemPtr)
}
// State
if exp.state.I != state.I {
t.Errorf("%s: Expected I to be %x, got %x", name, exp.state.I, state.I)
}
if exp.state.IM != state.IMode {
t.Errorf("%s: Expected IM to be %d, got %d", name, exp.state.IM, state.IMode)
}
if exp.state.IFF1 != state.Iff1 {
t.Errorf("%s: Expected IIF1 to be %t, got %t", name, exp.state.IFF1, state.Iff1)
}
if exp.state.IFF2 != state.Iff2 {
t.Errorf("%s: Expected IIF2 to be %t, got %t", name, exp.state.IFF2, state.Iff2)
}
if exp.state.isHalted != state.Halted {
t.Errorf("%s: Expected isHalted to be %t, got %t", name, exp.state.isHalted, state.Halted)
}
// FLAGS
if lo(exp.registers.AF) != state.Flags.GetFlags() {
t.Errorf("%s: Expected Flags to be %08b, got %08b", name, lo(exp.registers.AF), state.Flags.GetFlags())
}
if lo(exp.registers.AFa) != state.FlagsAlt.GetFlags() {
t.Errorf("%s: Expected Flags' to be %08b, got %08b", name, lo(exp.registers.AFa), state.FlagsAlt.GetFlags())
}
// Check memory
for _, mExpect := range exp.memory {
addr := mExpect.addr
for _, b := range mExpect.values {
if computer.memory[addr] != b {
t.Errorf("%s: Expected memory[%x] to be %x, got %x", name, addr, b, computer.memory[addr])
}
addr++
}
}
}

672
src/z80/dis/z80disasm.go Normal file
View File

@ -0,0 +1,672 @@
package dis
import (
"fmt"
"okemu/z80"
"strings"
)
type Disassembler struct {
pc uint16
core z80.MemIoRW
}
type Disassembly interface {
Disassm(pc uint16) string
}
func NewDisassembler(core z80.MemIoRW) *Disassembler {
d := Disassembler{
pc: 0,
core: core,
}
return &d
}
// opcode & 0x07
var operands = []string{"B", "C", "D", "E", "H", "L", "(HL)", "A"}
var aluOp = []string{"ADD A" + sep, "ADC A" + sep, "SUB ", "SBC A" + sep, "AND ", "XOR ", "OR ", "CP "}
const sep = ", "
func (d *Disassembler) jp(op, cond string) string {
addr := d.getW()
if cond != "" {
cond += sep
}
return fmt.Sprintf("%s %s%s", op, cond, addr)
}
func (d *Disassembler) jr(op, cond string) string {
addr := d.pc
offset := d.getByte()
if offset&0x80 != 0 {
addr += 0xFF00 | uint16(offset)
} else {
addr += d.pc + uint16(offset)
}
if cond != "" {
cond += sep
}
return fmt.Sprintf("%s %s0x%04X", op, cond, addr)
}
func (d *Disassembler) getByte() byte {
b := d.core.MemRead(d.pc)
d.pc++
return b
}
func (d *Disassembler) Disassm(pc uint16) string {
d.pc = pc
result := fmt.Sprintf(" %04X ", d.pc)
op := d.getByte()
switch {
// == 00:0F
case op == 0x00:
result += "NOP"
case op == 0x01:
result += "LD BC" + sep + d.getW()
case op == 0x02:
result += "LD (BC)" + sep + "A"
case op == 0x03:
result += "INC BC"
case op == 0x04:
result += "INC B"
case op == 0x05:
result += "DEC B"
case op == 0x06:
result += "LD B" + sep + d.getB()
case op == 0x07:
result += "RLCA"
case op == 0x08:
result += "EX AF, AF'"
case op == 0x09:
result += "ADD HL" + sep + "BC"
case op == 0x0A:
result += "LD A" + sep + "(BC)"
case op == 0x0B:
result += "DEC BC"
case op == 0x0C:
result += "INC C"
case op == 0x0D:
result += "DEC C"
case op == 0x0E:
result += "LD C" + sep + d.getB()
case op == 0x0F:
result += "RRCA"
// 10:1F
case op == 0x10:
// DJNZ rel
result += d.jr("DJNZ", "")
case op == 0x11:
result += "LD DE" + sep + d.getW()
case op == 0x12:
result += "LD (DE)" + sep + "A"
case op == 0x13:
result += "INC DE"
case op == 0x14:
result += "INC D"
case op == 0x15:
result += "DEC D"
case op == 0x16:
result += "LD D" + sep + d.getB()
case op == 0x17:
result += "RLA"
case op == 0x18:
result += d.jr("JR", "")
case op == 0x19:
result += "ADD HL" + sep + "DE"
case op == 0x1A:
result += "LD A" + sep + "(DE)"
case op == 0x1B:
result += "DEC DE"
case op == 0x1C:
result += "INC E"
case op == 0x1D:
result += "DEC E"
case op == 0x1E:
result += "LD E" + sep + d.getB()
case op == 0x1F:
result += "RRA"
// == 20:2F
case op == 0x20:
result += d.jr("JR", "NZ")
case op == 0x21:
result += "LD HL" + sep + d.getW()
case op == 0x22:
// LD (nn),HL
result += "LD (" + d.getW() + ")" + sep + "HL"
case op == 0x23:
result += "INC HL"
case op == 0x24:
result += "INC H"
case op == 0x25:
result += "DEC H"
case op == 0x26:
result += "LD H" + sep + d.getB()
case op == 0x27:
result += "DAA"
case op == 0x28:
result += d.jr("JR", "Z")
case op == 0x29:
result += "ADD HL" + sep + "HL"
case op == 0x2A:
result += "LD HL" + sep + "(" + d.getW() + ")"
case op == 0x2B:
result += "DEC HL"
case op == 0x2C:
result += "INC L"
case op == 0x2D:
result += "DEC L"
case op == 0x2E:
result += "LD L" + sep + d.getB()
case op == 0x2F:
result += "CPL"
// == 30:3F
case op == 0x30:
result += d.jr("JR", "NC")
case op == 0x31:
result += "LD SP" + sep + d.getW()
case op == 0x32:
result += "LD (" + d.getW() + ")" + sep + "A"
case op == 0x33:
result += "INC SP"
case op == 0x34:
result += "INC (HL)"
case op == 0x35:
result += "DEC (HL)"
case op == 0x36:
result += "LD (HL)" + sep + d.getB()
case op == 0x37:
result += "SCF"
case op == 0x38:
result += d.jr("JR", "C")
case op == 0x39:
result += "ADD HL" + sep + "SP"
case op == 0x3A:
result += "LD A" + sep + "(" + d.getW() + ")"
case op == 0x3B:
result += "DEC SP"
case op == 0x3C:
result += "INC A"
case op == 0x3D:
result += "DEC A"
case op == 0x3E:
result += "LD A" + sep + d.getB()
case op == 0x3F:
result += "CCF"
case op == 0x76:
result += "HALT"
case op >= 0x40 && op <= 0x7F:
// LD op8, op8
result += "LD " + operands[(op>>3)&0x07] + sep + operands[op&0x07]
case op >= 0x80 && op <= 0xBF:
// ALU op8
result += aluOp[(op>>3)&0x07] + operands[op&0x07]
case op == 0xc0:
result += "RET NZ"
case op == 0xc1:
result += "POP BC"
case op == 0xc2:
result += d.jp("JP", "NZ")
case op == 0xc3:
result += d.jp("JP", "")
case op == 0xc4:
result += d.jp("CALL", "NZ")
case op == 0xc5:
result += "PUSH BC"
case op == 0xc6:
result += "ADD A" + sep + d.getB()
case op == 0xc7 || op == 0xd7 || op == 0xe7 || op == 0xf7 || op == 0xcf || op == 0xdf || op == 0xef || op == 0xff:
// RST nnH
result += fmt.Sprintf("RST %d%dH", (op>>4)&3, (op&1)*8)
case op == 0xc8:
result += "RET Z"
case op == 0xc9:
result += "RET"
case op == 0xca:
result += d.jp("JP", "Z")
case op == 0xcb:
result += d.opocodeCB()
case op == 0xcc:
result += d.jp("CALL", "Z")
case op == 0xcd:
result += d.jp("CALL", "")
case op == 0xce:
result += "ADC A" + sep + d.getB()
case op == 0xd0:
result += "RET NC"
case op == 0xd1:
result += "POP DE"
case op == 0xd2:
result += d.jp("JP", "NC")
case op == 0xd3:
result += "OUT (" + d.getB() + ")" + sep + "A"
case op == 0xd4:
result += d.jp("CALL", "NC")
case op == 0xd5:
result += "PUSH DE"
case op == 0xd6:
result += "SUB " + d.getB()
case op == 0xd8:
result += "RET C"
case op == 0xd9:
result += "EXX"
case op == 0xda:
result += d.jp("JP", "C")
case op == 0xdb:
result += "IN A" + sep + " (" + d.getB() + ")"
case op == 0xdc:
result += d.jp("CALL", "C")
case op == 0xdd:
result += d.opocodeDD(op)
case op == 0xde:
result += "SBC A" + sep + d.getB()
case op == 0xe0:
result += "RET PO"
case op == 0xe1:
result += "POP HL"
case op == 0xe2:
result += d.jp("JP", "PO")
case op == 0xe3:
result += "EX (SP)" + sep + "HL"
case op == 0xe4:
result += d.jp("CALL", "PO")
case op == 0xe5:
result += "PUSH HL"
case op == 0xe6:
result += "AND " + d.getB()
case op == 0xe8:
result += "RET PE"
case op == 0xe9:
result += "JP (HL)"
case op == 0xea:
result += d.jp("JP", "PE")
case op == 0xeb:
result += "EX DE" + sep + "HL"
case op == 0xec:
result += d.jp("CALL", "PE")
case op == 0xed:
result += d.opocodeED()
case op == 0xee:
result += "XOR " + d.getB()
case op == 0xf0:
result += "RET P"
case op == 0xf1:
result += "POP AF"
case op == 0xf2:
result += d.jp("JP", "P")
case op == 0xf3:
result += "DI"
case op == 0xf4:
result += d.jp("CALL", "P")
case op == 0xf5:
result += "PUSH AF"
case op == 0xf6:
result += "OR " + d.getB()
case op == 0xf8:
result += "RET M"
case op == 0xf9:
result += "LD SP" + sep + "HL"
case op == 0xfa:
result += d.jp("JP", "M")
case op == 0xfb:
result += "EI"
case op == 0xfc:
result += d.jp("CALL", "M")
case op == 0xfd:
result += d.opocodeDD(op)
case op == 0xfe:
result += "CP " + d.getB()
default:
// All unknown as DB
result += fmt.Sprintf("DB 0x%02X", op)
}
return result
}
func (d *Disassembler) getW() string {
lo := d.core.MemRead(d.pc)
d.pc++
hi := d.core.MemRead(d.pc)
d.pc++
return fmt.Sprintf("0x%02X%02X", hi, lo)
}
func (d *Disassembler) getB() string {
lo := d.core.MemRead(d.pc)
d.pc++
return fmt.Sprintf("0x%02X", lo)
}
func (d *Disassembler) getRel() string {
offset := d.core.MemRead(d.pc)
var sign string
if int8(offset) < 0 {
sign = "-"
} else {
sign = "+"
}
return sign + fmt.Sprintf("0x%02X", offset&0x7F)
}
var shiftOps = []string{"RLC", "RRC", "RL", "RR", "SLA", "SRA", "SLL", "SRL"}
var bitOps = []string{"BIT", "RES", "SET"}
// opocodeCB disassemble Z80 Opcodes, with CB first byte
func (d *Disassembler) opocodeCB() string {
op := ""
opcode := d.getByte()
if opcode <= 0x3F {
op = shiftOps[opcode>>3&0x07] + operands[opcode&0x7]
} else {
op = shiftOps[(opcode>>6&0x03)-1] + operands[opcode&0x7]
}
return op
}
func (d *Disassembler) opocodeDD(op byte) string {
opcode := d.getByte()
result := ""
switch opcode {
case 0x09:
result = "ADD ii" + sep + "BC"
case 0x19:
result = "ADD ii" + sep + "DE"
case 0x21:
result = "LD ii" + sep + d.getW()
case 0x22:
result = "LD (" + d.getW() + ")" + sep + "ii"
case 0x23:
result = "INC ii"
case 0x24:
result = "INC IXH"
case 0x25:
result = "DEC IXH"
case 0x26:
result = "LD IXH" + sep + "n"
case 0x29:
result = "ADD ii" + sep + "ii"
case 0x2A:
result = "LD ii" + sep + "(" + d.getW() + ")"
case 0x2B:
result = "DEC ii"
case 0x34:
result = "INC (ii" + d.getRel() + ")"
case 0x35:
result = "DEC (ii" + d.getRel() + ")"
case 0x36:
result = "LD (ii" + d.getRel() + ")" + sep + "n"
case 0x39:
result = "ADD ii" + sep + "SP"
case 0x46:
result = "LD B" + sep + "(ii" + d.getRel() + ")"
case 0x4E:
result = "LD C" + sep + "(ii" + d.getRel() + ")"
case 0x56:
result = "LD D" + sep + "(ii" + d.getRel() + ")"
case 0x5E:
result = "LD E" + sep + "(ii" + d.getRel() + ")"
case 0x66:
result = "LD H" + sep + "(ii" + d.getRel() + ")"
case 0x6E:
result = "LD L" + sep + "(ii" + d.getRel() + ")"
case 0x70:
result = "LD (ii" + d.getRel() + ")" + sep + "B"
case 0x71:
result = "LD (ii" + d.getRel() + ")" + sep + "C"
case 0x72:
result = "LD (ii" + d.getRel() + ")" + sep + "D"
case 0x73:
result = "LD (ii" + d.getRel() + ")" + sep + "E"
case 0x74:
result = "LD (ii" + d.getRel() + ")" + sep + "H"
case 0x75:
result = "LD (ii" + d.getRel() + ")" + sep + "L"
case 0x77:
result = "LD (ii" + d.getRel() + ")" + sep + "A"
case 0x7E:
result = "LD A" + sep + "(ii" + d.getRel() + ")"
case 0x86:
result = "ADD A" + sep + "(ii" + d.getRel() + ")"
case 0x8E:
result = "ADC A" + sep + "(ii" + d.getRel() + ")"
case 0x96:
result = "SUB (ii" + d.getRel() + ")"
case 0x9E:
result = "SBC A" + sep + "(ii" + d.getRel() + ")"
case 0xA6:
result = "AND (ii" + d.getRel() + ")"
case 0xAE:
result = "XOR (ii" + d.getRel() + ")"
case 0xB6:
result = "OR (ii" + d.getRel() + ")"
case 0xBE:
result = "CP (ii" + d.getRel() + ")"
case 0xCB:
result = d.opocodeDDCB(op, opcode)
case 0xE1:
result = "POP ii"
case 0xE3:
result = "EX (SP)" + sep + "ii"
case 0xE5:
result = "PUSH ii"
case 0xE9:
result = "JP (ii)"
case 0xF9:
result = "LD SP" + sep + "ii"
default:
return fmt.Sprintf("DB 0x%02X, 0x%02X", op, opcode)
}
reg := "IX"
if op == 0xFD {
reg = "IY"
}
return strings.ReplaceAll(result, "ii", reg)
}
func (d *Disassembler) opocodeDDCB(op1 byte, op2 byte) string {
opcode := d.getByte()
result := ""
switch opcode {
case 0x06:
result = "RLC (ii" + d.getRel() + ")"
case 0x0E:
result = "RRC (ii" + d.getRel() + ")"
case 0x16:
result = "RL (ii" + d.getRel() + ")"
case 0x1E:
result = "RR (ii" + d.getRel() + ")"
case 0x26:
result = "SLA (ii" + d.getRel() + ")"
case 0x2E:
result = "SRA (ii" + d.getRel() + ")"
case 0x3E:
result = "SRL (ii" + d.getRel() + ")"
case 0x46:
result = "BIT 0" + sep + "(ii" + d.getRel() + ")"
case 0x4E:
result = "BIT 1" + sep + "(ii" + d.getRel() + ")"
case 0x56:
result = "BIT 2" + sep + "(ii" + d.getRel() + ")"
case 0x5E:
result = "BIT 3" + sep + "(ii" + d.getRel() + ")"
case 0x66:
result = "BIT 4" + sep + "(ii" + d.getRel() + ")"
case 0x6E:
result = "BIT 5" + sep + "(ii" + d.getRel() + ")"
case 0x76:
result = "BIT 6" + sep + "(ii" + d.getRel() + ")"
case 0x7E:
result = "BIT 7" + sep + "(ii" + d.getRel() + ")"
case 0x86:
result = "RES 0" + sep + "(ii" + d.getRel() + ")"
case 0x8E:
result = "RES 1" + sep + "(ii" + d.getRel() + ")"
case 0x96:
result = "RES 2" + sep + "(ii" + d.getRel() + ")"
case 0x9E:
result = "RES 3" + sep + "(ii" + d.getRel() + ")"
case 0xA6:
result = "RES 4" + sep + "(ii" + d.getRel() + ")"
case 0xAE:
result = "RES 5" + sep + "(ii" + d.getRel() + ")"
case 0xB6:
result = "RES 6" + sep + "(ii" + d.getRel() + ")"
case 0xBE:
result = "RES 7" + sep + "(ii" + d.getRel() + ")"
case 0xC6:
result = "SET 0" + sep + "(ii" + d.getRel() + ")"
case 0xCE:
result = "SET 1" + sep + "(ii" + d.getRel() + ")"
case 0xD6:
result = "SET 2" + sep + "(ii" + d.getRel() + ")"
case 0xDE:
result = "SET 3" + sep + "(ii" + d.getRel() + ")"
case 0xE6:
result = "SET 4" + sep + "(ii" + d.getRel() + ")"
case 0xEE:
result = "SET 5" + sep + "(ii" + d.getRel() + ")"
case 0xF6:
result = "SET 6" + sep + "(ii" + d.getRel() + ")"
case 0xFE:
result = "SET 7" + sep + "(ii" + d.getRel() + ")"
default:
result = fmt.Sprintf("DB 0x%02X, 0x%02X, 0x%02X", op1, op2, opcode)
}
return result
}
func (d *Disassembler) opocodeED() string {
opcode := d.getByte()
result := ""
switch opcode {
case 0x40:
result = "IN B" + sep + "(C)"
case 0x41:
result = "OUT (C)" + sep + "B"
case 0x42:
result = "SBC HL" + sep + "BC"
case 0x43:
result = "LD (" + d.getW() + ")" + sep + "BC"
case 0x44, 0x4C, 0x54, 0x5C, 0x64, 0x6C, 0x74, 0x7C:
result = "NEG"
case 0x45, 0x55, 0x5D, 0x65, 0x6D, 0x75, 0x7D:
result = "RETN"
case 0x46, 0x4E, 0x66, 0x6E:
result = "IM 0"
case 0x47:
result = "LD I" + sep + "A"
case 0x48:
result = "IN C" + sep + "(C)"
case 0x49:
result = "OUT (C)" + sep + "C"
case 0x4A:
result = "ADC HL" + sep + "BC"
case 0x4B:
result = "LD BC" + sep + "(" + d.getW() + ")"
case 0x4D:
result = "REТI"
case 0x4F:
result = "LD R" + sep + "A"
case 0x50:
result = "IN D" + sep + "(C)"
case 0x51:
result = "OUT (C)" + sep + "D"
case 0x52:
result = "SBC HL" + sep + "DE"
case 0x53:
result = "LD (nn)" + sep + "DE"
case 0x56, 0x76:
result = "IM 1"
case 0x57:
result = "LD A" + sep + "I"
case 0x58:
result = "IN E" + sep + "(C)"
case 0x59:
result = "OUT (C)" + sep + "E"
case 0x5A:
result = "ADC HL" + sep + "DE"
case 0x5B:
result = "LD DE" + sep + "(" + d.getW() + ")"
case 0x5E, 0x7E:
result = "IM 2"
case 0x5F:
result = "LD A" + sep + "R"
case 0x60:
result = "IN H" + sep + "(C)"
case 0x61:
result = "OUT (C)" + sep + "H"
case 0x62:
result = "SBC HL" + sep + "HL"
case 0x63:
result = "LD (nn)" + sep + "HL"
case 0x67:
result = "RRD"
case 0x68:
result = "IN L" + sep + " (C)"
case 0x69:
result = "OUT (C)" + sep + "L"
case 0x6A:
result = "ADC HL" + sep + " HL"
case 0x6B:
result = "LD HL" + sep + " (nn)"
case 0x6F:
result = "RLD"
case 0x70:
result = "INF"
case 0x71:
result = "OUT (C)" + sep + " 0"
case 0x72:
result = "SBC HL" + sep + "SP"
case 0x73:
result = "LD (nn)" + sep + "SP"
case 0x78:
result = "IN A" + sep + "(C)"
case 0x79:
result = "OUT (C)" + sep + "A"
case 0x7A:
result = "ADC HL" + sep + "SP"
case 0x7B:
result = "LD SP" + sep + "(" + d.getW() + ")"
case 0xA0:
result = "LDI"
case 0xA1:
result = "CPI"
case 0xA2:
result = "INI"
case 0xA3:
result = "OUTI"
case 0xA8:
result = "LDD"
case 0xA9:
result = "CPD"
case 0xAA:
result = "IND"
case 0xAB:
result = "OUTD"
case 0xB0:
result = "LDIR"
case 0xB1:
result = "CPIR"
case 0xB2:
result = "INIR"
case 0xB3:
result = "OTIR"
case 0xB8:
result = "LDDR"
case 0xB9:
result = "CPDR"
case 0xBA:
result = "INDR"
case 0xBB:
result = "OTDR"
default:
result = fmt.Sprintf("DB 0xED, 0x%02X", opcode)
}
return result
}

View File

@ -0,0 +1,91 @@
package dis
import "testing"
var disasm *Disassembler
type TestComp struct {
memory [65536]byte
}
func (t *TestComp) M1MemRead(addr uint16) byte {
return t.memory[addr]
}
func (t *TestComp) MemRead(addr uint16) byte {
return t.memory[addr]
}
func (t *TestComp) MemWrite(addr uint16, val byte) {
t.memory[addr] = val
}
func (t *TestComp) IORead(port uint16) byte {
return byte(port >> 8)
}
func (t *TestComp) IOWrite(port uint16, val byte) {
//
}
var testComp *TestComp
func init() {
testComp = &TestComp{}
for i := 0; i < 65536; i++ {
testComp.memory[i] = 0x3f
}
disasm = NewDisassembler(testComp)
}
func setMemory(addr uint16, value []byte) {
for i := 0; i < len(value); i++ {
testComp.memory[addr+uint16(i)] = value[i]
}
}
var test = []byte{0x31, 0x2c, 0x05, 0x11, 0x0e, 0x01, 0x0e, 0x09, 0xcd, 0x05, 0x00, 0xc3, 0x00, 0x00}
func Test_LD_SP_nn(t *testing.T) {
expected := " 0100 LD SP, 0x052C"
setMemory(0x100, test)
res := disasm.Disassm(0x100)
if res != expected {
t.Errorf("Error disasm LD SP, nn, result '%s', expected '%s'", res, expected)
}
}
func Test_LD_DE_nn(t *testing.T) {
expected := " 0103 LD DE, 0x010E"
setMemory(0x100, test)
res := disasm.Disassm(0x103)
if res != expected {
t.Errorf("Error disasm LD DE, nn, result '%s', expected '%s'", res, expected)
}
}
func Test_LD_C_n(t *testing.T) {
expected := " 0106 LD C, 0x09"
setMemory(0x100, test)
res := disasm.Disassm(0x106)
if res != expected {
t.Errorf("Error disasm LD C, n, result '%s', expected '%s'", res, expected)
}
}
func Test_CALL_nn(t *testing.T) {
expected := " 0108 CALL 0x0005"
setMemory(0x100, test)
res := disasm.Disassm(0x108)
if res != expected {
t.Errorf("Error disasm CALL nn, result '%s', expected '%s'", res, expected)
}
}
func Test_JP_nn(t *testing.T) {
expected := " 010B JP 0x0000"
setMemory(0x100, test)
res := disasm.Disassm(0x10b)
if res != expected {
t.Errorf("Error disasm JP nn, result '%s', expected '%s'", res, expected)
}
}

18913
src/z80/tests.expected Normal file

File diff suppressed because it is too large Load Diff

9153
src/z80/tests.in Normal file

File diff suppressed because it is too large Load Diff