ph00524: add emulator
This commit is contained in:
		
							parent
							
								
									7c663f42ca
								
							
						
					
					
						commit
						be255f2e6f
					
				
					 8 changed files with 605 additions and 0 deletions
				
			
		
							
								
								
									
										29
									
								
								modules/ph00524/emu/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								modules/ph00524/emu/README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					PH00524, but emulated
 | 
				
			||||||
 | 
					===
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When troubleshooting the PH00524 evacuation controller board it's useful to be
 | 
				
			||||||
 | 
					able to tell what the microcontroller _should_ be doing, rather than just
 | 
				
			||||||
 | 
					staring at disassembly/decompilation all day long.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This little program executes our scope evacuation controller's code (from the
 | 
				
			||||||
 | 
					U12 EEPROM) in an environment consisting of a Z80 CPU and all peripherals
 | 
				
			||||||
 | 
					required to reach a pumpdown state.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It comes with a little text UI interface:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Controls: [v] vent, [p] pumpdown, [q] quit
 | 
				
			||||||
 | 
					    PC 02bc
 | 
				
			||||||
 | 
					    ---- -------- VENT
 | 
				
			||||||
 | 
					      M1     M2
 | 
				
			||||||
 | 
					    |... ..
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This shows the controls (keyboard buttons v, p and q), the last program counter
 | 
				
			||||||
 | 
					at which an I/O operation was performed, indicator values, motor movement and
 | 
				
			||||||
 | 
					lightbarrier status.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Running
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Change to this directory and run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ go run ./
 | 
				
			||||||
							
								
								
									
										98
									
								
								modules/ph00524/emu/arbiters.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								modules/ph00524/emu/arbiters.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,98 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/koron-go/z80"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ioPeripheral is a z80.IO device at a given address.
 | 
				
			||||||
 | 
					type ioPeripheral struct {
 | 
				
			||||||
 | 
						start      uint8
 | 
				
			||||||
 | 
						length     uint8
 | 
				
			||||||
 | 
						peripheral z80.IO
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ioArbiter is a collection of I/O devices at addresses. It implements z80.IO
 | 
				
			||||||
 | 
					// itself, dispatching to the appropriate device as needed.
 | 
				
			||||||
 | 
					type ioArbiter struct {
 | 
				
			||||||
 | 
						peripherals []*ioPeripheral
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *ioArbiter) get(addr uint8) *ioPeripheral {
 | 
				
			||||||
 | 
						for _, periph := range i.peripherals {
 | 
				
			||||||
 | 
							if addr < periph.start {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if addr > periph.start+periph.length {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return periph
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *ioArbiter) In(addr uint8) uint8 {
 | 
				
			||||||
 | 
						p := i.get(addr)
 | 
				
			||||||
 | 
						if p == nil {
 | 
				
			||||||
 | 
							log.Fatalf("Unhandled I/O In at %02x", addr)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return p.peripheral.In(addr - p.start)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (i *ioArbiter) Out(addr uint8, value uint8) {
 | 
				
			||||||
 | 
						p := i.get(addr)
 | 
				
			||||||
 | 
						if p == nil {
 | 
				
			||||||
 | 
							log.Fatalf("Unhandled I/O Out at %02x", addr)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						p.peripheral.Out(addr-p.start, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// memory is a continuous memory region at a given address, optionally marked
 | 
				
			||||||
 | 
					// read only.
 | 
				
			||||||
 | 
					type memory struct {
 | 
				
			||||||
 | 
						start    uint16
 | 
				
			||||||
 | 
						data     []uint8
 | 
				
			||||||
 | 
						readonly bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// memoryArbiter implements Z80.Memory and dispatches accesses to subordinate
 | 
				
			||||||
 | 
					// memory instances.
 | 
				
			||||||
 | 
					type memoryArbiter struct {
 | 
				
			||||||
 | 
						memories []memory
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *memoryArbiter) get(addr uint16) *memory {
 | 
				
			||||||
 | 
						for _, mem := range m.memories {
 | 
				
			||||||
 | 
							if addr < mem.start {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if addr >= mem.start+uint16(len(mem.data)) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return &mem
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *memoryArbiter) Get(addr uint16) uint8 {
 | 
				
			||||||
 | 
						mem := m.get(addr)
 | 
				
			||||||
 | 
						if mem == nil {
 | 
				
			||||||
 | 
							log.Fatalf("Unhandled memory Get at %04x", addr)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mem.data[addr-mem.start]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *memoryArbiter) Set(addr uint16, value uint8) {
 | 
				
			||||||
 | 
						mem := m.get(addr)
 | 
				
			||||||
 | 
						if mem == nil {
 | 
				
			||||||
 | 
							log.Fatalf("Unhandled memory Set at %04x", addr)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mem.readonly {
 | 
				
			||||||
 | 
							log.Fatalf("Read-only memory Set at %04x", addr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mem.data[addr-mem.start] = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										157
									
								
								modules/ph00524/emu/controller.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								modules/ph00524/emu/controller.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,157 @@
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								modules/ph00524/emu/go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/ph00524/emu/go.mod
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					module git.fa-fo.de/fafo/jeol-t330a/modules/ph00524/emu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.22.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/koron-go/z80 v0.10.1
 | 
				
			||||||
 | 
						golang.org/x/term v0.23.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require golang.org/x/sys v0.23.0 // indirect
 | 
				
			||||||
							
								
								
									
										8
									
								
								modules/ph00524/emu/go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								modules/ph00524/emu/go.sum
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
				
			||||||
 | 
					github.com/koron-go/z80 v0.10.1 h1:Jfb0esP/QFL4cvcr+eFECVG0Y/mA9JBLC4EKbMU5zAY=
 | 
				
			||||||
 | 
					github.com/koron-go/z80 v0.10.1/go.mod h1:ry+Zl9kRKelzaDG9UzEtUpUnXy0Yv/kk1YEaX958xdk=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
				
			||||||
 | 
					golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
 | 
				
			||||||
 | 
					golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
 | 
				
			||||||
							
								
								
									
										102
									
								
								modules/ph00524/emu/i8255.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								modules/ph00524/emu/i8255.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,102 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// i8255 is a generic Intel 8255-compatible I/O controller chip.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Only mode 0 is supported.
 | 
				
			||||||
 | 
					type i8255 struct {
 | 
				
			||||||
 | 
						pa, pb, pc uint8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						onOut func(port i8255Port)
 | 
				
			||||||
 | 
						onIn  func(port i8255Port)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type i8255Port string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						PA i8255Port = "PA"
 | 
				
			||||||
 | 
						PB i8255Port = "PB"
 | 
				
			||||||
 | 
						PC i8255Port = "PC"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *i8255) getPort(p i8255Port) uint8 {
 | 
				
			||||||
 | 
						switch p {
 | 
				
			||||||
 | 
						case PA:
 | 
				
			||||||
 | 
							return i.pa
 | 
				
			||||||
 | 
						case PB:
 | 
				
			||||||
 | 
							return i.pb
 | 
				
			||||||
 | 
						case PC:
 | 
				
			||||||
 | 
							return i.pc
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							log.Fatalf("invalid port")
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *i8255) Out(addr, value uint8) {
 | 
				
			||||||
 | 
						switch addr {
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							i.pa = value
 | 
				
			||||||
 | 
							if i.onOut != nil {
 | 
				
			||||||
 | 
								i.onOut(PA)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							i.pb = value
 | 
				
			||||||
 | 
							if i.onOut != nil {
 | 
				
			||||||
 | 
								i.onOut(PB)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							i.pc = value
 | 
				
			||||||
 | 
							if i.onOut != nil {
 | 
				
			||||||
 | 
								i.onOut(PC)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case 3:
 | 
				
			||||||
 | 
							if (value >> 7) == 0 {
 | 
				
			||||||
 | 
								// BSR mode
 | 
				
			||||||
 | 
								bitno := (value >> 1) & 0b111
 | 
				
			||||||
 | 
								bitval := value & 1
 | 
				
			||||||
 | 
								if bitval == 1 {
 | 
				
			||||||
 | 
									i.pc |= (1 << bitno)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									i.pc &= 0xff ^ (1 << bitno)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Input/Output mode
 | 
				
			||||||
 | 
								gaMode := (value >> 5) & 0b11
 | 
				
			||||||
 | 
								gbMode := (value >> 5) & 0b11
 | 
				
			||||||
 | 
								if gaMode != 0 {
 | 
				
			||||||
 | 
									log.Fatalf("I8255 implementation only supports mode 0 on Port A")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if gbMode != 0 {
 | 
				
			||||||
 | 
									log.Fatalf("I8255 implementation only supports mode 0 on Port B")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Don't care about the rest (input/output settings per group/port).
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							log.Printf("I8255: invalid out")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *i8255) In(addr uint8) uint8 {
 | 
				
			||||||
 | 
						switch addr {
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							if i.onIn != nil {
 | 
				
			||||||
 | 
								i.onIn(PA)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return i.pa
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							if i.onIn != nil {
 | 
				
			||||||
 | 
								i.onIn(PB)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return i.pb
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							if i.onIn != nil {
 | 
				
			||||||
 | 
								i.onIn(PC)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return i.pc
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							log.Printf("I8255: invalid in")
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										83
									
								
								modules/ph00524/emu/main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								modules/ph00524/emu/main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"golang.org/x/term"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						ctrl := newEvacuationController("../eeprom/Vac-ours FK-U12.bin")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Switch stdin into 'raw' mode.
 | 
				
			||||||
 | 
						oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer term.Restore(int(os.Stdin.Fd()), oldState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Keyboard input channel, byte at a time.
 | 
				
			||||||
 | 
						kbdC := make(chan byte)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								b := make([]byte, 1)
 | 
				
			||||||
 | 
								_, err = os.Stdin.Read(b)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									kbdC <- b[0]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Countdown timers for simulating a button press.
 | 
				
			||||||
 | 
						pumpdownButton := time.Duration(0)
 | 
				
			||||||
 | 
						ventButton := time.Duration(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastPrint := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Print("\033[H\033[2J")
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							dt := time.Since(t)
 | 
				
			||||||
 | 
							t = time.Now()
 | 
				
			||||||
 | 
							if !ctrl.step(dt) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case b := <-kbdC:
 | 
				
			||||||
 | 
								switch b {
 | 
				
			||||||
 | 
								case 'p':
 | 
				
			||||||
 | 
									pumpdownButton = time.Millisecond * 1000
 | 
				
			||||||
 | 
								case 'v':
 | 
				
			||||||
 | 
									ventButton = time.Millisecond * 1000
 | 
				
			||||||
 | 
								case 'q':
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ctrl.pressPumpdown(pumpdownButton > 0)
 | 
				
			||||||
 | 
							if pumpdownButton > 0 {
 | 
				
			||||||
 | 
								pumpdownButton -= dt
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctrl.pressVent(ventButton > 0)
 | 
				
			||||||
 | 
							if ventButton > 0 {
 | 
				
			||||||
 | 
								ventButton -= dt
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if time.Since(lastPrint) > time.Millisecond*10 {
 | 
				
			||||||
 | 
								lastPrint = time.Now()
 | 
				
			||||||
 | 
								fmt.Print("\033[1;1H")
 | 
				
			||||||
 | 
								fmt.Printf("Controls: [v] vent, [p] pumpdown, [q] quit\r\n")
 | 
				
			||||||
 | 
								fmt.Printf("PC %04x\r\n", ctrl.lastIOPC)
 | 
				
			||||||
 | 
								fmt.Printf("%s\r\n", ctrl.getIndcators().String())
 | 
				
			||||||
 | 
								fmt.Printf("%s %s\r\n", ctrl.m1.status("M1"), ctrl.m2.status("M2"))
 | 
				
			||||||
 | 
								fmt.Printf("%s %s\r\n", ctrl.m1.barrier.status(), ctrl.m2.barrier.status())
 | 
				
			||||||
 | 
								fmt.Print("\r\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										118
									
								
								modules/ph00524/emu/scope.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								modules/ph00524/emu/scope.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// barrier represents a multi-position light barrier attached to a motor.
 | 
				
			||||||
 | 
					type barrier struct {
 | 
				
			||||||
 | 
						// npos is the number of the positions of the light barrier.
 | 
				
			||||||
 | 
						npos uint
 | 
				
			||||||
 | 
						// cur is the current position of the motor.
 | 
				
			||||||
 | 
						cur float64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// pins returns an npos-length array representing the interruption state of the
 | 
				
			||||||
 | 
					// barrier given cur. An interrupted light barrier is represented by a true
 | 
				
			||||||
 | 
					// value, an uninterrupted one with a false value.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Every light barrier position is half a unit away from eachother, with the
 | 
				
			||||||
 | 
					// first light barrier at unit 1.
 | 
				
			||||||
 | 
					func (b *barrier) pins() []bool {
 | 
				
			||||||
 | 
						res := make([]bool, b.npos)
 | 
				
			||||||
 | 
						for i := uint(0); i < b.npos; i++ {
 | 
				
			||||||
 | 
							pos := float64(i) * float64(1.0)
 | 
				
			||||||
 | 
							pos += 1.0
 | 
				
			||||||
 | 
							if b.cur*2.0 >= pos {
 | 
				
			||||||
 | 
								res[i] = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// status returns a human-readable status of the barrier like '||..' for a
 | 
				
			||||||
 | 
					// 4-position light barrier with the first two barrier interrupted.
 | 
				
			||||||
 | 
					func (b *barrier) status() string {
 | 
				
			||||||
 | 
						res := ""
 | 
				
			||||||
 | 
						for _, v := range b.pins() {
 | 
				
			||||||
 | 
							if v {
 | 
				
			||||||
 | 
								res += "|"
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res += "."
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// motor represents an electrical motor which drives a linear actuator at 1 unit per second.
 | 
				
			||||||
 | 
					type motor struct {
 | 
				
			||||||
 | 
						// en is true if the motor is enabled.
 | 
				
			||||||
 | 
						en bool
 | 
				
			||||||
 | 
						// dir is used to select the direction of the motor. If true, the motor
 | 
				
			||||||
 | 
						// actuator moves towards positive values.
 | 
				
			||||||
 | 
						dir bool
 | 
				
			||||||
 | 
						// pos is the current position of the motor actuator.
 | 
				
			||||||
 | 
						pos float64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// barrier attached to this motor, will be automatically updated when the
 | 
				
			||||||
 | 
						// motor simulation runs.
 | 
				
			||||||
 | 
						barrier *barrier
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// status returns a human-readable staus of the motor like '<-ID  ' indicating
 | 
				
			||||||
 | 
					// whether the motor is moving, and if so, in which direction.
 | 
				
			||||||
 | 
					func (s *motor) status(id string) string {
 | 
				
			||||||
 | 
						res := ""
 | 
				
			||||||
 | 
						if s.en {
 | 
				
			||||||
 | 
							if s.dir {
 | 
				
			||||||
 | 
								res += fmt.Sprintf("  %s->", id)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res += fmt.Sprintf("<-%s  ", id)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res += fmt.Sprintf("  %s  ", id)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// sim runs a simulation step of the motor given a delta time.
 | 
				
			||||||
 | 
					func (s *motor) sim(dt time.Duration) {
 | 
				
			||||||
 | 
						if !s.en {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						d := dt.Seconds()
 | 
				
			||||||
 | 
						if s.dir {
 | 
				
			||||||
 | 
							s.pos += d
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							s.pos -= d
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.barrier.cur = s.pos
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type indicators struct {
 | 
				
			||||||
 | 
						indEvac     bool
 | 
				
			||||||
 | 
						indHTReadyN bool
 | 
				
			||||||
 | 
						indPumpDown bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i indicators) String() string {
 | 
				
			||||||
 | 
						res := ""
 | 
				
			||||||
 | 
						if i.indEvac {
 | 
				
			||||||
 | 
							res += "EVAC "
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res += "---- "
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !i.indHTReadyN {
 | 
				
			||||||
 | 
							res += "HT READY "
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res += "-------- "
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if i.indPumpDown {
 | 
				
			||||||
 | 
							res += "PUMP DOWN "
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res += "VENT      "
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue