202 lines
4.7 KiB
Go
202 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
z80 "git.tygh.ru/kiltum/emuz80go"
|
|
)
|
|
|
|
// Memory64K represents 64KB of memory
|
|
type Memory64K struct {
|
|
data [0x10000]byte // 64KB = 65536 bytes
|
|
}
|
|
|
|
// ReadByte reads a byte from memory
|
|
func (m *Memory64K) ReadByte(address uint16) byte {
|
|
return m.data[address]
|
|
}
|
|
|
|
// WriteByte writes a byte to memory
|
|
func (m *Memory64K) WriteByte(address uint16, value byte) {
|
|
m.data[address] = value
|
|
}
|
|
|
|
// ReadWord reads a word from memory
|
|
func (m *Memory64K) ReadWord(address uint16) uint16 {
|
|
return uint16(m.data[address]) | (uint16(m.data[address+1]) << 8)
|
|
}
|
|
|
|
// WriteWord writes a word to memory
|
|
func (m *Memory64K) WriteWord(address uint16, value uint16) {
|
|
m.data[address] = byte(value)
|
|
m.data[address+1] = byte(value >> 8)
|
|
}
|
|
|
|
// IOHandler handles I/O operations
|
|
type IOHandler struct{}
|
|
|
|
// ReadPort reads from an I/O port
|
|
func (io *IOHandler) ReadPort(port uint16) byte {
|
|
return 0xFF // Default value
|
|
}
|
|
|
|
// WritePort writes to an I/O port
|
|
func (io *IOHandler) WritePort(port uint16, value byte) {
|
|
// No I/O port handling needed for BDOS calls
|
|
}
|
|
|
|
// CheckInterrupt checks for interrupts
|
|
func (io *IOHandler) CheckInterrupt() bool {
|
|
return false
|
|
}
|
|
|
|
// handleBDOSCall handles CP/M BDOS calls
|
|
func handleBDOSCall(cpu *z80.CPU, memory *Memory64K) {
|
|
// BDOS is called by jumping to 0x0005
|
|
// Function number is in register C
|
|
// Parameters are in other registers depending on the function
|
|
|
|
function := cpu.C
|
|
|
|
switch function {
|
|
case 2: // Print character (character in E)
|
|
fmt.Print(string(cpu.E))
|
|
case 9: // Print string (string address in DE)
|
|
addr := cpu.GetDE()
|
|
// Read characters from memory until we encounter '$'
|
|
for {
|
|
char := memory.ReadByte(addr)
|
|
if char == '$' {
|
|
break
|
|
}
|
|
fmt.Print(string(char))
|
|
addr++
|
|
}
|
|
}
|
|
|
|
// In a real CP/M system, after handling the BDOS call,
|
|
// execution would return to the caller via a RET instruction.
|
|
// We simulate this by popping the return address from the stack
|
|
// and setting the PC to that address.
|
|
|
|
// Pop return address from stack
|
|
lowByte := memory.ReadByte(cpu.SP)
|
|
cpu.SP++
|
|
highByte := memory.ReadByte(cpu.SP)
|
|
cpu.SP++
|
|
returnAddress := (uint16(highByte) << 8) | uint16(lowByte)
|
|
|
|
// Set PC to return address
|
|
cpu.PC = returnAddress
|
|
}
|
|
|
|
// loadZEXALL loads the zexall.com file into memory at address 0x100
|
|
func loadZEXALL(memory *Memory64K, filename string) error {
|
|
// Load file data
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read file %s: %v", filename, err)
|
|
}
|
|
|
|
// Load into memory at address 0x100
|
|
loadAddress := uint16(0x100)
|
|
for i, b := range data {
|
|
addr := uint32(loadAddress) + uint32(i)
|
|
if addr < 0x10000 {
|
|
memory.data[uint16(addr)] = b
|
|
} else {
|
|
break // Memory overflow protection
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
// Create memory and IO instances
|
|
memory := &Memory64K{}
|
|
io := &IOHandler{}
|
|
|
|
// Create CPU instance
|
|
cpu := z80.New(memory, io)
|
|
|
|
// Set up initial state for CP/M program
|
|
// Stack pointer typically starts at 0xFFFF in CP/M programs
|
|
cpu.SP = 0xFFFF
|
|
|
|
// Program counter starts at 0x100 for .COM files
|
|
cpu.PC = 0x100
|
|
|
|
fmt.Printf("ZEXDOC\n")
|
|
// Load zexall.com file
|
|
err := loadZEXALL(memory, "zexdoc.com")
|
|
if err != nil {
|
|
fmt.Printf("Error: Could not load zexdoc.com: %v\n", err)
|
|
fmt.Println("Exiting program.")
|
|
return
|
|
}
|
|
|
|
// Execute instructions
|
|
for {
|
|
// Check if program has ended (PC = 0x0000)
|
|
if cpu.PC == 0x0000 {
|
|
fmt.Println("Program ended (PC reached 0x0000)")
|
|
break
|
|
}
|
|
|
|
// Check if this is a BDOS call (PC = 0x0005)
|
|
if cpu.PC == 0x0005 {
|
|
handleBDOSCall(cpu, memory)
|
|
// After handling BDOS call, continue execution
|
|
continue
|
|
}
|
|
|
|
// Execute one instruction
|
|
ticks := cpu.ExecuteOneInstruction()
|
|
_ = ticks // Ignore ticks for now
|
|
|
|
// Optional: Add a safety counter to prevent infinite loops during development
|
|
// You can remove this once everything is working properly
|
|
}
|
|
|
|
fmt.Printf("ZEXALL\n")
|
|
|
|
cpu.SP = 0xFFFF
|
|
|
|
// Program counter starts at 0x100 for .COM files
|
|
cpu.PC = 0x100
|
|
|
|
// Load zexall.com file
|
|
err = loadZEXALL(memory, "zexall.com")
|
|
if err != nil {
|
|
fmt.Printf("Error: Could not load zexall.com: %v\n", err)
|
|
fmt.Println("Exiting program.")
|
|
return
|
|
}
|
|
|
|
// Execute instructions
|
|
for {
|
|
// Check if program has ended (PC = 0x0000)
|
|
if cpu.PC == 0x0000 {
|
|
fmt.Println("Program ended (PC reached 0x0000)")
|
|
break
|
|
}
|
|
|
|
// Check if this is a BDOS call (PC = 0x0005)
|
|
if cpu.PC == 0x0005 {
|
|
handleBDOSCall(cpu, memory)
|
|
// After handling BDOS call, continue execution
|
|
continue
|
|
}
|
|
|
|
// Execute one instruction
|
|
ticks := cpu.ExecuteOneInstruction()
|
|
_ = ticks // Ignore ticks for now
|
|
|
|
// Optional: Add a safety counter to prevent infinite loops during development
|
|
// You can remove this once everything is working properly
|
|
}
|
|
|
|
}
|