commit 76829b80cc71360b440751cfb8894ba11b0701f2 Author: Roman Boykov Date: Fri Feb 27 13:57:49 2026 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb0b961 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.bak +*.lst +*.tmp diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ac35241 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/z80em.iml b/.idea/z80em.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/z80em.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..332cb7d --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module z80em + +go 1.25 diff --git a/main.go b/main.go new file mode 100644 index 0000000..df6b8ab --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" +) + +//TIP

To run your code, right-click the code and select Run.

Alternatively, click +// the icon in the gutter and select the Run menu item from here.

+ +func main() { + //TIP

Press when your caret is at the underlined text + // to see how GoLand suggests fixing the warning.

Alternatively, if available, click the lightbulb to view possible fixes.

+ s := "gopher" + fmt.Printf("Hello and welcome, %s!\n", s) + + for i := 1; i <= 5; i++ { + //TIP

To start your debugging session, right-click your code in the editor and select the Debug option.

We have set one breakpoint + // for you, but you can always add more by pressing .

+ fmt.Println("i =", 100/i) + } +} \ No newline at end of file diff --git a/z80em/core.go b/z80em/core.go new file mode 100644 index 0000000..4609f89 --- /dev/null +++ b/z80em/core.go @@ -0,0 +1,27 @@ +package z80em + +const OP_HALT = 0x76 +const OP_LD_B_B = 0x40 +const OP_LD_A_A = 0x7f +const OP_ADD_A_B = 0x80 +const OP_RET_NZ = 0xc0 + +var CYCLE_COUNTS = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +var PARITY_BITS = []bool{ + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, +} diff --git a/z80em/z80em.go b/z80em/z80em.go new file mode 100644 index 0000000..516e595 --- /dev/null +++ b/z80em/z80em.go @@ -0,0 +1,550 @@ +package z80em + +const SpDefault uint16 = 0xdff0 + +// FlagsType - Processor flags +type FlagsType struct { + S bool + Z bool + Y bool + H bool + X bool + P bool + N bool + C bool +} + +// StateType - Processor state +type StateType struct { + A byte + B byte + C byte + D byte + E byte + H byte + L byte + APrime byte + BPrime byte + CPrime byte + DPrime byte + EPrime byte + HPrime byte + LPrime byte + IX byte + IY byte + I byte + R byte + SP uint16 + PC uint16 + Flags FlagsType + FlagsPrime FlagsType + + IMode byte + Iff1 byte + Iff2 byte + Halted bool + DoDelayedDI bool + DoDelayedEI bool + CycleCounter byte + interruptOccurred bool + core MemIoRW +} + +type MemIoRW interface { + // M1MemRead Read byte from memory for specified address + M1MemRead(addr uint16) byte + // MemRead Read byte from memory for specified address + MemRead(addr uint16) byte + // MemWrite Write byte to memory to specified address + MemWrite(addr uint16, val byte) + // IORead Read byte from specified port + IORead(port uint16) byte + // IOWrite Write byte to specified port + IOWrite(port uint16, val byte) +} + +type Z80em interface { + // Reset CPU to initial state + Reset() + // RunInstruction Run single instruction, return number of CPU cycles + RunInstruction() byte + // GetState Get current CPU state + GetState() *StateType + // SetState Set current CPU state + SetState(state *StateType) + // setFlagsRegister Set value for CPU flags by specified byte [7:0] = SZYHXPNC + setFlagsRegister(flags byte) + + decodeInstruction(opcode byte) byte + pushWord(pc uint16) +} + +func (s StateType) Reset() { + s.A = 0 + s.R = 0 + s.SP = SpDefault + s.PC = 0 + s.setFlagsRegister(0) + // Interrupts disabled + s.IMode = 0 + s.Iff1 = 0 + s.Iff2 = 0 + s.interruptOccurred = false + + // Start not halted + s.Halted = false + s.DoDelayedDI = false + s.DoDelayedEI = false + // no cycles + s.CycleCounter = 0 +} + +func (s StateType) GetState() *StateType { + return &StateType{ + A: s.A, + B: s.B, + C: s.C, + D: s.D, + E: s.E, + H: s.H, + L: s.L, + APrime: s.APrime, + BPrime: s.BPrime, + CPrime: s.CPrime, + DPrime: s.DPrime, + EPrime: s.EPrime, + HPrime: s.HPrime, + IX: s.IX, + IY: s.IY, + R: s.R, + SP: s.SP, + PC: s.PC, + Flags: s.Flags, + FlagsPrime: s.FlagsPrime, + IMode: s.IMode, + Iff1: s.Iff1, + Iff2: s.Iff2, + Halted: s.Halted, + DoDelayedDI: s.DoDelayedDI, + DoDelayedEI: s.DoDelayedEI, + CycleCounter: s.CycleCounter, + } +} + +func (s StateType) SetState(state *StateType) { + s.A = state.A + s.B = state.B + s.C = state.C + s.D = state.D + s.E = state.E + s.H = state.H + s.L = state.L + s.APrime = state.APrime + s.BPrime = state.BPrime + s.CPrime = state.CPrime + s.DPrime = state.DPrime + s.EPrime = state.EPrime + s.HPrime = state.HPrime + s.IX = state.IX + s.IY = state.IY + s.I = state.I + s.R = state.R + s.SP = state.SP + s.PC = state.PC + s.Flags = state.Flags + s.FlagsPrime = state.FlagsPrime + s.IMode = state.IMode + s.Iff1 = state.Iff1 + s.Iff2 = state.Iff2 + s.Halted = state.Halted + s.DoDelayedDI = state.DoDelayedDI + s.DoDelayedEI = state.DoDelayedEI + s.CycleCounter = state.CycleCounter +} + +// New Create new +func New(memIoRW MemIoRW) *StateType { + return &StateType{ + A: 0, + B: 0, + C: 0, + D: 0, + E: 0, + H: 0, + L: 0, + APrime: 0, + BPrime: 0, + CPrime: 0, + DPrime: 0, + EPrime: 0, + HPrime: 0, + IX: 0, + IY: 0, + I: 0, + + R: 0, + SP: SpDefault, + PC: 0, + Flags: FlagsType{false, false, false, false, false, false, false, false}, + FlagsPrime: FlagsType{false, false, false, false, false, false, false, false}, + IMode: 0, + Iff1: 0, + Iff2: 0, + Halted: false, + DoDelayedDI: false, + DoDelayedEI: false, + CycleCounter: 0, + interruptOccurred: false, + core: memIoRW, + } +} + +func (s StateType) RunInstruction() byte { + + // R is incremented at the start of every instruction cycle, + // before the instruction actually runs. + // The high bit of R is not affected by this increment, + // it can only be changed using the LD R, A instruction. + // Note: also a HALT does increment the R register. + s.R = (s.R & 0x80) | (((s.R & 0x7f) + 1) & 0x7f) + + if !s.Halted { + // If the previous instruction was a DI or an EI, + // we'll need to disable or enable interrupts + // after whatever instruction we're about to run is finished. + doingDelayedDi := false + doingDelayedEi := false + if s.DoDelayedDI { + s.DoDelayedDI = false + doingDelayedDi = true + } else if s.DoDelayedEI { + s.DoDelayedEI = false + doingDelayedEi = true + } + + // Read the byte at the PC and run the instruction it encodes. + opcode := s.core.M1MemRead(s.PC) + s.decodeInstruction(opcode) + + // HALT does not increase the PC + if !s.Halted { + s.PC++ + } + + // Actually do the delayed interrupt disable/enable if we have one. + if doingDelayedDi { + s.Iff1 = 0 + s.Iff2 = 0 + } else if doingDelayedEi { + s.Iff1 = 1 + s.Iff2 = 1 + } + + // And finally clear out the cycle counter for the next instruction + // before returning it to the emulator core. + cycleCounter := s.CycleCounter + s.CycleCounter = 0 + return cycleCounter + } else { // HALTED + // During HALT, NOPs are executed which is 4T + s.core.M1MemRead(s.PC) // HALT does a normal M1 fetch to keep the memory refresh active. The result is ignored (NOP). + return 4 + } +} + +// Simulates pulsing the processor's INT (or NMI) pin +// +// nonMaskable - true if this is a non-maskable interrupt +// data - the value to be placed on the data bus, if needed +func (s StateType) interrupt(nonMaskable bool, data byte) { + if nonMaskable { + // An interrupt, if halted, does increase the PC + if s.Halted { + s.PC++ + } + + // The high bit of R is not affected by this increment, + // it can only be changed using the LD R, A instruction. + s.R = (s.R & 0x80) | (((s.R & 0x7f) + 1) & 0x7f) + + // Non-maskable interrupts are always handled the same way; + // clear IFF1 and then do a CALL 0x0066. + // Also, all interrupts reset the HALT state. + + s.Halted = false + s.Iff2 = s.Iff1 + s.Iff1 = 0 + s.pushWord(s.PC) + s.PC = 0x66 + + s.CycleCounter += 11 + } else if s.Iff1 != 0 { + // An interrupt, if halted, does increase the PC + if s.Halted { + s.PC++ + } + + // The high bit of R is not affected by this increment, + // it can only be changed using the LD R,A instruction. + s.R = (s.R & 0x80) | (((s.R & 0x7f) + 1) & 0x7f) + + s.Halted = false + s.Iff1 = 0 + s.Iff2 = 0 + + if s.IMode == 0 { + // In the 8080-compatible interrupt mode, + // decode the content of the data bus as an instruction and run it. + // it's probably a RST instruction, which pushes (PC+1) onto the stack + // so we should decrement PC before we decode the instruction + s.PC-- + s.decodeInstruction(data) + s.PC++ // increment PC upon return + s.CycleCounter += 2 + } else if s.IMode == 1 { + // Mode 1 is always just RST 0x38. + s.pushWord(s.PC) + s.PC = 0x0038 + s.CycleCounter += 13 + } else if s.IMode == 2 { + // Mode 2 uses the value on the data bus as in index + // into the vector table pointer to by the I register. + s.pushWord(s.PC) + + // The Z80 manual says that this address must be 2-byte aligned, + // but it doesn't appear that this is actually the case on the hardware, + // so we don't attempt to enforce that here. + vectorAddress := (uint16(s.I) << 8) | uint16(data) + s.PC = uint16(s.core.MemRead(vectorAddress)) | (uint16(s.core.MemRead(vectorAddress+1)) << 8) + s.CycleCounter += 19 + // A "notification" is generated so that the calling program can break on it. + s.interruptOccurred = true + } + } +} + +func (s StateType) pushWord(pc uint16) { + // TODO: Implement + panic("not yet implemented") +} + +func (s StateType) getOperand(opcode byte) byte { + switch opcode & 0x07 { + case 0: + return s.B + case 1: + return s.C + case 2: + return s.D + case 3: + return s.E + case 4: + return s.H + case 5: + return s.L + case 6: + return s.core.MemRead(uint16(s.H)<<8 | uint16(s.L)) + default: + return s.A + } +} + +func (s StateType) decodeInstruction(opcode byte) { + // Handle HALT right up front, because it fouls up our LD decoding + // by falling where LD (HL), (HL) ought to be. + if opcode == OP_HALT { + s.Halted = true + } else if opcode >= OP_LD_B_B && opcode < OP_ADD_A_B { + // This entire range is all 8-bit register loads. + // Get the operand and assign it to the correct destination. + s.load8bit(opcode, s.getOperand(opcode)) + } else if (opcode >= OP_ADD_A_B) && (opcode < OP_RET_NZ) { + // These are the 8-bit register ALU instructions. + // We'll get the operand and then use this "jump table" + // to call the correct utility function for the instruction. + s.alu8bit(opcode, s.getOperand(opcode)) + } else { + // This is one of the less formulaic instructions; + // we'll get the specific function for it from our array. + s.otherInstructions(opcode) + } + s.CycleCounter += CYCLE_COUNTS[opcode] +} + +func (s StateType) load8bit(opcode byte, operand byte) { + switch (opcode & 0x38) >> 3 { + case 0: + s.B = operand + case 1: + s.C = operand + case 2: + s.D = operand + case 3: + s.E = operand + case 4: + s.H = operand + case 5: + s.L = operand + case 6: + s.core.MemWrite(uint16(s.H)<<8|uint16(s.L), operand) + default: + s.A = operand + } +} + +func (s StateType) alu8bit(opcode byte, operand byte) { + switch (opcode & 0x38) >> 3 { + case 0: + s.doAdd(operand) + case 1: + s.doAdc(operand) + case 2: + s.doSub(operand) + case 3: + s.doSbc(operand) + case 4: + s.doAnd(operand) + case 5: + s.doXor(operand) + case 6: + s.doOr(operand) + default: + s.doCp(operand) + } +} + +func (s StateType) doAdd(operand byte) { + +} + +func (s StateType) doAdc(operand byte) { + +} + +func (s StateType) doSub(operand byte) { + +} + +func (s StateType) doSbc(operand byte) { + +} + +func (s StateType) doAnd(operand byte) { + +} + +func (s StateType) doXor(operand byte) { + +} + +func (s StateType) doOr(operand byte) { + +} + +func (s StateType) doCp(operand byte) { + +} + +func (s StateType) otherInstructions(opcode byte) { + +} + +// getFlagsRegister return whole F register +func (s StateType) getFlagsRegister() byte { + return getFlags(&s.Flags) +} + +// getFlagsRegister return whole F' register +func (s StateType) getFlagsPrimeRegister() byte { + return getFlags(&s.FlagsPrime) +} + +func getFlags(f *FlagsType) byte { + var flags byte = 0 + if f.S { + flags |= 0x80 + } + if f.Z { + flags |= 0x40 + } + + if f.Y { + flags |= 0x20 + } + + if f.H { + flags |= 0x10 + } + + if f.X { + flags |= 0x08 + } + + if f.P { + flags |= 0x04 + } + + if f.N { + flags |= 0x02 + } + + if f.C { + flags |= 0x01 + } + return flags + +} + +func (s StateType) setFlagsRegister(flags byte) { + setFlags(flags, &s.Flags) +} + +func (s StateType) setFlagsPrimeRegister(flags byte) { + setFlags(flags, &s.FlagsPrime) +} + +func setFlags(flags byte, f *FlagsType) { + f.S = flags&0x80 != 0 + f.Z = flags&0x40 != 0 + f.Y = flags&0x20 != 0 + f.H = flags&0x10 != 0 + f.X = flags&0x08 != 0 + f.P = flags&0x04 != 0 + f.N = flags&0x02 != 0 + f.C = flags&0x01 != 0 +} + +func (s StateType) updateXYFlags(result byte) { + // Most of the time, the undocumented flags + // (sometimes called X and Y, or 3 and 5), + // take their values from the corresponding bits + // of the result of the instruction, + // or from some other related value. + // This is a utility function to set those flags based on those bits. + s.Flags.Y = (result&0x20)>>5 != 0 + s.Flags.X = (result&0x08)>>3 != 0 +} + +func getParity(value byte) bool { + return PARITY_BITS[value] +} + +func (s StateType) PushWord(operand uint16) { + // Pretty obvious what this function does; given a 16-bit value, + // decrement the stack pointer, write the high byte to the new + // stack pointer location, then repeat for the low byte. + s.SP-- + s.core.MemWrite(s.SP, byte((operand&0xff00)>>8)) + s.SP-- + s.core.MemWrite(s.SP, byte(operand&0x00ff)) +} + +func (s StateType) PopWord() uint16 { + // Again, not complicated; read a byte off the top of the stack, + // increment the stack pointer, rinse and repeat. + result := uint16(s.core.MemRead(s.SP)) + s.SP++ + result |= uint16(s.core.MemRead(s.SP)) << 8 + s.SP++ + return result +}