mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-04-11 17:39:47 +00:00
Disk II state machine
This commit is contained in:
parent
902437419e
commit
0daf1b9fd9
@ -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()
|
||||
|
@ -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 != "" {
|
||||
|
17
cardBase.go
17
cardBase.go
@ -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))
|
||||
}
|
||||
}
|
||||
|
85
cardDisk2.go
85
cardDisk2.go
@ -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
88
cardDisk2DriveStepper.go
Normal 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
305
cardDisk2Sequencer.bak
Normal 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
267
cardDisk2Sequencer.go
Normal 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
|
||||
}
|
89
cardDisk2SequencerDrive.go
Normal file
89
cardDisk2SequencerDrive.go
Normal 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")
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
35
component/pins.go
Normal 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
29
component/pins_test.go
Normal 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
38
component/sn74ls174.go
Normal 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
45
component/sn74ls259.go
Normal 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
|
||||
}
|
30
component/sn74ls259_test.go
Normal file
30
component/sn74ls259_test.go
Normal 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
70
component/sn74ls323.go
Normal 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
|
||||
}
|
41
component/sn74ls323_test.go
Normal file
41
component/sn74ls323_test.go
Normal 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
65
disk/DISK2P6.rom
Normal 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
214
disk/main.go
Normal 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
2
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/ivanizag/izapple2
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.0.1
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user