From 231ae7a84069268dc5ea926b6625da8eaac825e0 Mon Sep 17 00:00:00 2001 From: Roman Boykov Date: Wed, 4 Mar 2026 23:57:39 +0300 Subject: [PATCH] Start FDC impl. VScroll --- main.go | 61 +++-- okean240/computer.go | 97 ++------ okean240/constants.go | 320 ++++++++++++++++++++++++++ okean240/fdc.go | 100 ++++++++ okean240/ioports.go | 236 +++++++------------ okean240/keyboard.go | 63 +++++ okean240/memory.go | 4 +- okean240/sio8251.go | 122 +++++++++- okean240/tmr8253.go | 56 ++--- okemu.log | 0 okemu.yml | 4 +- rom/CPM_r5.bin | Bin 0 -> 8192 bytes okean240/CPM_r7.BIN => rom/CPM_r7.bin | Bin rom/MON_r5.bin | Bin 0 -> 8192 bytes okean240/MON_r6.BIN => rom/MON_r6.bin | Bin 15 files changed, 794 insertions(+), 269 deletions(-) create mode 100644 okean240/constants.go create mode 100644 okean240/fdc.go create mode 100644 okean240/keyboard.go create mode 100644 okemu.log create mode 100644 rom/CPM_r5.bin rename okean240/CPM_r7.BIN => rom/CPM_r7.bin (100%) create mode 100644 rom/MON_r5.bin rename okean240/MON_r6.BIN => rom/MON_r6.bin (100%) diff --git a/main.go b/main.go index dc746e7..1eed26a 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" ) @@ -20,6 +21,8 @@ var BuildTime = "2026-03-01" func main() { + fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime) + // base log init logger.InitLogging() @@ -30,12 +33,17 @@ func main() { // Reconfigure logging by config values //logger.ReconfigureLogging(conf) - - emuapp := app.New() - w := emuapp.NewWindow("Океан 240.2") - computer := okean240.New(conf) + emulatorApp := app.New() + w := emulatorApp.NewWindow("Океан 240.2") + w.Canvas().SetOnTypedKey( + func(key *fyne.KeyEvent) { + computer.PutKey(key) + }) + + addShortcuts(w.Canvas(), computer) + label := widget.NewLabel(fmt.Sprintf("Screen size: %dx%d", computer.ScreenWidth(), computer.ScreenHeight())) raster := canvas.NewRasterWithPixels( @@ -44,35 +52,37 @@ func main() { }) raster.Resize(fyne.NewSize(512, 512)) raster.SetMinSize(fyne.NewSize(512, 512)) + + centerRaster := container.NewCenter(raster) + w.Resize(fyne.NewSize(600, 600)) hBox := container.NewHBox( + widget.NewButton("Ctrl+C", func() { + computer.PutCtrlKey(0x03) + }), + widget.NewSeparator(), widget.NewButton("Reset", func() { computer.Reset() }), + widget.NewSeparator(), widget.NewButton("Закрыть", func() { - emuapp.Quit() + emulatorApp.Quit() }), ) vBox := container.NewVBox( - raster, + centerRaster, label, hBox, ) + w.SetContent(vBox) go emulator(computer, raster, label) w.ShowAndRun() - - //println("Tick computer") - //computer := okean240.New(conf) - //println("Run computer") - //computer.Run() } -const TicksPerFrame = 20_000_000 / 133 - func emulator(computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) { ticker := time.NewTicker(133 * time.Nanosecond) var ticks = 0 @@ -81,6 +91,10 @@ func emulator(computer *okean240.ComputerType, raster *canvas.Raster, label *wid //var frameStartTime = time.Now().UnixMicro() frameNextTime := time.Now().UnixMicro() + 20000 frame := 0 + var pre uint64 = 0 + var freq uint64 = 0 + + nextSecond := time.Now().Add(time.Second).UnixMicro() curScrWidth := 256 for range ticker.C { ticks++ @@ -89,7 +103,13 @@ func emulator(computer *okean240.ComputerType, raster *canvas.Raster, label *wid computer.TimerClk() } if ticks > ticksCPU { - ticksCPU = ticks + computer.Do()*3 + ticksCPU = ticks + computer.Do()*2 + } + + if time.Now().UnixMicro() > nextSecond { + nextSecond = time.Now().Add(time.Second).UnixMicro() + freq = computer.Cycles() - pre + pre = computer.Cycles() } //if ticks >= ticksSCR { @@ -107,11 +127,20 @@ func emulator(computer *okean240.ComputerType, raster *canvas.Raster, label *wid raster.Resize(newSize) } // status for every 25 frames - if frame%25 == 0 { - label.SetText(fmt.Sprintf("Screen size: %dx%d Tick: %d", computer.ScreenWidth(), computer.ScreenHeight(), computer.Cycles())) + if frame%50 == 0 { + label.SetText(fmt.Sprintf("Screen size: %dx%d F: %d", computer.ScreenWidth(), computer.ScreenHeight(), freq)) } raster.Refresh() }) } } } + +func addShortcuts(c fyne.Canvas, computer *okean240.ComputerType) { + // Add shortcuts for Ctrl+A to Ctrl+Z + for kName := 'A'; kName <= 'Z'; kName++ { + kk := fyne.KeyName(kName) + sc := &desktop.CustomShortcut{KeyName: kk, Modifier: fyne.KeyModifierControl} + c.AddShortcut(sc, func(shortcut fyne.Shortcut) { computer.PutCtrlKey(byte(kName&0xff) - 0x40) }) + } +} diff --git a/okean240/computer.go b/okean240/computer.go index 2ccb199..cceee8b 100644 --- a/okean240/computer.go +++ b/okean240/computer.go @@ -5,6 +5,7 @@ import ( "okemu/config" "okemu/z80em" + "fyne.io/fyne/v2" log "github.com/sirupsen/logrus" ) @@ -22,12 +23,17 @@ type ComputerType struct { bgColor byte dd70 *Timer8253 dd72 *Sio8251 + fdc *FDCType + kbdBuffer []byte + vShift byte + hShift byte } const VRAMBlock0 = 3 const VRAMBlock1 = 7 const VidVsuBit = 0x80 const VidColorBit = 0x40 +const KbdBufferSize = 3 type ComputerInterface interface { Run() @@ -35,6 +41,8 @@ type ComputerInterface interface { GetPixel(x uint16, y uint16) color.RGBA Do() uint64 TimerClk() + PutKey(key *fyne.KeyEvent) + PutCtrlKey(shortcut fyne.Shortcut) } func (c *ComputerType) M1MemRead(addr uint16) byte { @@ -49,68 +57,6 @@ func (c *ComputerType) MemWrite(addr uint16, val byte) { c.memory.MemWrite(addr, val) } -func (c *ComputerType) IORead(port uint16) byte { - switch port & 0x00ff { - case PIC_DD75RS: - v := c.ioPorts[PIC_DD75RS] - c.ioPorts[PIC_DD75RS] = 0 - return v - default: - log.Debugf("IORead from port: %x", port) - } - return c.ioPorts[byte(port&0x00ff)] -} - -func (c *ComputerType) IOWrite(port uint16, val byte) { - bp := byte(port & 0x00ff) - c.ioPorts[bp] = val - //log.Debugf("OUT (%x), %x", bp, val) - switch bp { - case SYS_DD17PB: - if c.dd17EnableOut { - c.memory.Configure(val) - } - case SYS_DD17CTR: - c.dd17EnableOut = val == 0x80 - case VID_DD67PB: - if val&VidVsuBit == 0 { - // video page 0 - c.vRAM = c.memory.allMemory[VRAMBlock0] - } else { - // video page 1 - c.vRAM = c.memory.allMemory[VRAMBlock1] - } - if val&VidColorBit != 0 { - c.colorMode = true - c.screenWidth = 256 - } else { - c.colorMode = false - c.screenWidth = 512 - } - c.palette = val & 0x07 - c.bgColor = val & 0x38 >> 3 - case DD67CTR: - - case TMR_DD70CTR: - // Timer VI63 config register - c.dd70.Configure(val) - case TMR_DD70C1: - // Timer VI63 counter0 register - c.dd70.Load(0, val) - case TMR_DD70C2: - // Timer VI63 counter1 register - c.dd70.Load(1, val) - case TMR_DD70C3: - // Timer VI63 counter2 register - c.dd70.Load(2, val) - - case KBD_DD78CTR: - default: - //log.Debugf("OUT to Unknown port (%x), %x", bp, val) - - } -} - // New Builds new computer func New(cfg *config.OkEmuConfig) *ComputerType { c := ComputerType{} @@ -127,8 +73,12 @@ func New(cfg *config.OkEmuConfig) *ComputerType { c.palette = 0 c.bgColor = 0 + c.vShift = 0 + c.hShift = 0 + c.dd70 = NewTimer8253() c.dd72 = NewSio8251() + c.fdc = NewFDCType() return &c } @@ -136,6 +86,8 @@ func New(cfg *config.OkEmuConfig) *ComputerType { func (c *ComputerType) Reset() { c.cpu.Reset() c.cycles = 0 + c.vShift = 0 + c.hShift = 0 } func (c *ComputerType) Do() int { @@ -160,18 +112,16 @@ func (c *ComputerType) GetPixel(x uint16, y uint16) color.RGBA { if x > 255 { return CWhite } + y += uint16(c.vShift) + // x += uint16(c.hShift >> 3) // Color 256x256 mode - addr = ((x & 0xf8) << 6) | y - var mask byte = 1 << (x & 0x07) - pix1 := c.vRAM.memory[addr]&(mask) != 0 - pix2 := c.vRAM.memory[addr+0x100]&(mask) != 0 - var cl byte = 0 - if pix1 { - cl |= 1 - } - if pix2 { - cl |= 2 + addr = ((x & 0xf8) << 6) | (y & 0xff) + if c.vShift != 0 { + addr -= 8 } + + var cl byte = (c.vRAM.memory[addr&0x3fff] >> (x & 0x07)) & 1 + cl |= (c.vRAM.memory[(addr+0x100)&0x3fff] >> (x & 0x07)) & 1 << 1 if cl == 0 { resColor = BgColorPalette[c.bgColor] } else { @@ -182,7 +132,8 @@ func (c *ComputerType) GetPixel(x uint16, y uint16) color.RGBA { return CWhite } // Mono 512x256 mode - addr = ((x & 0xf8) << 5) | y + y += uint16(c.vShift) + addr = ((x & 0xf8) << 5) | (y & 0xff) pix := c.vRAM.memory[addr]&(1<> 5 & 0x01 + f.deenN = val >> 4 & 0x01 + f.init = val >> 3 & 0x01 + f.drsel = val >> 2 & 0x01 + f.mot1 = val >> 1 & 0x01 + f.mot0 = val & 0x01 +} + +func (f FDCType) GetFloppy() byte { + // RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT + floppy := f.intr | (f.mot0 << 1) | (f.mot1 << 2) | (f.drsel << 3) | (f.ssen << 6) | (f.motst << 7) + return floppy +} + +func (f FDCType) SetCmd(value byte) { + log.Debugf("FCD CMD: %x", value) +} + +func (f FDCType) SetTrack(value byte) { + log.Debugf("FCD Track: %d", value) + f.track = value +} + +func (f FDCType) SetSector(value byte) { + log.Debugf("FCD Sector: %d", value) + f.sector = value +} + +func (f FDCType) SetData(value byte) { + log.Debugf("FCD Data: %d", value) + f.data = value +} + +func (f FDCType) GetData() byte { + return f.data +} + +func NewFDCType() *FDCType { + sec := [FloppySizeInS]SectorType{} + for i := 0; i < FloppySizeInS; i++ { + sec[i] = make(SectorType, SectorSize) + for s := 0; s < 128; s++ { + sec[i][s] = 0 + } + } + return &FDCType{ + ssen: 0, + deenN: 0, + init: 0, + drsel: 0, + mot1: 0, + mot0: 0, + intr: 0, + motst: 0, + sectors: sec, + } +} + +// diff --git a/okean240/ioports.go b/okean240/ioports.go index cb85eee..f3cb11c 100644 --- a/okean240/ioports.go +++ b/okean240/ioports.go @@ -8,156 +8,96 @@ package okean240 * By Romych 2026-03-01 */ -/* - * КР580ВВ55 DD79 USER PORT - */ +import log "github.com/sirupsen/logrus" -// USR_DD79PA User port A -const USR_DD79PA = 0x00 +func (c *ComputerType) IORead(port uint16) byte { + switch port & 0x00ff { + case PIC_DD75RS: + // PIO VN59 + v := c.ioPorts[PIC_DD75RS] + c.ioPorts[PIC_DD75RS] = 0 + return v + case UART_DD72RR: + // SIO VV51 CMD + return c.dd72.Status() + case UART_DD72RD: + // SIO VV51 Data + return c.dd72.Receive() + case KBD_DD78PA: + // Keyboard data + return c.ioPorts[KBD_DD78PA] + case KBD_DD78PB: + return c.ioPorts[KBD_DD78PB] + case FLOPPY: + return c.fdc.GetFloppy() + default: + log.Debugf("IORead from port: %x", port) + } + return c.ioPorts[byte(port&0x00ff)] +} -// USR_DD79PB User port B -const USR_DD79PB = 0x01 +func (c *ComputerType) IOWrite(port uint16, val byte) { + bp := byte(port & 0x00ff) + c.ioPorts[bp] = val + //log.Debugf("OUT (%x), %x", bp, val) + switch bp { + case SYS_DD17PB: + if c.dd17EnableOut { + c.memory.Configure(val) + } + case SYS_DD17CTR: + c.dd17EnableOut = val == 0x80 + case VID_DD67PB: + if val&VidVsuBit == 0 { + // video page 0 + c.vRAM = c.memory.allMemory[VRAMBlock0] + } else { + // video page 1 + c.vRAM = c.memory.allMemory[VRAMBlock1] + } + if val&VidColorBit != 0 { + c.colorMode = true + c.screenWidth = 256 + } else { + c.colorMode = false + c.screenWidth = 512 + } + c.palette = val & 0x07 + c.bgColor = val & 0x38 >> 3 + case SYS_DD17PA: + c.vShift = val + case SYS_DD17PC: + c.hShift = val + case TMR_DD70CTR: + // Timer VI63 config register + c.dd70.Configure(val) + case TMR_DD70C1: + // Timer VI63 counter0 register + c.dd70.Load(0, val) + case TMR_DD70C2: + // Timer VI63 counter1 register + c.dd70.Load(1, val) + case TMR_DD70C3: + // Timer VI63 counter2 register + c.dd70.Load(2, val) -// USR_DD79PC User port C -const USR_DD79PC = 0x02 + case UART_DD72RR: + // SIO VV51 CMD + c.dd72.Command(val) + case UART_DD72RD: + // SIO VV51 Data + c.dd72.Send(val) + case FDC_CMD: + c.fdc.SetCmd(val) + case FDC_TRACK: + c.fdc.SetTrack(val) + case FDC_SECT: + c.fdc.SetSector(val) + case FLOPPY: + c.fdc.SetFloppy(val) -// USR_DD79CTR Config -const USR_DD79CTR = 0x03 // Config: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI] -// Set bit: [0][xxx][bbb][0|1] + default: + //log.Debugf("OUT to Unknown port (%x), %x", bp, val) -/* - * КР1818ВГ93 FDC Controller - */ - -// FDC_CMD FDC Command -const FDC_CMD = 0x20 - -// FDC_TRACK FDC Track No -const FDC_TRACK = 0x21 - -// FDC_SECT FDC Sector -const FDC_SECT = 0x22 - -// FDC_DATA FDC Data -const FDC_DATA = 0x23 - -// FDC_WAIT FDC Wait -const FDC_WAIT = 0x24 - -/* - * Floppy Controller port - */ - -// FLOPPY Floppy Controller port -const FLOPPY = 0x25 // WR: 5-SSEN, 4-#DDEN, 3-INIT, 2-DRSEL, 1-MOT1, 0-MOT0 -// RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT - -/* - * КР580ВВ55 DD78 Keyboard - */ - -// KBD_DD78PA Port A - Keyboard Data -const KBD_DD78PA = 0x40 - -// KBD_DD78PB Port B - JST3,SHFT,CTRL,ACK,TAPE5,TAPE4,GK,GC -const KBD_DD78PB = 0x41 - -// KBD_DD78PC Port C - [PC7:5],[KBD_ACK],[PC3:0] -const KBD_DD78PC = 0x42 - -// KBD_DD78CTR Control port -const KBD_DD78CTR = 0x43 // Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI] -// Set bit: [0][xxx][bbb][0|1] - -/* - * КР580ВИ53 DD70 - */ - -// TMR_DD70C1 Timer load 1 -const TMR_DD70C1 = 0x60 - -// TMR_DD70C2 Timer load 2 -const TMR_DD70C2 = 0x61 - -// TMR_DD70C3 Timer load 3 -const TMR_DD70C3 = 0x62 - -/* - TMR_DD70CTR - Timer config: [sc1,sc0][rl1,rl0][m2,m1,m0][bcd] - sc - timer, rl=01-LSB, 10-MSB, 11-LSB+MSB - mode 000 - int on fin, - 001 - one shot, - x10 - rate gen, - x11-sq wave -*/ -const TMR_DD70CTR = 0x63 - -/* - * Programmable Interrupt controller PIC KR580VV59 - */ - -// PIC_DD75RS RS Port -const PIC_DD75RS = 0x80 - -const Rst0SysFlag = 0x01 // System interrupt -const Rst1KbdFlag = 0x02 // Keyboard interrupt -const Rst2SerFlag = 0x04 // Serial interface interrupt -const RstЗLptFlag = 0x08 // Printer ready -const Rst4TmrFlag = 0x10 // System timer -const Rst5PwrFlag = 0x20 // Power int -const Rst6UsrFlag = 0x40 // User device 1 interrupt -const Rst7UsrFlag = 0x80 // User device 1 interrupt - -// PIC_DD75RM RM Port -const PIC_DD75RM = 0x81 - -/* - * КР580ВВ51 DD72 - */ - -// UART_DD72RD Serial data -const UART_DD72RD = 0xA0 - -// UART_DD72RR Serial status [RST,RQ_RX,RST_ERR,PAUSE,RX_EN,RX_RDY,TX_RDY] -const UART_DD72RR = 0xA1 - -/* - * КР580ВВ55 DD17 System port - */ - -// Port A - VShift[8..1] Vertical shift -const SYS_DD17PA = 0xC0 - -// Port B - Memory mapper [ROM14,13][REST][ENROM-][A18,17,16][32k] -const SYS_DD17PB = 0xC1 - -// Port C - HShift[HS5..1,SB3..1] Horisontal shift -const SYS_DD17PC = 0xC2 - -/* - * SYS_DD17CTR - * Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI] - * Set bit: [0][xxx][bbb][0|1] - */ -const SYS_DD17CTR = 0xC3 - -/* - * КР580ВВ55 DD67 - */ - -// LPT_DD67PA Port A - Printer Data -const LPT_DD67PA = 0xE0 - -// VID_DD67PB Port B - Video control [VSU,C/M,FL3:1,COL3:1] -const VID_DD67PB = 0xE1 - -// DD67PC Port C - [USER3:1, STB-LP, BELL, TAPE3:1] -const DD67PC = 0xE2 - -/* - * DD67CTR - * Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI] - * Set bit: [0][xxx][bbb][0|1] - */ -const DD67CTR = 0xE3 + } +} diff --git a/okean240/keyboard.go b/okean240/keyboard.go new file mode 100644 index 0000000..ddc60e2 --- /dev/null +++ b/okean240/keyboard.go @@ -0,0 +1,63 @@ +package okean240 + +import ( + "fyne.io/fyne/v2" + log "github.com/sirupsen/logrus" +) + +func (c *ComputerType) PutKey(key *fyne.KeyEvent) { + + if key.Name == fyne.KeyUnknown { + log.Debugf("Unknown key scancode: %X", key.Physical.ScanCode) + return + } + + if len(c.kbdBuffer) < KbdBufferSize { + + var code byte + + if (c.ioPorts[KBD_DD78PB] & 0x40) == 0 { + // No shift + code = RemapKey[key.Name] + } else { + // Shift + code = RemapKeyShift[key.Name] + } + c.ioPorts[KBD_DD78PB] &= 0x1f + if code != 0 { + c.ioPorts[KBD_DD78PA] = code + c.ioPorts[PIC_DD75RS] |= Rst1KbdFlag + } else { + switch key.Name { + case "LeftAlt", "RightAlt": + c.ioPorts[KBD_DD78PB] |= 0x80 + case "LeftControl", "RightControl": + c.ioPorts[KBD_DD78PB] |= 0x20 + case "LeftShift", "RightShift": + c.ioPorts[KBD_DD78PB] |= 0x40 + default: + log.Debugf("Unhandled KeyName: %s code: %X", key.Name, key.Physical.ScanCode) + } + } + } + +} + +/* + CTRL_C EQU 0x03 ; Warm boot + CTRL_H EQU 0x08 ; Backspace + CTRL_E EQU 0x05 ; Move to beginning of new line (Physical EOL) + CTRL_J EQU 0x0A ; LF - Line Feed + CTRL_M EQU 0x0D ; CR - Carriage Return + CTRL_P EQU 0x10 ; turn on/off printer + CTRL_R EQU 0x12 ; Repeat current cmd line + CTRL_S EQU 0x13 ; Temporary stop display data to console (aka DC3) + CTRL_U EQU 0x15 ; Cancel (erase) current cmd line + CTRL_X EQU 0x18 ; Cancel (erase) current cmd line +*/ + +func (c *ComputerType) PutCtrlKey(key byte) { + c.ioPorts[KBD_DD78PA] = key + c.ioPorts[PIC_DD75RS] |= Rst1KbdFlag + c.ioPorts[KBD_DD78PB] &= 0x1f | 0x20 +} diff --git a/okean240/memory.go b/okean240/memory.go index 14721d2..eb4edc3 100644 --- a/okean240/memory.go +++ b/okean240/memory.go @@ -38,7 +38,7 @@ type Memory struct { } type MemoryInterface interface { - // Init - Initialize memory at "computer start" + // Init - Initialize memory at "computer started" Init(rom0 string, rom1 string) // Configure - Set memory configuration Configure(value byte) @@ -64,7 +64,7 @@ func (m *Memory) Init(monFile string, cmpFile string) { m.allMemory[block] = &rb } - // Load ROM files and init ROM0,1 + // Command ROM files and init ROM0,1 // Read the entire file into a byte slice rom0bin, err := os.ReadFile(monFile) if err != nil { diff --git a/okean240/sio8251.go b/okean240/sio8251.go index 47cfdf2..c6e7826 100644 --- a/okean240/sio8251.go +++ b/okean240/sio8251.go @@ -1,19 +1,137 @@ package okean240 +const I8251DSRFlag = 0x80 +const I8251SynDetFlag = 0x40 +const I8251FrameErrorFlag = 0x20 +const I8251OverrunErrorFlag = 0x10 +const I8251ParityErrorFlag = 0x08 +const I8251TxEnableFlag = 0x04 +const I8251RxReadyFlag = 0x02 +const I8251TxReadyFlag = 0x01 +const I8251TxBuffMaxLen = 16 + +const ( + Sio8251Reset = iota + Sio8251LoadSyncChar1 + Sio8251LoadSyncChar2 + Sio8251LoadCommand +) + type Sio8251 struct { - counter uint64 + counter uint64 + mode byte + initState byte + syncChar1 byte + syncChar2 byte + bufferRx []byte + bufferTx []byte + rxe bool + txe bool } type Sio8251Interface interface { Tick() + Status() byte + Reset() + Command(value byte) + Send(value byte) + Receive() byte } func NewSio8251() *Sio8251 { return &Sio8251{ - counter: 0, + counter: 0, + mode: 0, + initState: 0, + rxe: false, + txe: false, + bufferRx: []byte{}, + bufferTx: []byte{}, } } func (s *Sio8251) Tick() { s.counter++ } + +// Status i8251 status [RST,RQ_RX,RST_ERR,PAUSE,RX_EN,RX_RDY,TX_RDY] +func (s *Sio8251) Status() byte { + var status byte = 0 + if len(s.bufferRx) > 0 { + status |= I8251RxReadyFlag + } + if len(s.bufferTx) < I8251TxBuffMaxLen { + status |= I8251TxReadyFlag + } + if s.txe { + status |= I8251TxEnableFlag + } + return status +} + +func (s *Sio8251) Reset() { + s.counter = 0 + s.mode = 0 + s.initState = 0 + s.bufferRx = make([]byte, 8) + s.bufferTx = make([]byte, I8251TxBuffMaxLen) + s.rxe = false + s.txe = false +} + +func (s *Sio8251) Command(value byte) { + switch s.initState { + case Sio8251Reset: + s.mode = value + if s.mode&0x03 > 0 { + // SYNC + s.initState = Sio8251LoadSyncChar1 + } + // ASYNC + s.initState = Sio8251LoadCommand + case Sio8251LoadSyncChar1: + s.mode = value + if s.mode&0x80 == 0 { // SYNC DOUBLE + s.initState = Sio8251LoadSyncChar2 + } + case Sio8251LoadSyncChar2: + s.mode = value + s.initState = Sio8251LoadCommand + case Sio8251LoadCommand: + // value = command + if value&0x40 != 0 { + // RESET CMD + s.Reset() + } else { + // Set RXE, TXE + if value&0x04 != 0 { + s.rxe = true + } else { + s.rxe = false + } + if value&0x01 != 0 { + s.txe = true + } else { + s.txe = false + } + } + } + +} + +func (s *Sio8251) Send(value byte) { + if s.txe { + s.bufferTx = append(s.bufferTx, value) + } +} + +func (s *Sio8251) Receive() byte { + if s.rxe { + if len(s.bufferRx) > 0 { + res := s.bufferRx[0] + s.bufferRx = s.bufferRx[1:] + return res + } + } + return 0 +} diff --git a/okean240/tmr8253.go b/okean240/tmr8253.go index 201d503..507133e 100644 --- a/okean240/tmr8253.go +++ b/okean240/tmr8253.go @@ -1,14 +1,16 @@ package okean240 /* - Timer config: [sc1,sc0][rl1,rl0][m2,m1,m0][bcd] - sc - timer, rl=01-LSB, 10-MSB, 11-LSB+MSB - mode 000 - int on fin, + Timer config byte: [sc1:0][rl1:0][m2:0][bcd] + sc1:0 - timer No + rl=01-LSB, 10-MSB, 11-LSB+MSB + mode 000 - intr on fin, 001 - one shot, x10 - rate gen, x11 - sq wave */ +// Timer work modes const ( TimerModeIntOnFin = iota TimerModeOneShot @@ -16,21 +18,22 @@ const ( TimerModeSqWave ) +// Timer load counter modes const ( TimerRLMsbLsb = iota - TimerRLLsbLsb + TimerRLLsb TimerRLMsb TimerRLLsbMsb ) type Timer8253Ch struct { - rl byte - mode byte - bcd bool - load uint16 - counter uint16 - fb bool - start bool + rl byte // load mode + mode byte // counter mode + bcd bool // decimal/BCD load mode + load uint16 // value to count from + counter uint16 // timer counter + fb bool // first byte load flag + started bool // true if timer started fired bool } type Timer8253 struct { @@ -60,22 +63,22 @@ func NewTimer8253() *Timer8253 { func (t *Timer8253) Tick(chNo int) { tmr := &t.channel[chNo] - if tmr.start { + if tmr.started { tmr.counter-- if tmr.counter == 0 { switch tmr.mode { case TimerModeIntOnFin: { - tmr.start = false + tmr.started = false tmr.fired = true } case TimerModeOneShot: - tmr.start = false + tmr.started = false case TimerModeRateGen: - tmr.start = false + tmr.started = false case TimerModeSqWave: { - tmr.start = true + tmr.started = true tmr.counter = tmr.load tmr.fired = true } @@ -97,13 +100,13 @@ func (t *Timer8253) Fired(chNo int) bool { } func (t *Timer8253) Start(chNo int) bool { - return t.channel[chNo].start + return t.channel[chNo].started } func (t *Timer8253) Configure(value byte) { chNo := (value & 0xC0) >> 6 rl := value & 0x30 >> 4 - t.channel[chNo].start = false + t.channel[chNo].started = false t.channel[chNo].rl = rl t.channel[chNo].mode = (value & 0x0E) >> 1 t.channel[chNo].fb = true @@ -113,7 +116,8 @@ func (t *Timer8253) Configure(value byte) { func (t *Timer8253) Load(chNo byte, value byte) { timer := &t.channel[chNo] - if timer.rl == 0 { + switch timer.rl { + case TimerRLMsbLsb: // MSB+LSB if timer.fb { // MSB @@ -122,17 +126,17 @@ func (t *Timer8253) Load(chNo byte, value byte) { } else { // LSB timer.load |= uint16(value) - timer.start = true + timer.started = true } - } else if timer.rl == 1 { + case TimerRLLsb: // LSB Only timer.load = (timer.load & 0xff00) | uint16(value) - timer.start = true - } else if timer.rl == 2 { + timer.started = true + case TimerRLMsb: // MSB Only timer.load = (timer.load & 0x00ff) | (uint16(value) << 8) - timer.start = true - } else { + timer.started = true + case TimerRLLsbMsb: // LSB+MSB if timer.fb { // LSB @@ -141,7 +145,7 @@ func (t *Timer8253) Load(chNo byte, value byte) { } else { // MSB timer.load = (uint16(value) << 8) | (timer.load & 0x00ff) - timer.start = true + timer.started = true timer.counter = timer.load } } diff --git a/okemu.log b/okemu.log new file mode 100644 index 0000000..e69de29 diff --git a/okemu.yml b/okemu.yml index 8cfc7d7..6a80b08 100644 --- a/okemu.yml +++ b/okemu.yml @@ -1,4 +1,4 @@ logFile: "okemu.log" logLevel: "info" -monitorFile: "rom/MON_v5.bin" -cpmFile: "rom/CPM_v5.bin" +monitorFile: "rom/MON_r6.bin" +cpmFile: "rom/CPM_r7.bin" diff --git a/rom/CPM_r5.bin b/rom/CPM_r5.bin new file mode 100644 index 0000000000000000000000000000000000000000..ca29f858fc055984d76afe66d1003f0ae303258d GIT binary patch literal 8192 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