mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
249 lines
6.0 KiB
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()
|
|
//}
|