158 lines
2.8 KiB
Go
158 lines
2.8 KiB
Go
|
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
|
||
|
}
|