Disk II state machine

This commit is contained in:
Ivan Izaguirre 2021-05-09 19:48:54 +02:00
parent 902437419e
commit 0daf1b9fd9
25 changed files with 1483 additions and 142 deletions

View File

@ -116,6 +116,28 @@ func (a *Apple2) AddDisk2(slot int, diskImage, diskBImage string) error {
return nil
}
// AddDisk2 inserts a DiskII controller
func (a *Apple2) AddDisk2Sequencer(slot int, diskImage, diskBImage string) error {
c := NewCardDisk2Sequencer()
a.insertCard(c, slot)
if diskImage != "" {
err := c.drive[0].insertDiskette(diskImage)
if err != nil {
return err
}
}
if diskBImage != "" {
err := c.drive[0].insertDiskette(diskBImage)
if err != nil {
return err
}
}
return nil
}
// AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, trace bool) error {
c := NewCardHardDisk()

View File

@ -138,6 +138,10 @@ func MainApple() *Apple2 {
"forceCaps",
false,
"force all letters to be uppercased (no need for caps lock!)")
expermintalWoz := flag.Bool(
"xw",
false,
"use the sequencer based Disk II card")
flag.Parse()
@ -284,9 +288,17 @@ func MainApple() *Apple2 {
a.AddFastChip(*fastChipCardSlot)
}
if *disk2Slot > 0 {
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage)
if err != nil {
panic(err)
if *expermintalWoz {
err := a.AddDisk2Sequencer(*disk2Slot, diskImageFinal, *diskBImage)
if err != nil {
panic(err)
}
} else {
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage)
if err != nil {
panic(err)
}
}
}
if hardDiskImageFinal != "" {

View File

@ -1,6 +1,8 @@
package izapple2
import (
"fmt"
"github.com/ivanizag/izapple2/storage"
)
@ -113,3 +115,18 @@ func (c *cardBase) addCardSoftSwitchW(address uint8, ss softSwitchW, name string
c._ssw[address] = ss
c._sswName[address] = name
}
type softSwitches func(io *ioC0Page, address uint8, data uint8, write bool) uint8
func (c *cardBase) addCardSoftSwitches(sss softSwitches, name string) {
for i := uint8(0x0); i <= 0xf; i++ {
address := i
c.addCardSoftSwitchR(address, func(io *ioC0Page) uint8 {
return sss(io, address, 0, false)
}, fmt.Sprintf("%v%xR", name, address))
c.addCardSoftSwitchW(address, func(io *ioC0Page, value uint8) {
sss(io, address, value, true)
}, fmt.Sprintf("%v%xW", name, address))
}
}

View File

@ -91,7 +91,7 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
// Update magnets and position
drive := &c.drive[c.selected]
drive.phases &^= (1 << phase)
drive.tracksStep = moveStep(drive.phases, drive.tracksStep)
drive.tracksStep = moveDriveStepper(drive.phases, drive.tracksStep)
return c.dataLatch // All even addresses return the last dataLatch
}, fmt.Sprintf("PHASE%vOFF", phase))
@ -100,7 +100,7 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
// Update magnets and position
drive := &c.drive[c.selected]
drive.phases |= (1 << phase)
drive.tracksStep = moveStep(drive.phases, drive.tracksStep)
drive.tracksStep = moveDriveStepper(drive.phases, drive.tracksStep)
return 0
}, fmt.Sprintf("PHASE%vON", phase))
@ -222,87 +222,6 @@ func (c *CardDisk2) processQ6Q7(in uint8) {
}
}
/*
Stepper motor to position the track.
There are a number of group of four magnets. The stepper motor can be thought as a long
line of groups of magnets, each group on the same configuration. We call phase each of those
magnets. The cog is attracted to the enabled magnets, and can stay aligned to a magnet or
between two.
Phases (magnets): 3 2 1 0 3 2 1 0 3 2 1 0
Cog direction (step within a group): 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
We will consider that the cog would go to the prefferred position if there is one. Independently
of the previous position. The previous position is only used to know if it goes up or down
a full group.
*/
const (
undefinedPosition = -1
maxStep = 68 * 2 // What is the maximum quarter tracks a DiskII can go?
stepsPerGroup = 8
stepsPerTrack = 4
)
var cogPositions = []int{
undefinedPosition, // 0000, phases active
0, // 0001
2, // 0010
1, // 0011
4, // 0100
undefinedPosition, // 0101
3, // 0110
2, // 0111
6, // 1000
7, // 1001
undefinedPosition, // 1010
0, // 1011
5, // 1100
6, // 1101
4, // 1110
undefinedPosition, // 1111
}
func moveStep(phases uint8, prevStep int) int {
//fmt.Printf("magnets: 0x%x\n", phases)
cogPosition := cogPositions[phases]
if cogPosition == undefinedPosition {
// Don't move if magnets don't push on a defined direction.
return prevStep
}
prevPosition := prevStep % stepsPerGroup // Direction, step in the current group of magnets.
delta := cogPosition - prevPosition
if delta < 0 {
delta = delta + stepsPerGroup
}
var nextStep int
if delta < 4 {
// Steps up
nextStep = prevStep + delta
if nextStep > maxStep {
nextStep = maxStep
}
} else if delta == 4 {
// Don't move if magnets push on the opposite direction
nextStep = prevStep
} else { // delta > 4
// Steps down
nextStep = prevStep + delta - stepsPerGroup
if nextStep < 0 {
nextStep = 0
}
}
//fmt.Printf("[DiskII] 1/4 track: %03d %vO\n", nextStep, strings.Repeat(" ", nextStep))
return nextStep
}
func (d *cardDisk2Drive) insertDiskette(name string, dt storage.Diskette) {
d.name = name
d.diskette = dt

88
cardDisk2DriveStepper.go Normal file
View File

@ -0,0 +1,88 @@
package izapple2
import (
"fmt"
"strings"
)
/*
Stepper motor to position the track.
There are a number of group of four magnets. The stepper motor can be thought as a long
line of groups of magnets, each group on the same configuration. We call phase each of those
magnets. The cog is attracted to the enabled magnets, and can stay aligned to a magnet or
between two.
Phases (magnets): 3 2 1 0 3 2 1 0 3 2 1 0
Cog direction (step within a group): 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
We will consider that the cog would go to the prefferred position if there is one. Independently
of the previous position. The previous position is only used to know if it goes up or down
a full group.
Phases are coded in 4 bits in an uint8. Q3, q2, q1 and q0 with q0 on the LSB.
*/
const (
undefinedPosition = -1
maxStep = 68 * 2 // What is the maximum quarter tracks a DiskII can go?
stepsPerGroup = 8
stepsPerTrack = 4
)
var cogPositions = []int{
undefinedPosition, // 0000, phases active
0, // 0001
2, // 0010
1, // 0011
4, // 0100
undefinedPosition, // 0101
3, // 0110
2, // 0111
6, // 1000
7, // 1001
undefinedPosition, // 1010
0, // 1011
5, // 1100
6, // 1101
4, // 1110
undefinedPosition, // 1111
}
func moveDriveStepper(phases uint8, prevStep int) int {
//fmt.Printf("magnets: 0x%x\n", phases)
cogPosition := cogPositions[phases]
if cogPosition == undefinedPosition {
// Don't move if magnets don't push on a defined direction.
return prevStep
}
prevPosition := prevStep % stepsPerGroup // Direction, step in the current group of magnets.
delta := cogPosition - prevPosition
if delta < 0 {
delta = delta + stepsPerGroup
}
var nextStep int
if delta < 4 {
// Steps up
nextStep = prevStep + delta
if nextStep > maxStep {
nextStep = maxStep
}
} else if delta == 4 {
// Don't move if magnets push on the opposite direction
nextStep = prevStep
} else { // delta > 4
// Steps down
nextStep = prevStep + delta - stepsPerGroup
if nextStep < 0 {
nextStep = 0
}
}
fmt.Printf("[DiskII] 1/4 track: %03d %vO\n", nextStep, strings.Repeat(" ", nextStep))
return nextStep
}

305
cardDisk2Sequencer.bak Normal file
View File

@ -0,0 +1,305 @@
package izapple2
import (
"fmt"
"github.com/ivanizag/izapple2/component"
"github.com/ivanizag/izapple2/storage"
)
// 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
type CardDisk2Sequencer struct {
cardBase
p6ROM []uint8
latch component.SN74LS259 // 8-bit latch
register component.SN74LS323 // 8-bit shift/storage register
flipFlop component.SN74LS174 // hex flip-flop
motorDelay uint8 // NE556 timer, used to delay motor off
drive [2]cardDisk2SequencerDrive
lastPulse bool
lastPulseCycles uint8 // There is a new pulse every 4ms, that's 8 cycles of 2Mhz
lastCycle uint64 // 2 Mhz cycles
}
const disk2MotorOffDelay = uint8(20) // 2 Mhz cycles, TODO: how long?
const disk2PulseCcyles = uint8(8) // 8 cycles = 4ms * 2Mhz
// NewCardDisk2Sequencer creates a new CardDisk2Sequencer
func NewCardDisk2Sequencer() *CardDisk2Sequencer {
var c CardDisk2Sequencer
c.name = "Disk II"
c.loadRomFromResource("<internal>/DISK2.rom")
data, _, err := storage.LoadResource("<internal>/DISK2P6raw.rom")
if err != nil {
// The resource should be internal and never fail
panic(err)
}
c.p6ROM = data
return &c
}
func (c *CardDisk2Sequencer) dumpState() {
fmt.Printf("Q5 %v, Q4 %v, delay %v\n", c.latch.Q(5), c.latch.Q(4), c.motorDelay)
}
// 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.latch.Reset()
}
func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
c.addCardSoftSwitches(func(_ *ioC0Page, address uint8, data uint8, write bool) uint8 {
/*
Slot card pins to SN74LS259 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.latch.Write(address>>1, (address&1) != 0, true)
// Advance the Disk2 state machine since the last call to softswitches
c.catchUp(data)
//c.dumpState()
/*
Slot card pins to SN74LS259 mapping:
slot_address[0] => latch_oe2_n
*/
register_output_enable_neg := (address & 1) != 0
if !register_output_enable_neg {
reg := c.register.Output()
return component.ReversePins(reg)
} else {
return 33
}
}, "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 && c.lastCycle == 0 {
// The motor was off, now on. 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 // No tracking done
}
}
func (c *CardDisk2Sequencer) step(data uint8, firstStep bool) bool {
/*
Q4 and Q6 set on the sofswitches is stored on the
latch.
*/
q5 := c.latch.Q(5) // Drive selection
q4 := c.latch.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.flipFlop.Reset()
return false
}
c.motorDelay--
/*
The register is not connected to the data bus directly.
The pin correspondence is:
/*
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.latch.Q(0)
q1 := c.latch.Q(1)
q2 := c.latch.Q(3)
q3 := c.latch.Q(3)
c.drive[0].moveHead(q0, q1, q2, q3)
c.drive[1].moveHead(q0, q1, q2, q3)
}
/*
The reading from the drive is converted to a pulse detecting
changes using Q3 and Q4 of the flip flop, combines with
the last quarter of the 74LS132 NAND.
The woz format provides the pulse directly and we wont emulate
this detection.
*/
pulse := false
c.lastPulseCycles++
if c.lastPulseCycles == disk2PulseCcyles {
pulse = c.drive[0].readPulse() ||
c.drive[1].readPulse()
c.lastPulseCycles = 0
}
/* The write protected signal comes directly from the 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 =< MSB of register (pin Q7)
A2 <= Q6 from 9334
A3 <= Q7 from 9334
A4 <= pulse transition
*/
romAddress := component.PinsToByte([8]bool{
c.flipFlop.Q(5), // seq1
c.register.Q7(),
c.latch.Q(6),
c.latch.Q(7),
pulse,
c.flipFlop.Q(2), // seq0
c.flipFlop.Q(1), // seq2
c.flipFlop.Q(0), //seq3
})
sequence := component.PinsToByte([8]bool{
c.flipFlop.Q(0),
c.flipFlop.Q(1),
c.flipFlop.Q(5),
c.flipFlop.Q(2),
})
fmt.Printf("For Q6(%v) Q7(%v) H(%v) P(%v) Seq(%x) => ",
c.latch.Q(6), c.latch.Q(6), c.register.Q7(), pulse, sequence,
)
romDataByte := c.p6ROM[romAddress]
romData := component.ByteToPins(romDataByte)
inst := component.PinsToByte([8]bool{
romData[0],
romData[1],
romData[2],
romData[3],
})
next := component.PinsToByte([8]bool{
romData[7],
romData[6],
romData[5],
romData[4],
})
fmt.Printf("cmd(%x) seq(%x)\n", inst, next)
/*
The next sequence is feed back to the flip-flops.
Notes: Q3 and Q4 are not used as we are not emulating
the pulse detection. MotorOn here is always true, the
code for motorOn false is above.
*/
c.flipFlop.Update(
romData[7],
romData[6],
romData[4],
false,
false,
romData[5],
motorOn,
)
/*
sequenceNew := component.PinsToByte([8]bool{
c.flipFlop.Q(2),
c.flipFlop.Q(5),
c.flipFlop.Q(1),
c.flipFlop.Q(0),
})
*/
/*
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)
*/
c.register.Update(
component.ReversePins(data),
romData[1], // S0
romData[0], // S1
romData[3], // SR_N
wProt, // DS0
romData[2], // DS1
)
/*
fmt.Printf("Seq %x. pulse %v, P6ROM[%02x]=%02x -> %x-%x, reg %02x\n",
sequence, pulse, romAddress, romDataByte,
sequenceNew, command,
component.ReversePins(c.register.Output()))
*/
return true
}

