izapple2/cardDisk2Sequencer.go
2024-03-24 20:15:16 +01:00

318 lines
8.0 KiB
Go

package izapple2
import (
"github.com/ivanizag/izapple2/component"
)
// TODO: fast mode
/*
See:
"Understanding the Apple II, chapter 9"
Beneath Apple ProDOS, Appendix
Phases: http://yesterbits.com/media/pubs/AppleOrchard/articles/disk-ii-part-1-1983-apr.pdf
IMW Floppy Disk I/O Controller info: (https://www.brutaldeluxe.fr/documentation/iwm/apple2_IWM_INFO_19840510.pdf)
Woz https://applesaucefdc.com/woz/reference2/
Schematic: https://mirrors.apple2.org.za/ftp.apple.asimov.net/documentation/hardware/schematics/APPLE_DiskII_SCH.pdf
*/
// CardDisk2Sequencer is a DiskII interface card with the Woz state machine
type CardDisk2Sequencer struct {
cardBase
p6ROM []uint8
q [8]bool // 8-bit latch SN74LS259
register uint8 // 8-bit shift/storage register SN74LS323
sequence uint8 // 4 bits stored in an hex flip-flop SN74LS174
motorDelay uint64 // NE556 timer, used to delay motor off
drive [2]cardDisk2SequencerDrive
lastWriteValue bool // We write transitions to the WOZ file. We store the last value to send a pulse on change.
lastPulseCycles uint8 // There is a new pulse every 4ms, that's 8 cycles of 2Mhz
lastCycle uint64 // 2 Mhz cycles
trackTracer trackTracer
}
// Shared methods between both versions on the Disk II card
type cardDisk2Shared interface {
//insertDiskette(drive int, ...)
setTrackTracer(tt trackTracer)
}
const (
disk2MotorOffDelay = uint64(2 * 1000 * 1000) // 2 Mhz cycles. Total 1 second.
disk2PulseCyles = uint8(8) // 8 cycles = 4ms * 2Mhz
/*
We skip register calculations for long periods with the motor
on but not reading bytes. It's an optimizations, 10000 is too
short for cross track sync copy protections.
*/
disk2CyclestoLoseSsync = 100000
)
func newCardDisk2SequencerBuilder() *cardBuilder {
return &cardBuilder{
name: "Disk II Sequencer",
description: "Disk II interface card emulating the Woz state machine",
defaultParams: &[]paramSpec{
{"disk1", "Diskette image for drive 1", ""},
{"disk2", "Diskette image for drive 2", ""},
{"tracktracer", "Trace how the disk head moves between tracks", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2Sequencer
err := c.loadRomFromResource("<internal>/DISK2.rom", cardRomSimple)
if err != nil {
return nil, err
}
data, _, err := LoadResource("<internal>/DISK2P6.rom")
if err != nil {
return nil, err
}
c.p6ROM = data
disk1 := paramsGetString(params, "disk1")
if disk1 != "" {
err := c.drive[0].insertDiskette(disk1)
if err != nil {
return nil, err
}
}
disk2 := paramsGetString(params, "disk2")
if disk2 != "" {
err := c.drive[1].insertDiskette(disk2)
if err != nil {
return nil, err
}
}
trackTracer := paramsGetBool(params, "tracktracer")
if trackTracer {
c.trackTracer = makeTrackTracerLogger()
}
return &c, nil
},
}
}
// GetInfo returns card info
func (c *CardDisk2Sequencer) GetInfo() map[string]string {
info := make(map[string]string)
info["rom"] = "16 sector"
// TODO: add drives info
return info
}
func (c *CardDisk2Sequencer) reset() {
// UtA2e 9-12, all switches forced to off
c.q = [8]bool{}
}
func (c *CardDisk2Sequencer) setTrackTracer(tt trackTracer) {
c.trackTracer = tt
}
func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1])
c.addCardSoftSwitches(func(address uint8, data uint8, _ bool) uint8 {
/*
Slot card pins to SN74LS259 latch mapping:
slot_address[3,2,1] => latch_address[2,1,0]
slot_address[0] => latch_data
slot_dev_selct => latch_write_enable ;It will be true
*/
c.q[address>>1] = (address & 1) != 0
// Advance the Disk2 state machine since the last call to softswitches
c.catchUp(data)
/*
Slot card pins to SN74LS259 mapping:
slot_address[0] => latch_oe2_n
*/
registerOutputEnableNeg := (address & 1) != 0
if !registerOutputEnableNeg {
return c.register
} else {
return 33 // Floating
}
}, "DISK2SEQ")
c.cardBase.assign(a, slot)
}
func (c *CardDisk2Sequencer) catchUp(data uint8) {
currentCycle := c.a.cpu.GetCycles() << 1 // Disk2 cycles are x2 cpu cycle
motorOn := c.step(data, true)
if motorOn && currentCycle > c.lastCycle+disk2CyclestoLoseSsync {
// We have lost sync. We start the count.
// We do at least a couple 2 Mhz cycles
c.lastCycle = currentCycle - 2
}
c.lastCycle++
for motorOn && c.lastCycle <= currentCycle {
motorOn = c.step(data, false)
c.lastCycle++
}
if !motorOn {
c.lastCycle = 0 // Sync lost
}
}
func (c *CardDisk2Sequencer) step(data uint8, firstStep bool) bool {
/*
Q4 and Q6 set on the sofswitches is stored on the
latch.
*/
q5 := c.q[5] // Drive selection
q4 := c.q[4] // Motor on (before delay)
/*
Motor On comes from the latched q4 via the 556 to
provide a delay. The delay is reset while q4 is on.
*/
if q4 {
c.motorDelay = disk2MotorOffDelay
}
motorOn := c.motorDelay > 0
/*
The pins for the cable drives ENBL1 and ENBL2 are
connected to q5 and motor using half of the 74LS132
NAND to combine them.
*/
c.drive[0].enable(!q5 && motorOn)
c.drive[1].enable(q5 && motorOn)
/*
Motor on AND the 2 Mhz clock (Q3 pin 37 of the slot)
are connected to the clok pulse of the shift register
if off, the sequences does not advance. The and uses
another quarter of the 74LS132 NAND.
*/
if !motorOn {
c.sequence = 0
return false
}
c.motorDelay--
/*
Head movements. We assume it's instantaneous on Q0-Q3 change. We
will place it on the first step.
Q0 to Q3 are connected directly to the drives.
*/
if firstStep {
q0 := c.q[0]
q1 := c.q[1]
q2 := c.q[2]
q3 := c.q[3]
c.drive[0].moveHead(q0, q1, q2, q3, c.trackTracer, c.slot, 0)
c.drive[1].moveHead(q0, q1, q2, q3, c.trackTracer, c.slot, 1)
}
/*
The reading from the drive is converted to a pulse detecting
changes using Q3 and Q4 of the flip flop, combined with
the last quarter of the 74LS132 NAND.∫
The woz format provides the pulse directly and we won't emulate
this detection.
*/
pulse := false
c.lastPulseCycles++
if c.lastPulseCycles == disk2PulseCyles {
// Read
pulse = c.drive[0].readPulse() ||
c.drive[1].readPulse()
c.lastPulseCycles = 0
}
/*
The write protected signal comes directly from any of the
drives being enabled (motor on) and write protected.
*/
wProt := (c.drive[0].enabled && c.drive[0].writeProtected) ||
(c.drive[1].enabled && c.drive[1].writeProtected)
/*
The next instruction for the sequencer is retrieved from
the ROM P6 using the address:
A0, A5, A6, A7 <= sequence from 74LS174
A1 =< high, MSB of register (pin Q7)
A2 <= Q6 from 9334
A3 <= Q7 from 9334
A4 <= pulse transition
*/
high := c.register >= 0x80
seqBits := component.ByteToPins(c.sequence)
romAddress := component.PinsToByte([8]bool{
seqBits[1], // seq1
high,
c.q[6],
c.q[7],
!pulse,
seqBits[0], // seq0
seqBits[2], // seq2
seqBits[3], // seq3
})
romData := c.p6ROM[romAddress]
inst := romData & 0xf
next := romData >> 4
/*
The pins for the register shifter update are:
SR(CLR) <- ROM D3
S1 <- ROM D0
S0 <- ROM D1
DS0(SR) <- WPROT pin of the selected drive
DS7(SL) <- ROM D2
IO[7.0] <-> D[0-7] slot data bus (the order is reversed)
*/
if inst < 8 {
c.register = 0 // Bit 4 clear to reset
} else {
switch inst & 0x3 { // Bit 0 and 1 are the operation
case 0:
// Nothing
case 1:
// Shift left bringing bit 1
c.register = (c.register << 1) | ((inst >> 2) & 1)
case 2:
// Shift right bringing wProt
c.register = c.register >> 1
if wProt {
c.register |= 0x80
}
case 3:
// Load
c.register = data
}
if c.q[7] && (inst&0x3) != 0 {
currentWriteValue := next >= 0x8
writePulse := currentWriteValue != c.lastWriteValue
c.drive[0].writePulse(writePulse)
c.drive[1].writePulse(writePulse)
c.lastWriteValue = currentWriteValue
}
}
//fmt.Printf("[D2SEQ] Step. seq:%x inst:%x next:%x reg:%02x\n",
// c.sequence, inst, next, c.register)
c.sequence = next
return true
}