Ocean-240.2-Emulator/src/main.go

249 lines
6.0 KiB
Go

package main
import (
"context"
_ "embed"
"fmt"
"math"
"okemu/config"
"okemu/debug"
"okemu/debug/zrcp"
"okemu/forms"
"okemu/logger"
"okemu/okean240"
"runtime"
"sync/atomic"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
"github.com/loov/hrtime"
"github.com/romychs/z80go/dis"
log "github.com/sirupsen/logrus"
)
var Version = "v1.0.1"
var BuildTime = "2026-04-01"
const defaultTimerClkPeriod = 430
const defaultCpuClkPeriod = 311
const windowsTimerClkPeriod = 230
const windowsCpuClkPeriod = 111
////go:embed hex/m80.hex
//var serialBytes []byte
////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)
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)
if runtime.GOOS == "windows" {
cpuClkPeriod.Store(windowsCpuClkPeriod)
timerClkPeriod.Store(windowsTimerClkPeriod)
} else {
cpuClkPeriod.Store(defaultCpuClkPeriod)
timerClkPeriod.Store(defaultTimerClkPeriod)
}
debugger := debug.NewDebugger()
computer := okean240.NewComputer(conf, debugger)
computer.AutoLoadFloppy()
disassm := dis.NewDisassembler(computer)
w, raster, label := forms.NewMainWindow(computer, conf, "Океан 240.2 "+Version)
//dezog := dzrp.NewDZRP(conf, debugger, disassm, computer)
dezog := zrcp.NewZRCP(conf, debugger, disassm, computer)
go cpuClock(computer, dezog)
go timerClock(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(computer, cpuFreq, timerFreq)
log.Debugf("Cpu clk period: %d, Timer clock period: %d, frame time: %1.3fms", cpuClkPeriod.Load(), timerClkPeriod.Load(), period/50.0)
//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(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)
}
}
}
// calcDelta calculate step to change period
func calcDelta(currentFreq float64, destFreq float64) int64 {
delta := int64(math.Round(math.Abs(destFreq-currentFreq) * 100))
if delta < 1 {
return 1
} else if delta > 10 {
return 10
}
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",
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) {
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(computer *okean240.ComputerType, dezog debug.DEZOG) {
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 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)
}
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()
//}