267
cardDisk2Sequencer.go Normal file
View File

@ -0,0 +1,267 @@
package izapple2
import (
"fmt"
"github.com/ivanizag/izapple2/component"
"github.com/ivanizag/izapple2/storage"
)
// 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
type CardDisk2Sequencer struct {
cardBase
p6ROM []uint8
latch component.SN74LS259 // 8-bit latch
register uint8 // 8-bit shift/storage register SN74LS323
sequence uint8 // 4 bits stored in an hex flip-flop SN74LS174
motorDelay uint8 // NE556 timer, used to delay motor off
drive [2]cardDisk2SequencerDrive
lastPulse bool
lastPulseCycles uint8 // There is a new pulse every 4ms, that's 8 cycles of 2Mhz
lastCycle uint64 // 2 Mhz cycles
}
const disk2MotorOffDelay = uint8(20) // 2 Mhz cycles, TODO: how long?
const disk2PulseCcyles = uint8(8) // 8 cycles = 4ms * 2Mhz
// NewCardDisk2Sequencer creates a new CardDisk2Sequencer
func NewCardDisk2Sequencer() *CardDisk2Sequencer {
var c CardDisk2Sequencer
c.name = "Disk II"
c.loadRomFromResource("<internal>/DISK2.rom")
data, _, err := storage.LoadResource("<internal>/DISK2P6.rom")
if err != nil {
// The resource should be internal and never fail
panic(err)
}
c.p6ROM = data
return &c
}
func (c *CardDisk2Sequencer) dumpState() {
fmt.Printf("Q5 %v, Q4 %v, delay %v\n", c.latch.Q(5), c.latch.Q(4), c.motorDelay)
}
// 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.latch.Reset()
}
func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
c.addCardSoftSwitches(func(_ *ioC0Page, address uint8, data uint8, write bool) uint8 {
/*
Slot card pins to SN74LS259 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.latch.Write(address>>1, (address&1) != 0, true)
// Advance the Disk2 state machine since the last call to softswitches
c.catchUp(data)
//c.dumpState()
/*
Slot card pins to SN74LS259 mapping:
slot_address[0] => latch_oe2_n
*/
register_output_enable_neg := (address & 1) != 0
if !register_output_enable_neg {
//if c.register >= 0x80 && address == 0xc {
// fmt.Printf("Byte %x\n", c.register)
//}
return c.register
} else {
return 33
}
}, "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 && c.lastCycle == 0 {
if motorOn && currentCycle > c.lastCycle+100000 { // With 10000, cross track snc not working
// The motor was off, now on. 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 // No tracking done
}
}
func (c *CardDisk2Sequencer) step(data uint8, firstStep bool) bool {
/*
Q4 and Q6 set on the sofswitches is stored on the
latch.
*/
q5 := c.latch.Q(5) // Drive selection
q4 := c.latch.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.latch.Q(0)
q1 := c.latch.Q(1)
q2 := c.latch.Q(2)
q3 := c.latch.Q(3)
c.drive[0].moveHead(q0, q1, q2, q3)
c.drive[1].moveHead(q0, q1, q2, q3)
}
/*
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 == disk2PulseCcyles {
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
*/
seqBits := component.ByteToPins(c.sequence)
high := c.register >= 0x80
romAddress := component.PinsToByte([8]bool{
seqBits[1], // seq1
high,
c.latch.Q(6),
c.latch.Q(7),
!pulse,
seqBits[0], // seq0
seqBits[2], // seq2
seqBits[3], //seq3
})
//fmt.Printf("For Q6(%v) Q7(%v) H(%v) P(%v) Seq(%x) => ",
// c.latch.Q(6), c.latch.Q(6), high, pulse, c.sequence)
romData := c.p6ROM[romAddress]
inst := romData & 0xf
next := romData >> 4
//fmt.Printf("cmd(%x) seq(%x) ", inst, next)
/*
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
}
}
c.sequence = next
//fmt.Printf("reg %02x\n", c.register)
return true
}

View File

@ -0,0 +1,89 @@
package izapple2
import (
"errors"
"math/rand"
"github.com/ivanizag/izapple2/component"
"github.com/ivanizag/izapple2/storage"
)
type cardDisk2SequencerDrive struct {
data *storage.FileWoz
enabled bool
writeProtected bool
currentQuarterTrack int
position uint32 // Current position on the track
positionMax uint32 // As tracks may have different lengths position is related of positionMax of the las track
mc3470Buffer uint8 // Four bit buffer to detect weak bits and to add latency
}
func (d *cardDisk2SequencerDrive) insertDiskette(filename string) error {
data, writeable, err := storage.LoadResource(filename)
if err != nil {
return err
}
f, err := storage.NewFileWoz(data)
if err != nil {
return err
}
// Discard not supported features
if f.Info.DiskType != 1 {
return errors.New("Only 5.25 disks are supported")
}
if f.Info.BootSectorFormat == 2 { // Info not available in WOZ 1.0
return errors.New("Woz 13 sector disks are not supported")
}
d.data = f
d.writeProtected = !writeable
d.mc3470Buffer = 0xf // Test with the buffer full REMOVE
return nil
}
func (d *cardDisk2SequencerDrive) enable(enabled bool) {
d.enabled = enabled
}
func (d *cardDisk2SequencerDrive) moveHead(q0, q1, q2, q3 bool) {
if !d.enabled {
return
}
phases := component.PinsToByte([8]bool{
q0, q1, q2, q3,
false, false, false, false,
})
d.currentQuarterTrack = moveDriveStepper(phases, d.currentQuarterTrack)
}
func (d *cardDisk2SequencerDrive) readPulse() bool {
if !d.enabled || d.data == nil {
return false
}
// Get next bit taking into account the MC3470 latency and weak bits
var fluxBit uint8
fluxBit, d.position, d.positionMax = d.data.GetNextBitAndPosition(
d.position,
d.positionMax,
d.currentQuarterTrack)
d.mc3470Buffer = (d.mc3470Buffer<<1 + fluxBit) & 0x0f
bit := ((d.mc3470Buffer >> 1) & 0x1) != 0 // Use the previous to last bit to add latency
if d.mc3470Buffer == 0 && rand.Intn(100) < 3 {
// Four consecutive zeros. It'a a fake bit.
// Output a random value. 70% zero, 30% one
bit = true
}
return bit
}
func (d *cardDisk2SequencerDrive) writePulse(value uint8) {
panic("Write not implemented on woz disk implementation")
}

View File

@ -21,16 +21,14 @@ func NewCardLogger() *CardLogger {
}
func (c *CardLogger) assign(a *Apple2, slot int) {
for i := uint8(0x0); i <= 0xf; i++ {
iCopy := i
c.addCardSoftSwitchR(i, func(*ioC0Page) uint8 {
fmt.Printf("[cardLogger] Read access to softswith 0x%x for slot %v.\n", iCopy, slot)
return 0
}, "LOGGERR")
c.addCardSoftSwitchW(i, func(_ *ioC0Page, value uint8) {
fmt.Printf("[cardLogger] Write access to softswith 0x%x for slot %v, value 0x%02x.\n", iCopy, slot, value)
}, "LOGGERW")
}
c.addCardSoftSwitches(func(_ *ioC0Page, address uint8, data uint8, write bool) uint8 {
if write {
fmt.Printf("[cardLogger] Write access to softswith 0x%x for slot %v, value 0x%02x.\n", address, slot, data)
} else {
fmt.Printf("[cardLogger] Read access to softswith 0x%x for slot %v.\n", address, slot)
}
return 0
}, "LOGGER")
c.cardBase.assign(a, slot)
}

View File

@ -34,6 +34,7 @@ func (c *CardSaturn) assign(a *Apple2, slot int) {
c.activeBlock = 0
a.mmu.initLanguageRAM(saturnBlocks)
// TODO: use addCardSoftSwitches()
for i := uint8(0x0); i <= 0xf; i++ {
iCopy := i
c.addCardSoftSwitchR(iCopy, func(*ioC0Page) uint8 {

View File

@ -60,6 +60,8 @@ func (c *CardVidex) loadCharacterMap(filename string) error {
}
func (c *CardVidex) assign(a *Apple2, slot int) {
// TODO: use addCardSoftSwitches()
for i := uint8(0x0); i <= 0xf; i++ {
// Bit 0 goes to the RS pin of the MC6548. It controls
// whether a register is being accesed or the contents

35
component/pins.go Normal file
View File

@ -0,0 +1,35 @@
package component
func ByteToPins(v uint8) [8]bool {
var pins [8]bool
for i := 0; i < 8; i++ {
pins[i] = (v & 1) != 0
v >>= 1
}
return pins
}
func PinsToByte(pins [8]bool) uint8 {
v := uint8(0)
for i := 7; i >= 0; i-- {
v <<= 1
if pins[i] {
v++
}
}
return v
}
func ReversePins(data uint8) uint8 {
pins := ByteToPins(data)
return PinsToByte([8]bool{
pins[7],
pins[6],
pins[5],
pins[4],
pins[3],
pins[2],
pins[1],
pins[0],
})
}

29
component/pins_test.go Normal file
View File

@ -0,0 +1,29 @@
package component
import (
"testing"
)
func TestByteToPins(t *testing.T) {
v := uint8(0b10010110)
a := [8]bool{
false, true, true, false,
true, false, false, true,
}
if a != ByteToPins(v) {
t.Error("Error on byte to pins")
}
}
func TestPinsToByte(t *testing.T) {
v := uint8(0b10010110)
a := [8]bool{
false, true, true, false,
true, false, false, true,
}
if v != PinsToByte(a) {
t.Error("Error on pins to byte")
}
}

38
component/sn74ls174.go Normal file
View File

@ -0,0 +1,38 @@
package component
/*
SN74LS174: hex D flip-flop
http://www.skot9000.com/ttl/datasheets/174.pdf
Pins:
A0, A1, A2: Address inputs. 'address' parameter of Write()
D: Data input. 'value' parameter of Write()
E: Enable input. 'enable' parameter of Write()
C: Clear input. Method Reset()
Q0, Q7: Parallel latch outputs. Return of Q(n)
*/
type SN74LS174 struct {
q [6]bool
}
func (o *SN74LS174) Update(d0, d1, d2, d3, d4, d5, mr bool) {
o.q[0] = d0 && mr
o.q[1] = d1 && mr
o.q[2] = d2 && mr
o.q[3] = d3 && mr
o.q[4] = d4 && mr
o.q[5] = d5 && mr
}
func (o *SN74LS174) Reset() {
o.Update(false, false, false, false, false, false, false)
}
func (o *SN74LS174) Q(index uint8) bool {
if index > 5 {
panic("There are only 6 flip flops")
}
return o.q[index]
}

