package main import ( "log" "os" "time" "github.com/koron-go/z80" ) // evacuationController is the PH00524 board emulated in a synchronous step // manner. type evacuationController struct { cpu *z80.CPU m1 *motor m2 *motor u8 *i8255 u9 *i8255 // lastIOPC is the last PC on which an I/O operation (through U8 or U9) was // performed. This is used for status/debugging. lastIOPC uint16 } func newEvacuationController(romPath string) *evacuationController { rom, err := os.ReadFile(romPath) if err != nil { log.Fatalf("Failed to read ROM: %v", err) } mem := memoryArbiter{ memories: []memory{ {start: 0x0000, data: rom, readonly: true}, {start: 0x8000, data: make([]byte, 512)}, }, } u8 := i8255{} u9 := i8255{} io := ioArbiter{ peripherals: []*ioPeripheral{ {start: 0x40, length: 4, peripheral: &u8}, {start: 0x80, length: 4, peripheral: &u9}, }, } m1 := motor{barrier: &barrier{npos: 4}} m2 := motor{barrier: &barrier{npos: 2}} cpu := z80.CPU{ Memory: &mem, IO: &io, } res := &evacuationController{ cpu: &cpu, m1: &m1, m2: &m2, u8: &u8, u9: &u9, } // Make lastIOPC get updated on u8/u9 I/O. u8.onIn = res.onIO u8.onOut = res.onIO u9.onIn = res.onIO u9.onOut = res.onIO // Expansion bus connector, seems to be always pulled up. u9.pa = 0xff return res } func (e *evacuationController) onIO(_ i8255Port) { e.lastIOPC = e.cpu.PC } func (e *evacuationController) getIndcators() indicators { return indicators{ indEvac: (e.u8.pc & 1) != 0, indHTReadyN: (e.u8.pc & 2) != 0, indPumpDown: (e.u8.pc & 4) != 0, } } func (e *evacuationController) pressPumpdown(value bool) { if value { e.u8.pb |= 1 } else { e.u8.pb &= (0xff ^ 1) } } func (e *evacuationController) pressVent(value bool) { if value { e.u8.pb |= 2 } else { e.u8.pb &= (0xff ^ 2) } } // step through the simulation/emulation given a delta time. func (e *evacuationController) step(dt time.Duration) bool { e.cpu.Step() if e.cpu.HALT { return false } // Simulate motors and barriers. e.m1.dir = (e.u9.pc & 16) != 0 e.m1.en = (e.u9.pc & 4) != 0 e.m2.dir = (e.u9.pc & 8) != 0 e.m2.en = (e.u9.pc & 2) != 0 e.m1.sim(dt) e.m2.sim(dt) // Feed barrier status back into CPU. b1p := e.m1.barrier.pins() b2p := e.m2.barrier.pins() e.u8.pa = 0 if b1p[0] { e.u8.pa |= (1 << 5) } if b1p[1] { e.u8.pa |= (1 << 4) } if b1p[2] { e.u8.pa |= (1 << 3) } if b1p[3] { e.u8.pa |= (1 << 2) } if b2p[0] { e.u8.pa |= (1 << 1) } if b2p[1] { e.u8.pa |= (1 << 0) } // Simulate vacuum gauge / comparators given light barrier status. if b1p[3] { // half succ (PRE_EVAC) e.u8.pb |= 1 << 5 // full succ (???) e.u8.pb |= 1 << 6 } if !b1p[0] { // no more succ e.u8.pb &= 0xff ^ (1 << 5) e.u8.pb &= 0xff ^ (1 << 6) } return true }