mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
Redo main cycle
This commit is contained in:
parent
c1616dfbd6
commit
a74841b048
69
appWindow.go
69
appWindow.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"okemu/config"
|
||||||
"okemu/okean240"
|
"okemu/okean240"
|
||||||
"okemu/okean240/fdc"
|
"okemu/okean240/fdc"
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ import (
|
|||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster, *widget.Label) {
|
func mainWindow(computer *okean240.ComputerType, config *config.OkEmuConfig) (*fyne.Window, *canvas.Raster, *widget.Label) {
|
||||||
emulatorApp := app.New()
|
emulatorApp := app.New()
|
||||||
w := emulatorApp.NewWindow("Океан 240.2")
|
w := emulatorApp.NewWindow("Океан 240.2")
|
||||||
w.Canvas().SetOnTypedKey(
|
w.Canvas().SetOnTypedKey(
|
||||||
@ -52,7 +53,7 @@ func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster,
|
|||||||
w.Resize(fyne.NewSize(600, 600))
|
w.Resize(fyne.NewSize(600, 600))
|
||||||
|
|
||||||
vBox := container.NewVBox(
|
vBox := container.NewVBox(
|
||||||
newToolbar(computer, w),
|
newToolbar(computer, w, emulatorApp, config),
|
||||||
centerRaster,
|
centerRaster,
|
||||||
label,
|
label,
|
||||||
)
|
)
|
||||||
@ -62,45 +63,75 @@ func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster,
|
|||||||
return &w, raster, label
|
return &w, raster, label
|
||||||
}
|
}
|
||||||
|
|
||||||
func newToolbar(c *okean240.ComputerType, w fyne.Window) *fyne.Container {
|
func newToolbar(c *okean240.ComputerType, w fyne.Window, a fyne.App, config *config.OkEmuConfig) *fyne.Container {
|
||||||
hBox := container.NewHBox()
|
hBox := container.NewHBox()
|
||||||
for d := 0; d < fdc.TotalDrives; d++ {
|
for d := 0; d < fdc.TotalDrives; d++ {
|
||||||
hBox.Add(widget.NewLabel(string(rune(66+d)) + ":"))
|
hBox.Add(widget.NewLabel(string(rune(66+d)) + ":"))
|
||||||
hBox.Add(widget.NewToolbar(
|
hBox.Add(widget.NewToolbar(
|
||||||
widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
|
widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
|
||||||
err := c.SaveFloppy(fdc.FloppyB)
|
err := c.SaveFloppy(byte(d))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialog.ShowError(err, w)
|
dialog.ShowError(err, w)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
//widget.NewToolbarSpacer(),
|
//widget.NewToolbarSpacer(),
|
||||||
widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
|
widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
|
||||||
err := c.SaveFloppy(fdc.FloppyC)
|
err := c.LoadFloppy(byte(d))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialog.ShowError(err, w)
|
dialog.ShowError(err, w)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
|
||||||
hBox.Add(widget.NewSeparator())
|
hBox.Add(widget.NewSeparator())
|
||||||
hBox.Add(widget.NewButtonWithIcon("Ctrl+C", theme.LogoutIcon(), func() {
|
}
|
||||||
|
|
||||||
|
hBox.Add(widget.NewButtonWithIcon("1", theme.DownloadIcon(), func() {
|
||||||
|
c.SetRamBytes(ramBytes1)
|
||||||
|
}))
|
||||||
|
hBox.Add(widget.NewSeparator())
|
||||||
|
hBox.Add(widget.NewButtonWithIcon("2", theme.DownloadIcon(), func() {
|
||||||
|
c.SetRamBytes(ramBytes2)
|
||||||
|
}))
|
||||||
|
hBox.Add(widget.NewSeparator())
|
||||||
|
hBox.Add(widget.NewButtonWithIcon("^C", theme.MediaStopIcon(), func() {
|
||||||
c.PutCtrlKey(0x03)
|
c.PutCtrlKey(0x03)
|
||||||
}))
|
}))
|
||||||
hBox.Add(widget.NewSeparator())
|
hBox.Add(widget.NewSeparator())
|
||||||
bNorm := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {
|
cbFreq := widget.NewCheck("Fmax", func(checked bool) {
|
||||||
fullSpeed.Store(false)
|
fullSpeed.Store(checked)
|
||||||
|
if checked {
|
||||||
|
c.SetCPUFrequency(25_000_000)
|
||||||
|
} else {
|
||||||
c.SetCPUFrequency(2_500_000)
|
c.SetCPUFrequency(2_500_000)
|
||||||
//bNorm.Disable()
|
}
|
||||||
|
})
|
||||||
|
//bNorm := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {
|
||||||
|
// fullSpeed.Store(false)
|
||||||
|
// c.SetCPUFrequency(2_500_000)
|
||||||
|
// //bNorm.Disable()
|
||||||
|
//
|
||||||
|
//})
|
||||||
|
//bFast := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() {
|
||||||
|
// fullSpeed.Store(true)
|
||||||
|
// c.SetCPUFrequency(50_000_000)
|
||||||
|
// bNorm.Enable()
|
||||||
|
// //bFast.Disable()
|
||||||
|
//})
|
||||||
|
|
||||||
})
|
hBox.Add(cbFreq)
|
||||||
bFast := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() {
|
//hBox.Add(bFast)
|
||||||
fullSpeed.Store(true)
|
hBox.Add(widget.NewSeparator())
|
||||||
c.SetCPUFrequency(50_000_000)
|
hBox.Add(widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
|
||||||
bNorm.Enable()
|
cfg := config.Clone()
|
||||||
//bFast.Disable()
|
d := dialog.NewCustomConfirm("OK-Emu settings", "Save", "Cancel", settingsDialog(cfg), func(b bool) {
|
||||||
})
|
if b {
|
||||||
hBox.Add(bNorm)
|
cfg.Save()
|
||||||
hBox.Add(bFast)
|
}
|
||||||
|
}, w)
|
||||||
|
d.Resize(fyne.NewSize(450, 360))
|
||||||
|
//w.SetContent(settings.NewSettings().LoadAppearanceScreen(w))
|
||||||
|
d.Show()
|
||||||
|
}))
|
||||||
hBox.Add(layout.NewSpacer())
|
hBox.Add(layout.NewSpacer())
|
||||||
hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() {
|
hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() {
|
||||||
needReset = true
|
needReset = true
|
||||||
|
|||||||
@ -10,21 +10,21 @@ import (
|
|||||||
const defaultMonitorFile = "rom/MON_v5.bin"
|
const defaultMonitorFile = "rom/MON_v5.bin"
|
||||||
const defaultCPMFile = "rom/CPM_v5.bin"
|
const defaultCPMFile = "rom/CPM_v5.bin"
|
||||||
const DefaultDebufPort = 10000
|
const DefaultDebufPort = 10000
|
||||||
|
const confFile = "okemu.yml"
|
||||||
|
|
||||||
type OkEmuConfig struct {
|
type OkEmuConfig struct {
|
||||||
LogFile string `yaml:"logFile"`
|
LogFile string `yaml:"logFile"`
|
||||||
LogLevel string `yaml:"logLevel"`
|
LogLevel string `yaml:"logLevel"`
|
||||||
MonitorFile string `yaml:"monitorFile"`
|
MonitorFile string `yaml:"monitorFile"`
|
||||||
CPMFile string `yaml:"cpmFile"`
|
CPMFile string `yaml:"cpmFile"`
|
||||||
FDC FDCConfig `yaml:"fdc"`
|
FDC []FDCConfig `yaml:"fdc"`
|
||||||
Debugger DebuggerConfig `yaml:"debugger"`
|
Debugger DebuggerConfig `yaml:"debugger"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FDCConfig struct {
|
type FDCConfig struct {
|
||||||
AutoLoadB bool `yaml:"autoLoadB"`
|
AutoLoad bool `yaml:"autoLoad"`
|
||||||
AutoLoadC bool `yaml:"autoLoadC"`
|
AutoSave bool `yaml:"autoSave"`
|
||||||
FloppyB string `yaml:"floppyB"`
|
FloppyFile string `yaml:"floppyFile"`
|
||||||
FloppyC string `yaml:"floppyC"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DebuggerConfig struct {
|
type DebuggerConfig struct {
|
||||||
@ -54,7 +54,6 @@ func LoadConfig() {
|
|||||||
// usage()
|
// usage()
|
||||||
//}
|
//}
|
||||||
// confFile := args[2]
|
// confFile := args[2]
|
||||||
confFile := "okemu.yml"
|
|
||||||
|
|
||||||
conf := OkEmuConfig{}
|
conf := OkEmuConfig{}
|
||||||
data, err := os.ReadFile(confFile)
|
data, err := os.ReadFile(confFile)
|
||||||
@ -96,3 +95,37 @@ func setDefaultConf(conf *OkEmuConfig) {
|
|||||||
conf.Debugger.Port = DefaultDebufPort
|
conf.Debugger.Port = DefaultDebufPort
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OkEmuConfig) Clone() *OkEmuConfig {
|
||||||
|
|
||||||
|
fds := make([]FDCConfig, 2)
|
||||||
|
for n, fd := range c.FDC {
|
||||||
|
fds[n].FloppyFile = fd.FloppyFile
|
||||||
|
fds[n].AutoLoad = fd.AutoLoad
|
||||||
|
fds[n].AutoSave = fd.AutoSave
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OkEmuConfig{
|
||||||
|
LogFile: c.LogFile,
|
||||||
|
LogLevel: c.LogLevel,
|
||||||
|
MonitorFile: c.MonitorFile,
|
||||||
|
CPMFile: c.CPMFile,
|
||||||
|
FDC: fds,
|
||||||
|
Debugger: DebuggerConfig{
|
||||||
|
Enabled: c.Debugger.Enabled,
|
||||||
|
Host: c.Debugger.Host,
|
||||||
|
Port: c.Debugger.Port,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OkEmuConfig) Save() {
|
||||||
|
data, err := yaml.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("config error: %v", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(confFile, data, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("save config file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ var orMatch = regexp.MustCompile(`\s+OR\s+`)
|
|||||||
|
|
||||||
// var xorMatch = regexp.MustCompile(`\s+XOR\s+`)
|
// var xorMatch = regexp.MustCompile(`\s+XOR\s+`)
|
||||||
var hexHMatch = regexp.MustCompile(`[[:xdigit:]]+H`)
|
var hexHMatch = regexp.MustCompile(`[[:xdigit:]]+H`)
|
||||||
var eqMatch = regexp.MustCompile(`[^=><]=[^=]`)
|
var eqMatch = regexp.MustCompile(`[^=><!]=[^=]`)
|
||||||
|
|
||||||
func patchExpression(expr string) string {
|
func patchExpression(expr string) string {
|
||||||
ex := strings.ToUpper(expr)
|
ex := strings.ToUpper(expr)
|
||||||
@ -58,6 +58,8 @@ func patchExpression(expr string) string {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ex = strings.ReplaceAll(ex, "NOT", "!")
|
||||||
|
ex = strings.ReplaceAll(ex, "<>", "!=")
|
||||||
return ex
|
return ex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,13 +5,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const expr1 = "PC=00100h && SP>=256"
|
const expr1 = "PC=00100h and (NOT(B > 10)) and a != 5"
|
||||||
const expr2 = "PC=00115h"
|
const expr2 = "PC=00115h"
|
||||||
const expr3 = "SP>=1332"
|
const expr3 = "SP>=1332"
|
||||||
|
|
||||||
var ctx = map[string]interface{}{
|
var ctx = map[string]interface{}{
|
||||||
"PC": 0x100,
|
"PC": 0x100,
|
||||||
"A": 0x55,
|
"A": 0x55,
|
||||||
|
"B": 5,
|
||||||
"SP": 0x200,
|
"SP": 0x200,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -241,6 +241,8 @@ func HandleCommand(str string, writer *bufio.Writer) bool {
|
|||||||
quit = true
|
quit = true
|
||||||
case "snapshot-save":
|
case "snapshot-save":
|
||||||
writeResponseMessage(writer, snapshotSave(params))
|
writeResponseMessage(writer, snapshotSave(params))
|
||||||
|
case "snapshot-load":
|
||||||
|
writeResponseMessage(writer, snapshotLoad(params))
|
||||||
case "set-breakpointaction":
|
case "set-breakpointaction":
|
||||||
// now do nothing
|
// now do nothing
|
||||||
writeResponseMessage(writer, "")
|
writeResponseMessage(writer, "")
|
||||||
@ -665,3 +667,11 @@ func snapshotSave(params string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func snapshotLoad(params string) string {
|
||||||
|
e := computer.LoadSnapshot(strings.TrimSpace(params))
|
||||||
|
if e != nil {
|
||||||
|
return fmt.Sprintf("Error load snapshot: %s", e)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
157
main.go
157
main.go
@ -1,14 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"okemu/config"
|
"okemu/config"
|
||||||
"okemu/debug"
|
"okemu/debug"
|
||||||
"okemu/debug/listener"
|
"okemu/debug/listener"
|
||||||
"okemu/logger"
|
"okemu/logger"
|
||||||
|
"okemu/nanotime"
|
||||||
"okemu/okean240"
|
"okemu/okean240"
|
||||||
"okemu/okean240/fdc"
|
|
||||||
"okemu/z80/dis"
|
"okemu/z80/dis"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -16,27 +18,33 @@ import (
|
|||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/canvas"
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
|
//log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "v1.0.0"
|
var Version = "v1.0.0"
|
||||||
var BuildTime = "2026-03-01"
|
var BuildTime = "2026-03-01"
|
||||||
|
|
||||||
//go:embed hex/m80.hex
|
////go:embed hex/m80.hex
|
||||||
var serialBytes []byte
|
//var serialBytes []byte
|
||||||
|
|
||||||
//go:embed bin/main.com
|
//go:embed bin/2048.com
|
||||||
var ramBytes1 []byte
|
var ramBytes1 []byte
|
||||||
|
|
||||||
//go:embed bin/PLOT.BAS
|
//go:embed bin/JACK.COM
|
||||||
var ramBytes2 []byte
|
var ramBytes2 []byte
|
||||||
|
|
||||||
var needReset = false
|
var needReset = false
|
||||||
|
|
||||||
var fullSpeed atomic.Bool
|
var fullSpeed atomic.Bool
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime)
|
fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime)
|
||||||
|
|
||||||
|
// Create a context that can be cancelled
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
// base log init
|
// base log init
|
||||||
logger.InitLogging()
|
logger.InitLogging()
|
||||||
|
|
||||||
@ -51,91 +59,148 @@ func main() {
|
|||||||
debugger := debug.NewDebugger()
|
debugger := debug.NewDebugger()
|
||||||
computer := okean240.NewComputer(conf, debugger)
|
computer := okean240.NewComputer(conf, debugger)
|
||||||
|
|
||||||
computer.SetSerialBytes(serialBytes)
|
//computer.SetSerialBytes(serialBytes)
|
||||||
|
|
||||||
if conf.FDC.AutoLoadB {
|
computer.AutoLoadFloppy()
|
||||||
err := computer.LoadFloppy(fdc.FloppyB)
|
|
||||||
if err != nil {
|
|
||||||
// show message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if conf.FDC.AutoLoadC {
|
|
||||||
err := computer.LoadFloppy(fdc.FloppyC)
|
|
||||||
if err != nil {
|
|
||||||
// show message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disasm := dis.NewDisassembler(computer)
|
disasm := dis.NewDisassembler(computer)
|
||||||
|
|
||||||
w, raster, label := mainWindow(computer)
|
w, raster, label := mainWindow(computer, conf)
|
||||||
|
|
||||||
go emulator(computer)
|
go emulator(ctx, computer)
|
||||||
go screen(computer, raster, label)
|
// go timerClock(ctx, computer)
|
||||||
|
go screen(ctx, computer, raster, label)
|
||||||
|
|
||||||
if conf.Debugger.Enabled {
|
if conf.Debugger.Enabled {
|
||||||
go listener.SetupTcpHandler(conf, debugger, disasm, computer)
|
go listener.SetupTcpHandler(conf, debugger, disasm, computer)
|
||||||
}
|
}
|
||||||
|
|
||||||
(*w).ShowAndRun()
|
(*w).ShowAndRun()
|
||||||
|
computer.AutoSaveFloppy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func screen(computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) {
|
func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) {
|
||||||
ticker := time.NewTicker(20 * time.Millisecond)
|
ticker := time.NewTicker(20 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
frame := 0
|
frame := 0
|
||||||
var pre uint64 = 0
|
var pre uint64 = 0
|
||||||
var freq uint64 = 0
|
var preTim uint64 = 0
|
||||||
|
var freq float64 = 0
|
||||||
|
var freqTim float64 = 0
|
||||||
|
timeStart := time.Now()
|
||||||
|
|
||||||
for range ticker.C {
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
frame++
|
frame++
|
||||||
// redraw screen here
|
// redraw screen here
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
// status for every 50 frames
|
// status for every 50 frames
|
||||||
if frame%50 == 0 {
|
if frame%50 == 0 {
|
||||||
freq = computer.Cycles() - pre
|
timeElapsed := time.Since(timeStart)
|
||||||
|
period := float64(timeElapsed.Nanoseconds()) / 1_000_000.0
|
||||||
|
|
||||||
|
freq = math.Round(float64(computer.Cycles()-pre)/period) / 1000.0
|
||||||
|
freqTim = math.Round(float64(timerTicks.Load()-preTim)/period) / 1000.0
|
||||||
|
label.SetText(formatLabel(computer, freq, freqTim))
|
||||||
|
|
||||||
|
// adjust cpu clock
|
||||||
|
if freq > 2.55 && cpuClkPeriod.Load() < defaultCpuClkPeriod+40 {
|
||||||
|
cpuClkPeriod.Add(1)
|
||||||
|
} else if freq < 2.45 && cpuClkPeriod.Load() > defaultCpuClkPeriod-40 {
|
||||||
|
cpuClkPeriod.Add(-1)
|
||||||
|
}
|
||||||
|
// adjust timer clock
|
||||||
|
if freqTim > 1.53 && timerClkPeriod.Load() < defaultTimerClkPeriod+20 {
|
||||||
|
timerClkPeriod.Add(1)
|
||||||
|
} else if freqTim < 1.47 && timerClkPeriod.Load() > defaultTimerClkPeriod-20 {
|
||||||
|
timerClkPeriod.Add(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Debugf("Cpu clk period: %d, Timer clock period: %d, period: %1.3f", cpuClkPeriod.Load(), timerClkPeriod.Load(), period)
|
||||||
pre = computer.Cycles()
|
pre = computer.Cycles()
|
||||||
label.SetText(formatLabel(computer, freq))
|
preTim = timerTicks.Load()
|
||||||
|
timeStart = time.Now()
|
||||||
}
|
}
|
||||||
raster.Refresh()
|
raster.Refresh()
|
||||||
})
|
})
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatLabel(computer *okean240.ComputerType, freq uint64) string {
|
func formatLabel(computer *okean240.ComputerType, freq float64, freqTim float64) string {
|
||||||
return fmt.Sprintf("Screen size: %dx%d | F: %d | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, computer.DebuggerState())
|
return fmt.Sprintf("Screen size: %dx%d | Fcpu: %1.2fMHz | Ftmr: %1.2fMHz | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
|
||||||
}
|
}
|
||||||
|
|
||||||
const ticksPerTact uint64 = 4
|
var timerTicks atomic.Uint64
|
||||||
|
|
||||||
func emulator(computer *okean240.ComputerType) {
|
const defaultTimerClkPeriod = 564 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
|
||||||
ticker := time.NewTicker(66 * time.Nanosecond)
|
const defaultCpuClkPeriod = 221 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
|
||||||
var ticks uint64 = 0
|
|
||||||
var nextClock = ticks + ticksPerTact
|
var timerClkPeriod atomic.Int64 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
|
||||||
//var ticksCPU = 3
|
var cpuClkPeriod atomic.Int64 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
|
||||||
for range ticker.C {
|
|
||||||
ticks++
|
func emulator(ctx context.Context, computer *okean240.ComputerType) {
|
||||||
if ticks%10 == 0 {
|
ticker := time.NewTicker(133 * time.Nanosecond)
|
||||||
// 1.5 MHz
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
cpuClkPeriod.Store(defaultCpuClkPeriod)
|
||||||
|
timerClkPeriod.Store(defaultTimerClkPeriod)
|
||||||
|
|
||||||
|
cpuTicks := uint64(0)
|
||||||
|
nextTick := uint64(0)
|
||||||
|
|
||||||
|
cpuTStart := nanotime.Now()
|
||||||
|
tmrTStart := cpuTStart
|
||||||
|
|
||||||
|
var bp uint16
|
||||||
|
var bpType byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
tmrElapsed := nanotime.Since(tmrTStart)
|
||||||
|
// TIMER CLK
|
||||||
|
if tmrElapsed.Nanoseconds() >= timerClkPeriod.Load() {
|
||||||
computer.TimerClk()
|
computer.TimerClk()
|
||||||
|
timerTicks.Add(1)
|
||||||
|
tmrTStart = nanotime.Now()
|
||||||
}
|
}
|
||||||
var bp uint16 = 0
|
|
||||||
var typ byte = 0
|
// CPU
|
||||||
|
cpuElapsed := nanotime.Since(cpuTStart)
|
||||||
|
if cpuElapsed.Nanoseconds() >= cpuClkPeriod.Load() {
|
||||||
|
cpuTicks++
|
||||||
|
bp = 0
|
||||||
|
bpType = 0
|
||||||
|
|
||||||
if fullSpeed.Load() {
|
if fullSpeed.Load() {
|
||||||
_, bp, typ = computer.Do()
|
// Max frequency
|
||||||
|
_, bp, bpType = computer.Do()
|
||||||
} else {
|
} else {
|
||||||
if ticks >= nextClock {
|
// 2.5MHz frequency
|
||||||
|
if cpuTicks >= nextTick {
|
||||||
var t uint32
|
var t uint32
|
||||||
t, bp, typ = computer.Do()
|
t, bp, bpType = computer.Do()
|
||||||
nextClock = ticks + uint64(t)*ticksPerTact
|
nextTick += uint64(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Breakpoint hit
|
// Breakpoint hit
|
||||||
if bp > 0 || typ != 0 {
|
if bp > 0 || bpType != 0 {
|
||||||
listener.BreakpointHit(bp, typ)
|
listener.BreakpointHit(bp, bpType)
|
||||||
}
|
}
|
||||||
if needReset {
|
if needReset {
|
||||||
computer.Reset()
|
computer.Reset()
|
||||||
needReset = false
|
needReset = false
|
||||||
}
|
}
|
||||||
|
cpuTStart = nanotime.Now()
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
18
nanotime/nanotime.go
Normal file
18
nanotime/nanotime.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package nanotime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
_ "unsafe" // required to use //go:linkname
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
//go:linkname nanotime runtime.nanotime
|
||||||
|
func nanotime() int64
|
||||||
|
|
||||||
|
func Now() uint64 {
|
||||||
|
return uint64(nanotime())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Since(t uint64) time.Duration {
|
||||||
|
return time.Duration(Now() - t)
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"okemu/okean240/usart"
|
"okemu/okean240/usart"
|
||||||
"okemu/z80"
|
"okemu/z80"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
//"okemu/z80"
|
//"okemu/z80"
|
||||||
"okemu/z80/c99"
|
"okemu/z80/c99"
|
||||||
@ -48,6 +49,7 @@ type ComputerType struct {
|
|||||||
cpuFrequency uint32
|
cpuFrequency uint32
|
||||||
//
|
//
|
||||||
debugger *debug.Debugger
|
debugger *debug.Debugger
|
||||||
|
config *config.OkEmuConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
@ -98,6 +100,7 @@ func (c *ComputerType) MemWrite(addr uint16, val byte) {
|
|||||||
// NewComputer Builds new computer
|
// NewComputer Builds new computer
|
||||||
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
|
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
|
||||||
c := ComputerType{}
|
c := ComputerType{}
|
||||||
|
c.config = cfg
|
||||||
c.memory = Memory{}
|
c.memory = Memory{}
|
||||||
c.memory.Init(cfg.MonitorFile, cfg.CPMFile)
|
c.memory.Init(cfg.MonitorFile, cfg.CPMFile)
|
||||||
|
|
||||||
@ -379,17 +382,6 @@ func (c *ComputerType) memoryAsHexStr() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComputerType) SaveSnapshot(fn string) error {
|
func (c *ComputerType) SaveSnapshot(fn string) error {
|
||||||
// create snapshot file
|
|
||||||
file, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// take snapshot
|
// take snapshot
|
||||||
s := Snapshot{
|
s := Snapshot{
|
||||||
CPU: c.cpu.GetState(),
|
CPU: c.cpu.GetState(),
|
||||||
@ -401,9 +393,59 @@ func (c *ComputerType) SaveSnapshot(fn string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// and save
|
// and save
|
||||||
err = binary.Write(file, binary.LittleEndian, b)
|
err = os.WriteFile(fn, b, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) LoadSnapshot(fn string) error {
|
||||||
|
// read snapshot file
|
||||||
|
b, err := os.ReadFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// unmarshal from JSON
|
||||||
|
var result Snapshot
|
||||||
|
err = json.Unmarshal(b, &result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.cpu.SetState(result.CPU)
|
||||||
|
return c.restoreMemoryFromHex(result.Memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) restoreMemoryFromHex(memory string) error {
|
||||||
|
for addr := 0; addr <= 65535; addr++ {
|
||||||
|
b, e := strconv.ParseUint(memory[addr*2:addr*2+2], 16, 8)
|
||||||
|
if e != nil {
|
||||||
|
log.Error(e)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
c.memory.MemWrite(uint16(addr), byte(b))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) AutoSaveFloppy() {
|
||||||
|
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
|
||||||
|
if c.config.FDC[drv].AutoSave {
|
||||||
|
e := c.fdc.SaveFloppy(drv)
|
||||||
|
if e != nil {
|
||||||
|
log.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) AutoLoadFloppy() {
|
||||||
|
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
|
||||||
|
if c.config.FDC[drv].AutoLoad {
|
||||||
|
e := c.fdc.LoadFloppy(drv)
|
||||||
|
if e != nil {
|
||||||
|
log.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ type FloppyDriveController struct {
|
|||||||
//curSector *SectorType
|
//curSector *SectorType
|
||||||
bytePtr uint16
|
bytePtr uint16
|
||||||
trackBuffer []byte
|
trackBuffer []byte
|
||||||
floppyFile []string
|
// floppyFile []string
|
||||||
config *config.OkEmuConfig
|
config *config.OkEmuConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,14 +332,14 @@ func (f *FloppyDriveController) Drq() byte {
|
|||||||
|
|
||||||
func (f *FloppyDriveController) LoadFloppy(drive byte) error {
|
func (f *FloppyDriveController) LoadFloppy(drive byte) error {
|
||||||
if drive < TotalDrives {
|
if drive < TotalDrives {
|
||||||
return loadFloppy(&f.sectors[drive], f.floppyFile[drive])
|
return loadFloppy(&f.sectors[drive], f.config.FDC[drive].FloppyFile)
|
||||||
}
|
}
|
||||||
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FloppyDriveController) SaveFloppy(drive byte) error {
|
func (f *FloppyDriveController) SaveFloppy(drive byte) error {
|
||||||
if drive < TotalDrives {
|
if drive < TotalDrives {
|
||||||
return saveFloppy(&f.sectors[drive], f.floppyFile[drive])
|
return saveFloppy(&f.sectors[drive], f.config.FDC[drive].FloppyFile)
|
||||||
}
|
}
|
||||||
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
||||||
}
|
}
|
||||||
@ -353,6 +353,7 @@ func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
|
|||||||
sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize)
|
sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FloppyDriveController{
|
return &FloppyDriveController{
|
||||||
sideNo: 0,
|
sideNo: 0,
|
||||||
ddEn: 0,
|
ddEn: 0,
|
||||||
@ -366,7 +367,8 @@ func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
|
|||||||
lastCmd: 0xff,
|
lastCmd: 0xff,
|
||||||
sectors: sec,
|
sectors: sec,
|
||||||
bytePtr: 0xffff,
|
bytePtr: 0xffff,
|
||||||
floppyFile: []string{conf.FDC.FloppyB, conf.FDC.FloppyC},
|
//floppyConf: conf.FDC,
|
||||||
|
config: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
okemu.yml
22
okemu.yml
@ -1,15 +1,15 @@
|
|||||||
logFile: "okemu.log"
|
logFile: okemu.log
|
||||||
logLevel: "info"
|
logLevel: info
|
||||||
|
monitorFile: rom/MON_r8_9c6c6546.bin
|
||||||
monitorFile: "rom/MON_r8_9c6c6546.bin"
|
cpmFile: rom/CPM_r8_bc0695e4.bin
|
||||||
cpmFile: "rom/CPM_r8_bc0695e4.bin"
|
|
||||||
|
|
||||||
fdc:
|
fdc:
|
||||||
autoLoadB: true
|
- autoLoad: true
|
||||||
floppyB: "floppy/floppyB.okd"
|
autoSave: true
|
||||||
autoLoadC: true
|
floppyFile: floppy/floppyB.okd
|
||||||
floppyC: "floppy/floppyC.okd"
|
- autoLoad: true
|
||||||
|
autoSave: true
|
||||||
|
floppyFile: floppy/floppyC.okd
|
||||||
debugger:
|
debugger:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
host: localhost
|
||||||
port: 10001
|
port: 10001
|
||||||
|
|||||||
91
settings.go
Normal file
91
settings.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"okemu/config"
|
||||||
|
"okemu/okean240/fdc"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/data/validation"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func settingsDialog(config *config.OkEmuConfig) fyne.CanvasObject {
|
||||||
|
|
||||||
|
debugger := widget.NewForm(
|
||||||
|
widget.NewFormItem("Remote debug", debugCheckBox(config)),
|
||||||
|
widget.NewFormItem("Host", hostEntry(config)),
|
||||||
|
widget.NewFormItem("Port", portEntry(config)))
|
||||||
|
|
||||||
|
cont := container.NewAppTabs(
|
||||||
|
container.NewTabItemWithIcon("Debug", theme.MediaPlayIcon(), widget.NewCard("", "", debugger)),
|
||||||
|
)
|
||||||
|
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
|
||||||
|
cont.Append(
|
||||||
|
container.NewTabItemWithIcon("Drive "+string(rune(66+drv))+":", theme.DocumentSaveIcon(), widget.NewCard("Floppy 720k", "", diskForm(config, drv))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cont
|
||||||
|
}
|
||||||
|
|
||||||
|
func portEntry(cfg *config.OkEmuConfig) *widget.Entry {
|
||||||
|
dbgPort := widget.NewEntry()
|
||||||
|
dbgPort.SetText(strconv.Itoa(cfg.Debugger.Port))
|
||||||
|
dbgPort.Validator = validation.NewRegexp(`^[0-9]+$`, "port can only contain numbers")
|
||||||
|
dbgPort.OnSubmitted = func(s string) {
|
||||||
|
p, e := strconv.Atoi(s)
|
||||||
|
if e != nil {
|
||||||
|
log.Warn("Illegal port number: " + s)
|
||||||
|
} else {
|
||||||
|
cfg.Debugger.Port = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dbgPort
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostEntry(cfg *config.OkEmuConfig) *widget.Entry {
|
||||||
|
entry := widget.NewEntry()
|
||||||
|
entry.SetText(cfg.Debugger.Host)
|
||||||
|
entry.Validator = validation.NewRegexp(`^[A-Za-z0-9_-]+$`, "hostname can only contain letters, numbers, '_', and '-'")
|
||||||
|
entry.OnSubmitted = func(s string) {
|
||||||
|
cfg.Debugger.Host = s
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugCheckBox(cfg *config.OkEmuConfig) *widget.Check {
|
||||||
|
// Debug Enabled
|
||||||
|
check := widget.NewCheck("Enable", func(checked bool) {
|
||||||
|
cfg.Debugger.Enabled = checked
|
||||||
|
})
|
||||||
|
check.Checked = cfg.Debugger.Enabled
|
||||||
|
return check
|
||||||
|
}
|
||||||
|
|
||||||
|
func diskForm(cfg *config.OkEmuConfig, drv byte) *widget.Form {
|
||||||
|
dskAutoLoad := widget.NewCheck("AutoLoad", func(checked bool) {
|
||||||
|
cfg.FDC[drv].AutoLoad = checked
|
||||||
|
})
|
||||||
|
dskAutoLoad.Checked = cfg.FDC[drv].AutoLoad
|
||||||
|
|
||||||
|
dskAutoSave := widget.NewCheck("AutoSave", func(checked bool) {
|
||||||
|
cfg.FDC[drv].AutoSave = checked
|
||||||
|
})
|
||||||
|
|
||||||
|
dskAutoSave.Checked = cfg.FDC[drv].AutoSave
|
||||||
|
|
||||||
|
dskFileName := widget.NewEntry()
|
||||||
|
dskFileName.SetText(cfg.FDC[drv].FloppyFile)
|
||||||
|
dskFileName.OnSubmitted = func(s string) {
|
||||||
|
cfg.FDC[drv].FloppyFile = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return widget.NewForm(
|
||||||
|
widget.NewFormItem("AutoLoad", dskAutoLoad),
|
||||||
|
widget.NewFormItem("AutoSave", dskAutoSave),
|
||||||
|
widget.NewFormItem("File", dskFileName),
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user