45
component/sn74ls259.go Normal file
View File

@ -0,0 +1,45 @@
package component
/*
SN74LS259 or 9344: 8-bit addressable latch
http://www.skot9000.com/ttl/datasheets/259.pdf
Pins:
A0, A1, A2: Address inputs. 'address' parameter of Write()
D: Data input. 'value' parameter of Write()
E: Enable input. 'enable' parameter of Write()
C: Clear input. Method Reset()
Q0, Q7: Parallel latch outputs. Return of Q(n)
*/
type SN74LS259 struct {
value uint8
}
func (o *SN74LS259) Write(address uint8, value bool, enable bool) {
if address > 7 {
panic("The address can have only three bits")
}
if !enable {
return
}
o.value = o.value &^ (uint8(1) << address)
if value {
o.value |= 1 << address
}
}
func (o *SN74LS259) Q(address uint8) bool {
if address > 7 {
panic("The address can have only three bits")
}
return ((o.value >> address) & 1) == 1
}
func (o *SN74LS259) Reset() {
o.value = 0
}

View File

@ -0,0 +1,30 @@
package component
import (
"testing"
)
func TestSN74LS259WriteTrue(t *testing.T) {
var o SN74LS259
o.Write(2, true, true)
if !o.Q(2) {
t.Error("Wrote true but got false")
}
}
func TestSN74LS259WriteFalse(t *testing.T) {
var o SN74LS259
o.Write(1, false, true)
if o.Q(1) {
t.Error("Wrote false but got true")
}
}
func TestSN74LS259WriteTrueThenFalse(t *testing.T) {
var o SN74LS259
o.Write(5, true, true)
o.Write(5, false, true)
if o.Q(5) {
t.Error("Wrote true the false but got true")
}
}

