From ed43a19a10599b56ba5ae12f573b7f06cea3c9a6 Mon Sep 17 00:00:00 2001 From: Roman Boykov Date: Tue, 10 Mar 2026 15:00:22 +0300 Subject: [PATCH] Additional floppy drive C 720k --- config/config.go | 2 + floppy.okd | Bin 737280 -> 0 bytes main.go | 31 +++--- okean240/computer.go | 69 +++++++------ okean240/fdc/fdc.go | 240 ++++++++++++++++++++++++++----------------- okemu.yml | 2 + z80/c99/helper.go | 6 +- z80/cpu.go | 2 + 8 files changed, 213 insertions(+), 139 deletions(-) delete mode 100644 floppy.okd diff --git a/config/config.go b/config/config.go index cd0e23c..9f48ae5 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,8 @@ type OkEmuConfig struct { LogLevel string `yaml:"logLevel"` MonitorFile string `yaml:"monitorFile"` CPMFile string `yaml:"cpmFile"` + FloppyB string `yaml:"floppyB"` + FloppyC string `yaml:"floppyC"` } var config *OkEmuConfig diff --git a/floppy.okd b/floppy.okd deleted file mode 100644 index 675189f3d0ee2cd53d005de9a62542d3b0e1d703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737280 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 nextClock { - nextClock = ticks + computer.Do()*ticksPerTicker + if fullSpeed.Load() { + computer.Do() + } else { + if ticks >= nextClock { + nextClock = ticks + computer.Do()*ticksPerTact + } } + //computer.Do() if needReset { computer.Reset() needReset = false } - //if ticks > ticksCPU { - //ticksCPU = ticks + computer.Do()*2 - //} } } diff --git a/okean240/computer.go b/okean240/computer.go index ae62f34..03e8a55 100644 --- a/okean240/computer.go +++ b/okean240/computer.go @@ -2,20 +2,25 @@ package okean240 import ( _ "embed" + "encoding/binary" "image/color" "okemu/config" "okemu/okean240/fdc" "okemu/okean240/pic" "okemu/okean240/pit" "okemu/okean240/usart" - "okemu/z80/js" + "os" + + //"okemu/z80" + "okemu/z80/c99" + //"okemu/z80/js" "fyne.io/fyne/v2" log "github.com/sirupsen/logrus" ) type ComputerType struct { - cpu *js.Z80 + cpu *c99.Z80 memory Memory ioPorts [256]byte cycles uint64 @@ -73,7 +78,7 @@ func New(cfg *config.OkEmuConfig) *ComputerType { c.memory = Memory{} c.memory.Init(cfg.MonitorFile, cfg.CPMFile) - c.cpu = js.New(&c) + c.cpu = c99.New(&c) c.cycles = 0 c.dd17EnableOut = false @@ -90,7 +95,7 @@ func New(cfg *config.OkEmuConfig) *ComputerType { c.pit = pit.New() c.usart = usart.New() c.pic = pic.New() - c.fdc = fdc.New() + c.fdc = fdc.New(cfg) return &c } @@ -110,14 +115,11 @@ func (c *ComputerType) Reset() { } func (c *ComputerType) Do() uint64 { - //s := c.cpu.GetState() - //if s.PC == 0xe0db { - // log.Debugf("breakpoint") - //} ticks := c.cpu.RunInstruction() c.cycles += ticks - //if c.cpu.PC == 0xFF26 { - // log.Debugf("%4X: H:%X L:%X A:%X B: %X C: %X D: %X E: %X", c.cpu.PC, c.cpu.H, c.cpu.L, c.cpu.A, c.cpu.B, c.cpu.C, c.cpu.D, c.cpu.E) + //pc := c.cpu.GetState().PC + //if pc >= 0xfea3 && pc <= 0xff25 { + // c.cpu.DebugOutput() //} return ticks } @@ -245,25 +247,28 @@ func (c *ComputerType) SetRamBytes(bytes []byte) { //c.cpu.PC = 0x100 } -//func (c *ComputerType) Dump(start uint16, length uint16) { -// file, err := os.Create("dump.dat") -// if err != nil { -// log.Error(err) -// return -// } -// defer func(file *os.File) { -// err := file.Close() -// if err != nil { -// log.Error(err) -// } -// }(file) -// -// var buffer []byte -// for addr := 0; addr < 65535; addr++ { -// buffer = append(buffer, c.memory.MemRead(uint16(addr))) -// } -// err = binary.Write(file, binary.LittleEndian, buffer) -// if err != nil { -// log.Error("Save memory dump failed:", err) -// } -//} +func (c *ComputerType) Dump(start uint16, length uint16) { + file, err := os.Create("dump.dat") + if err != nil { + log.Error(err) + return + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Error(err) + } + }(file) + + var buffer []byte + for addr := start; addr < start+length; addr++ { + buffer = append(buffer, c.memory.MemRead(addr)) + } + _, err = file.Write(buffer) + err = binary.Write(file, binary.LittleEndian, buffer) + if err != nil { + log.Error("Save memory dump failed:", err) + } else { + log.Debug("Memory dump saved successfully") + } +} diff --git a/okean240/fdc/fdc.go b/okean240/fdc/fdc.go index e7554a7..815d215 100644 --- a/okean240/fdc/fdc.go +++ b/okean240/fdc/fdc.go @@ -8,7 +8,9 @@ package fdc */ import ( + "bytes" "encoding/binary" + "okemu/config" "os" "slices" "strconv" @@ -18,6 +20,7 @@ import ( // Floppy parameters const ( + TotalDrives = 2 FloppySizeK = 720 SectorSize = 512 SideCount = 2 @@ -69,14 +72,16 @@ type FloppyDriveController struct { sectorNo byte trackNo byte drq byte - // FloppyStorage - sectors [SizeInSectors]SectorType + // FloppyStorage B and C + sectors [TotalDrives][SizeInSectors]SectorType data byte status byte lastCmd byte //curSector *SectorType bytePtr uint16 trackBuffer []byte + floppyBFile string + floppyCFile string } type FloppyDriveControllerInterface interface { @@ -95,8 +100,12 @@ type FloppyDriveControllerInterface interface { Sector() byte } +func getSectorNo(side byte, track byte, sector byte) uint16 { + return uint16(side)*SectorsPerSide + uint16(track)*SectorPerTrack + uint16(sector) - 1 +} + func (f *FloppyDriveController) GetSectorNo() uint16 { - return uint16(f.sideNo)*SectorsPerSide + uint16(f.trackNo)*SectorPerTrack + uint16(f.sectorNo) - 1 + return getSectorNo(f.sideNo, f.trackNo, f.sectorNo) } func (f *FloppyDriveController) SetFloppy(val byte) { @@ -104,14 +113,14 @@ func (f *FloppyDriveController) SetFloppy(val byte) { f.sideNo = val >> 5 & 0x01 f.ddEn = val >> 4 & 0x01 f.init = val >> 3 & 0x01 - f.drive = val >> 2 & 0x01 + f.drive = (^val) >> 2 & 0x01 f.mot1 = val >> 1 & 0x01 f.mot0 = val & 0x01 } func (f *FloppyDriveController) GetFloppy() byte { // RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT - floppy := f.intRq | (f.mot0 << 1) | (f.mot1 << 2) | (f.drive << 3) | (f.sideNo << 6) | (f.motSt << 7) + floppy := f.intRq | (f.mot0 << 1) | (f.mot1 << 2) | ((^f.drive & 1) << 3) | (f.sideNo << 6) | (f.motSt << 7) return floppy } @@ -119,34 +128,34 @@ func (f *FloppyDriveController) SetCmd(value byte) { f.lastCmd = value >> 4 switch f.lastCmd { case CmdRestore: - log.Debug("CMD Restore (seek trackNo 0)") + log.Trace("CMD Restore (seek trackNo 0)") f.trackNo = 0 f.status = StatusTR0 | StatusHeadLoaded // TR0 & Head loaded case CmdSeek: - log.Debugf("CMD Seek %x", value&0xf) + log.Tracef("CMD Seek %x", value&0xf) f.status = StatusHeadLoaded f.trackNo = f.data case CmdStep: - log.Debugf("CMD Step %x", value&0xf) + log.Tracef("CMD Step %x", value&0xf) f.status = StatusHeadLoaded f.trackNo = f.data case CmdStepIn: - log.Debugf("CMD StepIn (Next track) %x", value&0xf) + log.Tracef("CMD StepIn (Next track) %x", value&0xf) f.status = StatusHeadLoaded if f.trackNo < TracksCount { f.trackNo++ } case CmdStepOut: - log.Debugf("CMD StepOut (Previous track) %x", value&0xf) + log.Tracef("CMD StepOut (Previous track) %x", value&0xf) f.status = StatusHeadLoaded if f.trackNo > 0 { f.trackNo-- } case CmdReadSector: sectorNo := f.GetSectorNo() - log.Debugf("CMD Read single sectorNo: %d", sectorNo) + log.Tracef("CMD Read single sectorNo: %d", sectorNo) if sectorNo < SizeInSectors { - f.trackBuffer = slices.Clone(f.sectors[sectorNo]) + f.trackBuffer = slices.Clone(f.sectors[f.drive][sectorNo]) f.drq = 1 f.status = 0x00 } else { @@ -157,14 +166,14 @@ func (f *FloppyDriveController) SetCmd(value byte) { sectorNo := f.GetSectorNo() f.trackBuffer = []byte{} for c := 0; c < SectorPerTrack; c++ { - f.trackBuffer = slices.Concat(f.trackBuffer, f.sectors[sectorNo]) + f.trackBuffer = slices.Concat(f.trackBuffer, f.sectors[f.drive][sectorNo]) sectorNo++ } f.drq = 1 f.status = 0x0 case CmdWriteSector: sectorNo := f.GetSectorNo() - log.Debugf("CMD Write Sector %d", sectorNo) + log.Tracef("CMD Write Sector %d", sectorNo) if sectorNo < SizeInSectors { f.bytePtr = 0 f.drq = 1 @@ -175,12 +184,12 @@ func (f *FloppyDriveController) SetCmd(value byte) { f.status = StatusRNF } case CmdWriteTrack: - log.Debugf("CMD Write Track %x", f.trackNo) + log.Tracef("CMD Write Track %x", f.trackNo) f.status = 0x00 f.trackBuffer = []byte{} f.drq = 1 default: - log.Debugf("Unknown CMD: %x VAL: %x", f.lastCmd, value&0xf) + log.Errorf("Unknown CMD: %x VAL: %x", f.lastCmd, value&0xf) } } @@ -189,7 +198,7 @@ func (f *FloppyDriveController) Status() byte { } func (f *FloppyDriveController) SetTrackNo(value byte) { - //log.Debugf("FDC Track: %d", value) + //log.Tracef("FDC Track: %d", value) if value > TracksCount { f.status |= 0x10 /// RNF log.Error("Track not found!") @@ -199,17 +208,17 @@ func (f *FloppyDriveController) SetTrackNo(value byte) { } func (f *FloppyDriveController) SetSectorNo(value byte) { - //log.Debugf("FDC Sector: %d", value) + //log.Tracef("FDC Sector: %d", value) if value > SectorPerTrack { f.status |= 0x10 - log.Error("Record not found!") + log.Errorf("Record not found %d!", value) } else { f.sectorNo = value } } func (f *FloppyDriveController) SetData(value byte) { - //log.Debugf("FCD Data: %d", value) + //log.Tracef("FCD Data: %d", value) if f.lastCmd == CmdWriteTrack { if len(f.trackBuffer) < TrackBufferSize { f.trackBuffer = append(f.trackBuffer, value) @@ -217,6 +226,7 @@ func (f *FloppyDriveController) SetData(value byte) { f.status = 0x00 } else { //f.dump() + f.writeTrack() f.drq = 0 f.status = 0x00 f.lastCmd = CmdNoCommand @@ -232,13 +242,44 @@ func (f *FloppyDriveController) SetData(value byte) { } if len(f.trackBuffer) == SectorSize { f.drq = 0 - f.sectors[f.GetSectorNo()] = slices.Clone(f.trackBuffer) + f.sectors[f.drive][f.GetSectorNo()] = slices.Clone(f.trackBuffer) f.lastCmd = CmdNoCommand } } f.data = value } +const SectorInfoSize = 626 +const SectorInfoOffset = 0x0092 +const TrackNoOffset = 0x0010 +const SideNoOffset = 0x0011 +const SectorNoOffset = 0x0012 +const SectorLengthOffset = 0x0013 +const SectorDataOffset = 0x003b + +var SectorLengths = []int{128, 256, 512, 1024} + +func (f *FloppyDriveController) writeTrack() { + // skip header + ptr := SectorInfoOffset + // repeat for every sector on track + for sec := 0; sec < SectorPerTrack; sec++ { + // get info from header + trackNo := f.trackBuffer[ptr+TrackNoOffset] + sideNo := f.trackBuffer[ptr+SideNoOffset] + sectorNo := f.trackBuffer[ptr+SectorNoOffset] + sectorLength := SectorLengths[f.trackBuffer[ptr+SectorLengthOffset]&0x03] + // get sector data + sectorData := f.trackBuffer[ptr+SectorDataOffset : ptr+SectorDataOffset+sectorLength] + absSector := getSectorNo(sideNo, trackNo, sectorNo) + log.Debugf("Write Drive: %d; side:%d; T: %d S: %d Len: %d Data: [%X..%X]; Abs sector: %d", f.drive, sideNo, trackNo, sectorNo, len(sectorData), sectorData[0], sectorData[len(sectorData)-1], absSector) + // write data to sector buffer + f.sectors[f.drive][absSector] = slices.Clone(sectorData) + // shift pointer to next sector info block + ptr += SectorInfoSize + } +} + func (f *FloppyDriveController) Data() byte { switch f.lastCmd { case CmdReadSector, CmdReadSectorMulti: @@ -263,90 +304,48 @@ func (f *FloppyDriveController) Drq() byte { } func (f *FloppyDriveController) LoadFloppy() { - log.Debug("Load Floppy content.") - file, err := os.Open("floppy.okd") - if err != nil { - log.Error(err) - return - } - defer func(file *os.File) { - err := file.Close() - if err != nil { - log.Error(err) - } - }(file) - - for sector := 0; sector < SizeInSectors; sector++ { - var n int - n, err = file.Read(f.sectors[sector]) - if n != SectorSize { - log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize) - } - // err = binary.Read(file, binary.LittleEndian, f.sectors[sector]) - if err != nil { - log.Error("Load floppy content failed:", err) - break - } - - } - + log.Debug("Load Floppy B") + loadFloppy(&f.sectors[0], f.floppyBFile) + log.Debug("Load Floppy C") + loadFloppy(&f.sectors[1], f.floppyCFile) } func (f *FloppyDriveController) SaveFloppy() { - log.Debug("Save Floppy content.") - file, err := os.Create("floppy.okd") - if err != nil { - log.Error(err) - return - } - defer func(file *os.File) { - err := file.Close() - if err != nil { - log.Error(err) - } - }(file) - - // Write the struct to the file in little-endian byte order - for sector := 0; sector < SizeInSectors; sector++ { - var n int - n, err = file.Write(f.sectors[sector]) - if n != SectorSize { - log.Errorf("Save floppy error, sector %d size: %d <> %d", sector, n, SectorSize) - } - if err != nil { - log.Error("Save floppy content failed:", err) - break - } - } - + log.Debug("Save Floppy B") + saveFloppy(&f.sectors[0], f.floppyBFile) + log.Debug("Save Floppy C") + saveFloppy(&f.sectors[1], f.floppyCFile) } -func New() *FloppyDriveController { - sec := [SizeInSectors]SectorType{} - for i := 0; i < SizeInSectors; i++ { - sec[i] = make([]byte, SectorSize) - for s := 0; s < SectorSize; s++ { - sec[i][s] = 0xE5 +func New(conf *config.OkEmuConfig) *FloppyDriveController { + sec := [2][SizeInSectors]SectorType{} + // for each drive + for d := 0; d < TotalDrives; d++ { + // for each sector + for i := 0; i < SizeInSectors; i++ { + sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize) } } return &FloppyDriveController{ - sideNo: 0, - ddEn: 0, - init: 0, - drive: 0, - mot1: 0, - mot0: 0, - intRq: 0, - motSt: 0, - drq: 0, - lastCmd: 0xff, - sectors: sec, - bytePtr: 0xffff, + sideNo: 0, + ddEn: 0, + init: 0, + drive: 0, + mot1: 0, + mot0: 0, + intRq: 0, + motSt: 0, + drq: 0, + lastCmd: 0xff, + sectors: sec, + bytePtr: 0xffff, + floppyBFile: conf.FloppyB, + floppyCFile: conf.FloppyC, } } func (f *FloppyDriveController) dump() { - log.Debug("Dump Buffer content.") + log.Trace("Dump Buffer content.") file, err := os.Create("track-" + strconv.Itoa(int(f.trackNo)) + ".dat") if err != nil { log.Error(err) @@ -375,3 +374,58 @@ func (f *FloppyDriveController) Sector() byte { } // + +func loadFloppy(sectors *[SizeInSectors]SectorType, fileName string) { + log.Debugf("Load Floppy content from file %s.", fileName) + file, err := os.Open(fileName) + if err != nil { + log.Error(err) + return + } + + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Error(err) + } + }(file) + + for sector := 0; sector < SizeInSectors; sector++ { + var n int + n, err = file.Read(sectors[sector]) + if n != SectorSize { + log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize) + } + if err != nil { + log.Error("Load floppy content failed:", err) + break + } + } +} + +func saveFloppy(sectors *[SizeInSectors]SectorType, fileName string) { + log.Debugf("Save Floppy to file %s.", fileName) + file, err := os.Create(fileName) + if err != nil { + log.Error(err) + return + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Error(err) + } + }(file) + + for sector := 0; sector < SizeInSectors; sector++ { + var n int + n, err = file.Write(sectors[sector]) + if n != SectorSize { + log.Errorf("Save floppy error, sector %d size: %d <> %d", sector, n, SectorSize) + } + if err != nil { + log.Error("Save floppy content failed:", err) + break + } + } +} diff --git a/okemu.yml b/okemu.yml index d1de349..a9a416c 100644 --- a/okemu.yml +++ b/okemu.yml @@ -2,3 +2,5 @@ logFile: "okemu.log" logLevel: "info" monitorFile: "rom/MON_r8_9c6c6546.bin" cpmFile: "rom/CPM_r8_bc0695e4.bin" +floppyB: "floppy/floppyB.okd" +floppyC: "floppy/floppyC.okd" \ No newline at end of file diff --git a/z80/c99/helper.go b/z80/c99/helper.go index 6cf3020..81a3004 100644 --- a/z80/c99/helper.go +++ b/z80/c99/helper.go @@ -155,7 +155,7 @@ func (z *Z80) updateXY(result byte) { z.xf = result&0x08 != 0 } -func (z *Z80) debugOutput() { +func (z *Z80) DebugOutput() { log.Debugf("PC: %04X, AF: %04X, BC: %04X, DE: %04X, HL: %04X, SP: %04X, IX: %04X, IY: %04X, I: %02X, R: %02X", z.pc, (uint16(z.a)<<8)|uint16(z.get_f()), z.get_bc(), z.get_de(), z.get_hl(), z.sp, z.ix, z.iy, z.i, z.r) @@ -163,3 +163,7 @@ func (z *Z80) debugOutput() { log.Debugf("\t(%02X %02X %02X %02X), cyc: %d\n", z.rb(z.pc), z.rb(z.pc+1), z.rb(z.pc+2), z.rb(z.pc+3), z.cyc) } + +func (z *Z80) Reset() { + +} diff --git a/z80/cpu.go b/z80/cpu.go index 99e771d..201f023 100644 --- a/z80/cpu.go +++ b/z80/cpu.go @@ -22,6 +22,8 @@ type CPUInterface interface { GetState() *Z80CPU // SetState Set current CPU state SetState(state *Z80CPU) + // DebugOutput out current CPU state + DebugOutput() } // FlagsType - Processor flags