Ocean-240.2-Emulator/main.go

234 lines
5.7 KiB
Go

package main
import (
"context"
_ "embed"
"fmt"
"math"
"okemu/config"
"okemu/debug"
"okemu/debug/zrcp"
"okemu/logger"
"okemu/okean240"
"okemu/z80/dis"
"runtime"
"sync/atomic"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
"github.com/loov/hrtime"
)
var Version = "v1.0.0"
var BuildTime = "2026-03-01"
////go:embed hex/m80.hex
//var serialBytes []byte
//go:embed bin/PIP.COM
var ramBytes []byte
////go:embed bin/LINK.COM
//var ramBytes2 []byte
var needReset = false
var fullSpeed atomic.Bool
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()
// base log init
logger.InitLogging()
// load config yml file
config.LoadConfig()
conf := config.GetConfig()
// Reconfigure logging by config values
// logger.ReconfigureLogging(conf)
debugger := debug.NewDebugger()
computer := okean240.NewComputer(conf, debugger)
//computer.SetSerialBytes(serialBytes)
computer.AutoLoadFloppy()
disassm := dis.NewDisassembler(computer)
w, raster, label := mainWindow(computer, conf)
//dezog := dzrp.NewDZRP(conf, debugger, disassm, computer)
dezog := zrcp.NewZRCP(conf, debugger, disassm, computer)
go cpuClock(ctx, computer, dezog)
go timerClock(ctx, computer)
go screen(ctx, computer, raster, label)
if conf.Debugger.Enabled {
go dezog.SetupTcpHandler()
}
(*w).ShowAndRun()
computer.AutoSaveFloppy()
}
func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) {
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
frame := 0
var pre uint64 = 0
var preTim uint64 = 0
var cpuFreq float64 = 0
var timerFreq float64 = 0
timeStart := hrtime.Now()
for {
select {
case <-ticker.C:
frame++
// redraw screen here
fyne.Do(func() {
// status for every 50 frames
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(cpuFreq, timerFreq)
//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()
}
raster.Refresh()
})
case <-ctx.Done():
return
}
}
}
// adjustPeriods Adjust periods for CPU and Timer clock frequencies
func adjustPeriods(cpuFreq float64, timerFreq float64) {
// adjust cpu clock if not full speed
if !fullSpeed.Load() {
if cpuFreq > okean240.CPUFrequencyHi && cpuClkPeriod.Load() < defaultCpuClkPeriod+defaultCpuClkPeriod/2 {
cpuClkPeriod.Add(1)
} else if cpuFreq < okean240.CPUFrequencyLow && cpuClkPeriod.Load() > 3 {
cpuClkPeriod.Add(-1)
}
}
// 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)
}
}
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", computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
}
var timerTicks atomic.Uint64
const defaultTimerClkPeriod = 430 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
const defaultCpuClkPeriod = 311 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
var timerClkPeriod atomic.Int64 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
var cpuClkPeriod atomic.Int64 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
//var timerTicker *time.Ticker
//var cpuTicker *time.Ticker
func timerClock(ctx context.Context, computer *okean240.ComputerType) {
timerClkPeriod.Store(defaultTimerClkPeriod)
timeStart := hrtime.Now()
for {
elapsed := hrtime.Since(timeStart)
if int64(elapsed) > timerClkPeriod.Load() {
timeStart = hrtime.Now()
computer.TimerClk()
timerTicks.Add(1)
runtime.Gosched()
}
}
}
var cpuTicks atomic.Uint64
func cpuClock(ctx context.Context, computer *okean240.ComputerType, dezog debug.DEZOG) {
cpuClkPeriod.Store(defaultCpuClkPeriod)
cpuTicks.Store(0)
nextTick := uint64(0)
var bp uint16
var bpType byte
timeStart := hrtime.Now()
for {
elapsed := hrtime.Since(timeStart)
if int64(elapsed) >= cpuClkPeriod.Load() {
timeStart = hrtime.Now()
bp = 0
bpType = 0
// 2.5MHz frequency
cpuTicks.Add(1)
if fullSpeed.Load() {
// 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)
}
if needReset {
computer.Reset()
needReset = false
}
}
}
}