70
component/sn74ls323.go Normal file
View File

@ -0,0 +1,70 @@
package component
/*
SN74LS323: 8-bit shift storage register
http://www.skot9000.com/ttl/datasheets/323.pdf
Pins:
CP: Clock pulse. Call to Update()
DS0: Serial data input for shift right. 'ds0' parameter of Update()
DS7: Serial data input for shift left. 'ds1' parameter of Update()
I0-7: Parallel data input. 'data' parameter of Update()
O0-7: Parallel data output. Return of Output()
OE1-2: Not supported
Q0: Serial outputs LSB. Return of Q0()
Q7: Serial outputs MSB. Return of Q7()
S0: Mode select, 's0' parameter of Update()
S1: Mode select, 's1' parameter of Update()
SR: Sync reset (active low), 'sr_n' parameter of Update()
Note: left for datasheet (I0-I1-I2..I7) is right for uint8 (b7-b6...b0)
*/
type SN74LS323 struct {
value uint8
}
func (o *SN74LS323) Update(data uint8, s0 bool, s1 bool, sr_n bool, ds0 bool, ds7 bool) {
if !sr_n {
// Reset on SR low
o.value = 0
return
}
if s1 {
if s0 {
// high, high: Parallel load
o.value = data
} else {
// high, low: Shift left (it's shift right for uint8)
o.value >>= 1
if ds7 {
o.value += 0x80
}
}
} else {
if s0 {
// low, high: Shift right (it's shift left for uint8)
o.value <<= 1
if ds0 {
o.value += 0x01
}
} else {
// low, low: Hold
// do nothing
}
}
}
func (o *SN74LS323) Q0() bool {
return (o.value & 1) != 0
}
func (o *SN74LS323) Q7() bool {
return (o.value & 0x80) != 0
}
func (o *SN74LS323) Output() uint8 {
return o.value
}

View File

@ -0,0 +1,41 @@
package component
import (
"testing"
)
func TestSN74LS323Reset(t *testing.T) {
var o SN74LS323
o.value = 0x89
o.Update(0x12, false, false, false, false, false)
if o.Output() != 0 {
t.Error("Value should reset to 0")
}
}
func TestSN74LS323ShiftLeft(t *testing.T) {
var o SN74LS323
o.value = 0x11
o.Update(0x12, false, true, true, false, true)
if o.Output() != 0x88 {
t.Error("Bad shift left")
}
}
func TestSN74LS323ShiftRight(t *testing.T) {
var o SN74LS323
o.value = 0x11
o.Update(0x12, true, false, true, true, false)
if o.Output() != 0x23 {
t.Error("Bad shift right")
}
}
func TestSN74LS323Load(t *testing.T) {
var o SN74LS323
o.value = 0x11
o.Update(0x12, true, true, true, true, false)
if o.Output() != 0x12 {
t.Error("Bad load")
}
}

65
disk/DISK2P6.rom Normal file
View File

@ -0,0 +1,65 @@
<18>
99;;8(
99;;-<2D>8H
(H(H(H(H-H8H
(H(H(H(H<><48><EFBFBD><EFBFBD>
XxXxXxXxXxXx
XxXxXxXx<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
hh<>hh<>h<EFBFBD>h<EFBFBD>
hh<>hh<><68><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD>ȨȨȨ<EFBFBD>)Y<><59>
<EFBFBD>ȨȨȨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><08><>M<><4D>
<EFBFBD><EFBFBD><08><>

214
disk/main.go Normal file
View File

@ -0,0 +1,214 @@
package main
import (
"fmt"
"github.com/ivanizag/izapple2/component"
"github.com/ivanizag/izapple2/storage"
)
// https://embeddedmicro.weebly.com/apple-ii-p6-prom-dump.html
func getInstruction(data []uint8, Q6 bool, Q7 bool, high bool, pulse bool, sequence uint8) uint8 {
seqBits := component.ByteToPins(sequence)
address := component.PinsToByte([8]bool{
seqBits[1],
high,
Q6,
Q7,
!pulse,
seqBits[0],
seqBits[2],
seqBits[3],
})
v := data[address]
//fmt.Printf("Seq %x. pulse %v, P6ROM[%02x]=%02x", sequence, pulse, i, v)
fmt.Printf("For Q6(%v) Q7(%v) H(%v) P(%v) Seq(%x) => ",
Q6, Q7, high, pulse, sequence,
)
return v
}
func printTable(data []uint8, q6 bool, q7 bool) {
for i := uint8(0); i < 16; i++ {
fmt.Printf("%x %02x %02x %02x %02x\n",
i,
getInstruction(data, q6, q7, false, true, i),
getInstruction(data, q6, q7, false, false, i),
getInstruction(data, q6, q7, true, true, i),
getInstruction(data, q6, q7, true, false, i),
)
}
}
func mainXX() {
fmt.Println("SM Analysis:")
data, _, _ := storage.LoadResource("<internal>/DISK2P6.rom")
fmt.Println("Q6 On, Q7 Off")
printTable(data, true, false)
fmt.Println("Q6 Off, Q7 Off")
printTable(data, false, false)
fmt.Println("Q6 On, Q7 On")
printTable(data, true, true)
fmt.Println("Q6 Off, Q7 On")
printTable(data, false, true)
}
type lsq struct {
sequence uint8
register uint8 // 74LS323, https://components101.com/asset/sites/default/files/component_datasheet/SN74LS323-Datasheet.pdf
}
func main() {
/*
$C08E -> q7 false
Every time $C08C high byte is set -> q6 false
*/
data, _, _ := storage.LoadResource("<internal>/DISK2P6.rom")
wozData, _, err := storage.LoadResource("/home/casa/applerelease/disks/woz test images/WOZ 2.0/DOS 3.3 System Master.woz")
if err != nil {
panic("Error creando woz")
}
woz, err := storage.NewFileWoz(wozData)
var lsq lsq
nibs := woz.DumpTrackAsNib(0)
for i := 0; i < 20; i++ {
fmt.Printf("\n%v: ", i)
for j := 0; j < 16; j++ {
fmt.Printf("%02x ", nibs[i*16+j])
}
}
fmt.Println("")
/*
bits := woz.DumpTrackAsWoz(0)
for i := 0; i < 20; i++ {
fmt.Printf("%v: %02x\n", i, bits[i])
}
*/
position := uint32(1)
maxPosition := uint32(50304)
hcycles := 0
lastReadCycle := 0
bit := uint8(0)
for position != 0 {
hcycles++
pulse := false
if (hcycles % 8) == 0 {
bit, position, maxPosition = woz.GetNextBitAndPosition(position, maxPosition, 0)
pulse = bit != 0
fmt.Printf("==Read bit %v @ %v \n", bit, position)
}
high := (lsq.register & 0x80) != 0
command := getInstruction(data, false, false, high, pulse, lsq.sequence)
//fmt.Printf(" reg %02x\n", lsq.register)
inst := command & 0xf
next := command >> 4
fmt.Printf("cmd(%x) seq(%x)\n", inst, next)
/*
https://www.youtube.com/watch?v=r1VlrJboDMw 21:30
74LS323 pins: (register)
SR = inst[3]
S1 = inst[0]
S0 = inst[1]
DS0 = write protect
DS7 = inst[2]
IO[7.0] = D[7-0] slot data bus
OE1 = AD[0], register copied to bus depending on this
OE2 = (DEV pin 41 slot)
Q7 = high
Q0 = not used
CP = clock pulse (Q3 pin37 slot, 2Mhz clock) AND motor (using 74LS132)
ROM P6 pins:
A0, A5, A6, A7 <= sequence from 74LS174
D4, D5, D6, D7 => sequence to 74LS174
A4 <= pulse transition
A2 <= Q6 from 9334
A3 <= Q7 from 9334
A1 =< MSB of register (pin Q7)
D0-S3 => S1, S0, SR and DS7 of register
E2: motor
74LS174 pins (hex flip flop) (sequence 0,1,2,5)
CP = clock pulse (Q3 pin37 slot, 2Mhz clock) AND motor (using 74LS132)
MR = motor
seq:
D0 <- ROM D7
Q0 -> ROM A7
D1 <- ROM D6
Q1 -> ROM A6
D2 <- ROM D4
Q2 -> ROM A5
D5 <- ROM D5
Q5 -> ROM A0
pulse transition:
D3 <- from Q4
Q3 -> and not Q4 -> to ROM A4 (detects change in pulse)
D4 <- pulse (from disk)
Q4 -> to D3
9334 (or 74LS259) 8 bit latch
Q0-Q3: phases
Q4: motor control to 556
Q5: drive select
Q6: to ROM P6
Q7: to ROM P7
R: to slot reset
A0: slot AD[1]
A1: slot AD[2]
A3: slot AD[3]
D: slot AD[0]
E: (DEV 41 pin slot)
Write on Q7 from the 9334 and Q0 (seq[3]) transition on 74ls174
556, for the motor signal, to continue a bit longer?
*/
//fmt.Printf("cmd %02x, reg %02x, inst %x, seq %x, next %x, pulse %v ", command, lsq.register, inst, lsq.sequence, next, pulse)
switch inst {
case 0: // CLR, 74LS323 pin 9, Sync Reset
lsq.register = 0
//fmt.Printf("CLR")
case 8: // NOP
// Nothing
//fmt.Printf("NOP")
case 9: // SL0 -> S1=1, S0=0
lsq.register = lsq.register << 1
//fmt.Printf("SL0")
case 0xa: // SR
lsq.register = lsq.register >> 1 // + write protect MSB on pin 11 DS0
//fmt.Printf("SR")
case 0xb: // LD -> parallel load S1=1, S0=1
panic("not in read mode")
// lsq.register = bus
case 0xd: // SL1
lsq.register = 1 + (lsq.register << 1)
//fmt.Printf("SL1")
default:
panic("missing instruction")
}
lsq.sequence = next
//fmt.Println("")
if lsq.register > 0x7f && (hcycles-lastReadCycle) > 60 {
fmt.Printf("Byte %02x at %v\n", lsq.register, position)
lastReadCycle = hcycles
}
}
}

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/ivanizag/izapple2
go 1.12
go 1.13
require (
fyne.io/fyne/v2 v2.0.1

View File

@ -19,7 +19,7 @@ var Assets = func() http.FileSystem {
fs := vfsgen۰FS{
"/": &vfsgen۰DirInfo{
name: "/",
modTime: time.Date(2021, 3, 14, 16, 59, 4, 46831383, time.UTC),
modTime: time.Date(2021, 5, 9, 17, 44, 37, 290610863, time.UTC),
},
"/80ColumnP109.BIN": &vfsgen۰CompressedFileInfo{
name: "80ColumnP109.BIN",
@ -133,6 +133,13 @@ var Assets = func() http.FileSystem {
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5a\xa4\xb0\x80\x61\x11\x73\x9b\x4d\x17\x97\x8a\xcd\x07\x01\x56\x1b\xcf\xff\x9a\x75\x1b\x38\xbc\x2e\xfc\x9e\x31\x37\x8c\xf9\xc4\x0b\x81\xa7\x0a\x11\xff\x77\xed\x65\x60\xe4\xe2\xe2\xe2\x6a\xd5\x5e\xb5\xb7\xef\xc0\xde\x9e\x03\x7b\xbb\x0e\xec\xed\x3c\xb0\x20\x60\x6f\xc3\x81\x19\x9a\xcc\x5c\xac\xda\xab\xf6\x36\x1e\x58\x19\xa6\xb0\xe2\x4f\x87\xc0\xeb\x56\xb5\x56\xdb\x56\xc7\x95\x1c\xad\xea\x12\x1c\x7b\x7b\x0e\x08\xfc\xf6\xbc\x7a\xe1\x3b\x98\x71\x72\xd5\x85\xcf\xaf\x20\xac\x69\x1f\x38\x35\x26\xdc\xf7\x5c\xfb\x41\xf5\xc2\xcd\x05\xcc\xad\x0e\x60\x51\xad\x56\x1b\x30\xad\x6a\xd3\x71\xe1\x8d\xc6\x51\xdb\x0b\xfb\x96\x3a\x1c\x75\xbc\xb0\x63\xc3\xf6\x05\x61\x2d\x36\x7b\x40\x52\x91\xd7\x98\x96\xd8\x74\xcc\x64\x60\xbe\xf0\x0e\x59\x64\xa2\xda\x89\x0b\xef\x61\xdc\x0b\xed\x0b\x18\x16\x85\x9d\x32\xf8\xbd\x51\x2d\x8e\x81\x59\x0b\x84\x41\xf2\xef\x9e\xa9\x3f\xb3\x5d\x6a\x7b\x96\x81\x63\x99\xf6\x84\xdb\x3e\x8c\x1c\x0c\x20\x00\x08\x00\x00\xff\xff\xf6\x44\x71\xce\x00\x01\x00\x00"),
},
"/DISK2P6.rom": &vfsgen۰CompressedFileInfo{
name: "DISK2P6.rom",
modTime: time.Date(2021, 4, 9, 16, 9, 37, 674022769, time.UTC),
uncompressedSize: 256,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x92\xb8\x21\xc1\xc1\xc5\xc5\xc5\x25\x61\x29\x61\x29\x61\x2d\x61\x2d\x61\x21\xa1\x81\xcc\xd7\xbd\x61\xe1\x01\xe2\x6b\x78\x40\xa0\xae\x07\x2a\xff\xc6\x8d\x1b\x37\x40\xfc\x88\x0a\x04\x44\xe6\xc3\xe4\x33\x38\x32\x3a\xc0\xb8\x23\xa3\x03\x99\x7f\xe3\x2c\x44\x7e\xc6\xce\x19\x3b\x67\xec\x9e\xb1\x7b\xc6\xde\x19\x3b\x90\xf9\x37\x6e\x42\xe4\x57\x9c\x80\x40\xcd\xc8\x15\x27\x90\xf9\x37\xff\xde\xf8\x01\xe2\xdf\xf8\x01\x81\x37\xff\x2e\x40\xe1\xdf\xb8\xfb\xe2\x01\x88\xff\xa2\xe3\x05\x07\x08\x73\xf8\xa2\xf2\x01\x01\x00\x00\xff\xff\x70\x2c\x2a\xb7\x00\x01\x00\x00"),
},
"/MemoryExpansionCard-341-0344a.bin": &vfsgen۰CompressedFileInfo{
name: "MemoryExpansionCard-341-0344a.bin",
modTime: time.Date(2021, 1, 23, 23, 12, 20, 701418936, time.UTC),
@ -179,6 +186,7 @@ var Assets = func() http.FileSystem {
fs["/BASE64A_F8.BIN"].(os.FileInfo),
fs["/BASE64A_ROM7_CharGen.BIN"].(os.FileInfo),
fs["/DISK2.rom"].(os.FileInfo),
fs["/DISK2P6.rom"].(os.FileInfo),
fs["/MemoryExpansionCard-341-0344a.bin"].(os.FileInfo),
fs["/ThunderclockPlusROM.bin"].(os.FileInfo),
fs["/Videx Videoterm ROM 2.4.bin"].(os.FileInfo),

View File

@ -43,7 +43,7 @@ func LoadDiskette(filename string) (Diskette, error) {
}
if isFileWoz(data) {
f, err := newFileWoz(data)
f, err := NewFileWoz(data)
if err != nil {
return nil, err
}

View File

@ -33,24 +33,56 @@ Emulation status for the disks used on the reference:
- When bits aren't really bits
- The Print Shop Companion: Working
- What is the lifespan of the data latch?
- *** First Math Adventures - Understanding Word Problems
- *** First Math Adventures: Not working
- Reading Offset Data Streams
- *** Wings of Fury: Not working
- Stickybear Town Builder: Working
- Optimal bit timing of WOZ 2,0
- * Border Zone: Unknown, there is no UI to swap disks
With the sequencer:
- How to begin
- DOS 3.3: Works
- * DOS 3.2: Not working, 13 sector disks can't boot
- Next choices
- *** Bouncing Kamungas: Not working
- Commando: Working
- Planetfall: Working
- Rescue Raiders: Working
- Sammy Lightfoot: Working
- Stargate: Working
- Cross track sync
- Blazing Paddles: Working
- Take 1: Working
- Hard Hat Mack: Working
- Half tracks
- The Bilestoad: Working
- Even more bit fiddling
- Dino Eggs: Working
- Crisis Mountain: Working
- Miner 2049er II: Working
- When bits aren't really bits
- The Print Shop Companion: Working
- What is the lifespan of the data latch?
- First Math Adventures: Working
- Reading Offset Data Streams
- Wings of Fury: Working
- Stickybear Town Builder: Working
- Optimal bit timing of WOZ 2,0
- * Border Zone: Unknown, there is no UI to swap disks
*/
type disketteWoz struct {
data *fileWoz
data *FileWoz
cycleOn uint64 // Cycle when the disk was last turned on
turning bool
latch uint8
position uint32
cycle uint64
lastQuarterTrack int
latch uint8
position uint32
positionMax uint32 // As tracks may have different lengths position is related of positionMax of the las track
cycle uint64
mc3470Buffer uint8 // Four bit buffer to detect weak bits and to add latency
@ -58,12 +90,12 @@ type disketteWoz struct {
visibleLatchCountDown int8 // The visible latch stores a valid latch reading for 2 bit timings
}
func newDisquetteWoz(f *fileWoz) (*disketteWoz, error) {
func newDisquetteWoz(f *FileWoz) (*disketteWoz, error) {
// Discard not supported features
if f.info.DiskType != 1 {
if f.Info.DiskType != 1 {
return nil, errors.New("Only 5.25 disks are supported")
}
if f.info.BootSectorFormat == 2 { // Info not available in WOZ 1.0
if f.Info.BootSectorFormat == 2 { // Info not available in WOZ 1.0
return nil, errors.New("Woz 13 sector disks are not supported")
}
@ -91,8 +123,7 @@ func (d *disketteWoz) Read(quarterTrack int, cycle uint64) uint8 {
for i := uint64(0); i < deltaBits; i++ {
// Get next bit taking into account the MC3470 latency and weak bits
var fluxBit uint8
fluxBit, d.position = d.data.getNextBitAndPosition(d.position, quarterTrack, d.lastQuarterTrack)
d.lastQuarterTrack = quarterTrack
fluxBit, d.position, d.positionMax = d.data.GetNextBitAndPosition(d.position, d.positionMax, quarterTrack)
d.mc3470Buffer = (d.mc3470Buffer<<1 + fluxBit) & 0x0f
bit := (d.mc3470Buffer >> 1) & 0x1 // Use the previous to last bit to add latency
if d.mc3470Buffer == 0 && rand.Intn(100) < 3 {

View File

@ -13,9 +13,9 @@ See:
https://applesaucefdc.com/woz/
*/
type fileWoz struct {
type FileWoz struct {
version int
info woz2Info
Info woz2Info
trackMap []uint8
tracks [wozMaxTrack]disketteTrackWoz
meta map[string]string
@ -80,20 +80,30 @@ const (
var headerWoz1 = []uint8{0x57, 0x4f, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A}
var headerWoz2 = []uint8{0x57, 0x4f, 0x5A, 0x32, 0xFF, 0x0A, 0x0D, 0x0A}
func (f *fileWoz) getNextBitAndPosition(position uint32, quarterTrack int, prevQuarterTrack int) (uint8, uint32) {
trackWoz := f.tracks[f.trackMap[quarterTrack]]
if prevQuarterTrack != quarterTrack {
// Adjust position as tracks may have different length
prevTrackWoz := f.tracks[f.trackMap[prevQuarterTrack]]
newPosition := position * trackWoz.bitCount / prevTrackWoz.bitCount
//fmt.Printf("%03d to %03d: adjustmend %d\n", prevQuarterTrack, quarterTrack, int64(newPosition)-int64(position))
position = newPosition
func (f *FileWoz) GetNextBitAndPosition(position uint32, positionMax uint32, quarterTrack int) (uint8, uint32, uint32) {
if positionMax == 0 {
// First unitialised use
positionMax = ^uint32(0) // MaxUint32
}
position++
position %= trackWoz.bitCount
return trackWoz.data[position/8] >> (7 - position%8) & 1, position
position %= positionMax
trackIndex := f.trackMap[quarterTrack]
if trackIndex == 0xff {
// No track defined
// TODO: return random value
return 0, position, positionMax
}
trackWoz := f.tracks[trackIndex]
if trackWoz.bitCount != positionMax {
// Adjust position as tracks have different length
position = position * trackWoz.bitCount / positionMax
positionMax = trackWoz.bitCount
}
return trackWoz.data[position/8] >> (7 - position%8) & 1, position, positionMax
}
func isFileWoz(data []uint8) bool {
@ -107,8 +117,8 @@ func isFileWoz(data []uint8) bool {
return false
}
func newFileWoz(data []uint8) (*fileWoz, error) {
var f fileWoz
func NewFileWoz(data []uint8) (*FileWoz, error) {
var f FileWoz
// Verify header. Note, the CRC is not verified
header := data[:len(headerWoz2)]
@ -147,9 +157,9 @@ func newFileWoz(data []uint8) (*fileWoz, error) {
}
switch f.version {
case 1:
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.info.woz1Info)
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.Info.woz1Info)
case 2:
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.info)
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.Info)
}
// Read the optional META chunk
@ -211,7 +221,12 @@ func newFileWoz(data []uint8) (*fileWoz, error) {
return &f, nil
}
func (f *fileWoz) dumpTrackAsNib(quarterTrack int) []uint8 {
func (f *FileWoz) DumpTrackAsWoz(quarterTrack int) []uint8 {
trackWoz := f.tracks[f.trackMap[quarterTrack]]
return trackWoz.data
}
func (f *FileWoz) DumpTrackAsNib(quarterTrack int) []uint8 {
trackWoz := f.tracks[f.trackMap[quarterTrack]]
out := make([]uint8, 0, trackWoz.bitCount/8)
latch := uint8(0)
@ -227,21 +242,21 @@ func (f *fileWoz) dumpTrackAsNib(quarterTrack int) []uint8 {
return out
}
func (f *fileWoz) dump() {
func (f *FileWoz) dump() {
fmt.Printf("Woz image:\n")
fmt.Printf(" Version: %v\n", f.info.Version)
fmt.Printf(" Disk type: %v\n", f.info.DiskType)
fmt.Printf(" Write protected: %v\n", f.info.WriteProtected)
fmt.Printf(" Synchronized: %v\n", f.info.Synchronized)
fmt.Printf(" Cleaned: %v\n", f.info.Cleaned)
fmt.Printf(" Creator: %v\n", string(f.info.Creator[:]))
if f.info.Version >= 2 {
fmt.Printf(" Disk sides: %v\n", f.info.DiskSides)
fmt.Printf(" Boot sector format: %v\n", f.info.BootSectorFormat)
fmt.Printf(" Optimal bit timing: %v ns\n", 125*int(f.info.OptimalBitTiming))
fmt.Printf(" Compatible hardware: 0x%x\n", f.info.CompatibleHardware)
fmt.Printf(" Required RAM: %vKB\n", f.info.RequiredRAM)
fmt.Printf(" Largest track: %v blocks\n", f.info.LargestTrack)
fmt.Printf(" Version: %v\n", f.Info.Version)
fmt.Printf(" Disk type: %v\n", f.Info.DiskType)
fmt.Printf(" Write protected: %v\n", f.Info.WriteProtected)
fmt.Printf(" Synchronized: %v\n", f.Info.Synchronized)
fmt.Printf(" Cleaned: %v\n", f.Info.Cleaned)
fmt.Printf(" Creator: %v\n", string(f.Info.Creator[:]))
if f.Info.Version >= 2 {
fmt.Printf(" Disk sides: %v\n", f.Info.DiskSides)
fmt.Printf(" Boot sector format: %v\n", f.Info.BootSectorFormat)
fmt.Printf(" Optimal bit timing: %v ns\n", 125*int(f.Info.OptimalBitTiming))
fmt.Printf(" Compatible hardware: 0x%x\n", f.Info.CompatibleHardware)
fmt.Printf(" Required RAM: %vKB\n", f.Info.RequiredRAM)
fmt.Printf(" Largest track: %v blocks\n", f.Info.LargestTrack)
}
if f.meta != nil {
fmt.Printf(" Metadata:\n")