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
|
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
|
// AddSmartPortDisk adds a smart port card and image
|
||||||
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, trace bool) error {
|
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, trace bool) error {
|
||||||
c := NewCardHardDisk()
|
c := NewCardHardDisk()
|
||||||
|
|
|
@ -138,6 +138,10 @@ func MainApple() *Apple2 {
|
||||||
"forceCaps",
|
"forceCaps",
|
||||||
false,
|
false,
|
||||||
"force all letters to be uppercased (no need for caps lock!)")
|
"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()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -284,9 +288,17 @@ func MainApple() *Apple2 {
|
||||||
a.AddFastChip(*fastChipCardSlot)
|
a.AddFastChip(*fastChipCardSlot)
|
||||||
}
|
}
|
||||||
if *disk2Slot > 0 {
|
if *disk2Slot > 0 {
|
||||||
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage)
|
if *expermintalWoz {
|
||||||
if err != nil {
|
err := a.AddDisk2Sequencer(*disk2Slot, diskImageFinal, *diskBImage)
|
||||||
panic(err)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hardDiskImageFinal != "" {
|
if hardDiskImageFinal != "" {
|
||||||
|
|
17
cardBase.go
17
cardBase.go
|
@ -1,6 +1,8 @@
|
||||||
package izapple2
|
package izapple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2/storage"
|
"github.com/ivanizag/izapple2/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,3 +115,18 @@ func (c *cardBase) addCardSoftSwitchW(address uint8, ss softSwitchW, name string
|
||||||
c._ssw[address] = ss
|
c._ssw[address] = ss
|
||||||
c._sswName[address] = name
|
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
|
// Update magnets and position
|
||||||
drive := &c.drive[c.selected]
|
drive := &c.drive[c.selected]
|
||||||
drive.phases &^= (1 << phase)
|
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
|
return c.dataLatch // All even addresses return the last dataLatch
|
||||||
}, fmt.Sprintf("PHASE%vOFF", phase))
|
}, fmt.Sprintf("PHASE%vOFF", phase))
|
||||||
|
@ -100,7 +100,7 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
|
||||||
// Update magnets and position
|
// Update magnets and position
|
||||||
drive := &c.drive[c.selected]
|
drive := &c.drive[c.selected]
|
||||||
drive.phases |= (1 << phase)
|
drive.phases |= (1 << phase)
|
||||||
drive.tracksStep = moveStep(drive.phases, drive.tracksStep)
|
drive.tracksStep = moveDriveStepper(drive.phases, drive.tracksStep)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}, fmt.Sprintf("PHASE%vON", phase))
|
}, 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) {
|
func (d *cardDisk2Drive) insertDiskette(name string, dt storage.Diskette) {
|
||||||
d.name = name
|
d.name = name
|
||||||
d.diskette = dt
|
d.diskette = dt
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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) {
|
func (c *CardLogger) assign(a *Apple2, slot int) {
|
||||||
for i := uint8(0x0); i <= 0xf; i++ {
|
c.addCardSoftSwitches(func(_ *ioC0Page, address uint8, data uint8, write bool) uint8 {
|
||||||
iCopy := i
|
if write {
|
||||||
c.addCardSoftSwitchR(i, func(*ioC0Page) uint8 {
|
fmt.Printf("[cardLogger] Write access to softswith 0x%x for slot %v, value 0x%02x.\n", address, slot, data)
|
||||||
fmt.Printf("[cardLogger] Read access to softswith 0x%x for slot %v.\n", iCopy, slot)
|
} else {
|
||||||
return 0
|
fmt.Printf("[cardLogger] Read access to softswith 0x%x for slot %v.\n", address, slot)
|
||||||
}, "LOGGERR")
|
}
|
||||||
c.addCardSoftSwitchW(i, func(_ *ioC0Page, value uint8) {
|
return 0
|
||||||
fmt.Printf("[cardLogger] Write access to softswith 0x%x for slot %v, value 0x%02x.\n", iCopy, slot, value)
|
}, "LOGGER")
|
||||||
}, "LOGGERW")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cardBase.assign(a, slot)
|
c.cardBase.assign(a, slot)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ func (c *CardSaturn) assign(a *Apple2, slot int) {
|
||||||
c.activeBlock = 0
|
c.activeBlock = 0
|
||||||
a.mmu.initLanguageRAM(saturnBlocks)
|
a.mmu.initLanguageRAM(saturnBlocks)
|
||||||
|
|
||||||
|
// TODO: use addCardSoftSwitches()
|
||||||
for i := uint8(0x0); i <= 0xf; i++ {
|
for i := uint8(0x0); i <= 0xf; i++ {
|
||||||
iCopy := i
|
iCopy := i
|
||||||
c.addCardSoftSwitchR(iCopy, func(*ioC0Page) uint8 {
|
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) {
|
func (c *CardVidex) assign(a *Apple2, slot int) {
|
||||||
|
|
||||||
|
// TODO: use addCardSoftSwitches()
|
||||||
for i := uint8(0x0); i <= 0xf; i++ {
|
for i := uint8(0x0); i <= 0xf; i++ {
|
||||||
// Bit 0 goes to the RS pin of the MC6548. It controls
|
// Bit 0 goes to the RS pin of the MC6548. It controls
|
||||||
// whether a register is being accesed or the contents
|
// whether a register is being accesed or the contents
|
||||||
|
|
|
@ -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],
|
||||||
|
})
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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><>
|
|
@ -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
|
module github.com/ivanizag/izapple2
|
||||||
|
|
||||||
go 1.12
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.0.1
|
fyne.io/fyne/v2 v2.0.1
|
||||||
|
|
|
@ -19,7 +19,7 @@ var Assets = func() http.FileSystem {
|
||||||
fs := vfsgen۰FS{
|
fs := vfsgen۰FS{
|
||||||
"/": &vfsgen۰DirInfo{
|
"/": &vfsgen۰DirInfo{
|
||||||
name: "/",
|
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{
|
"/80ColumnP109.BIN": &vfsgen۰CompressedFileInfo{
|
||||||
name: "80ColumnP109.BIN",
|
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"),
|
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{
|
"/MemoryExpansionCard-341-0344a.bin": &vfsgen۰CompressedFileInfo{
|
||||||
name: "MemoryExpansionCard-341-0344a.bin",
|
name: "MemoryExpansionCard-341-0344a.bin",
|
||||||
modTime: time.Date(2021, 1, 23, 23, 12, 20, 701418936, time.UTC),
|
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_F8.BIN"].(os.FileInfo),
|
||||||
fs["/BASE64A_ROM7_CharGen.BIN"].(os.FileInfo),
|
fs["/BASE64A_ROM7_CharGen.BIN"].(os.FileInfo),
|
||||||
fs["/DISK2.rom"].(os.FileInfo),
|
fs["/DISK2.rom"].(os.FileInfo),
|
||||||
|
fs["/DISK2P6.rom"].(os.FileInfo),
|
||||||
fs["/MemoryExpansionCard-341-0344a.bin"].(os.FileInfo),
|
fs["/MemoryExpansionCard-341-0344a.bin"].(os.FileInfo),
|
||||||
fs["/ThunderclockPlusROM.bin"].(os.FileInfo),
|
fs["/ThunderclockPlusROM.bin"].(os.FileInfo),
|
||||||
fs["/Videx Videoterm ROM 2.4.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) {
|
if isFileWoz(data) {
|
||||||
f, err := newFileWoz(data)
|
f, err := NewFileWoz(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,24 +33,56 @@ Emulation status for the disks used on the reference:
|
||||||
- When bits aren't really bits
|
- When bits aren't really bits
|
||||||
- The Print Shop Companion: Working
|
- The Print Shop Companion: Working
|
||||||
- What is the lifespan of the data latch?
|
- What is the lifespan of the data latch?
|
||||||
- *** First Math Adventures - Understanding Word Problems
|
- *** First Math Adventures: Not working
|
||||||
- Reading Offset Data Streams
|
- Reading Offset Data Streams
|
||||||
- *** Wings of Fury: Not working
|
- *** Wings of Fury: Not working
|
||||||
- Stickybear Town Builder: Working
|
- Stickybear Town Builder: Working
|
||||||
- Optimal bit timing of WOZ 2,0
|
- Optimal bit timing of WOZ 2,0
|
||||||
- * Border Zone: Unknown, there is no UI to swap disks
|
- * 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 {
|
type disketteWoz struct {
|
||||||
data *fileWoz
|
data *FileWoz
|
||||||
cycleOn uint64 // Cycle when the disk was last turned on
|
cycleOn uint64 // Cycle when the disk was last turned on
|
||||||
turning bool
|
turning bool
|
||||||
|
|
||||||
latch uint8
|
latch uint8
|
||||||
position uint32
|
position uint32
|
||||||
cycle uint64
|
positionMax uint32 // As tracks may have different lengths position is related of positionMax of the las track
|
||||||
lastQuarterTrack int
|
cycle uint64
|
||||||
|
|
||||||
mc3470Buffer uint8 // Four bit buffer to detect weak bits and to add latency
|
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
|
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
|
// Discard not supported features
|
||||||
if f.info.DiskType != 1 {
|
if f.Info.DiskType != 1 {
|
||||||
return nil, errors.New("Only 5.25 disks are supported")
|
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")
|
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++ {
|
for i := uint64(0); i < deltaBits; i++ {
|
||||||
// Get next bit taking into account the MC3470 latency and weak bits
|
// Get next bit taking into account the MC3470 latency and weak bits
|
||||||
var fluxBit uint8
|
var fluxBit uint8
|
||||||
fluxBit, d.position = d.data.getNextBitAndPosition(d.position, quarterTrack, d.lastQuarterTrack)
|
fluxBit, d.position, d.positionMax = d.data.GetNextBitAndPosition(d.position, d.positionMax, quarterTrack)
|
||||||
d.lastQuarterTrack = quarterTrack
|
|
||||||
d.mc3470Buffer = (d.mc3470Buffer<<1 + fluxBit) & 0x0f
|
d.mc3470Buffer = (d.mc3470Buffer<<1 + fluxBit) & 0x0f
|
||||||
bit := (d.mc3470Buffer >> 1) & 0x1 // Use the previous to last bit to add latency
|
bit := (d.mc3470Buffer >> 1) & 0x1 // Use the previous to last bit to add latency
|
||||||
if d.mc3470Buffer == 0 && rand.Intn(100) < 3 {
|
if d.mc3470Buffer == 0 && rand.Intn(100) < 3 {
|
||||||
|
|
|
@ -13,9 +13,9 @@ See:
|
||||||
https://applesaucefdc.com/woz/
|
https://applesaucefdc.com/woz/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type fileWoz struct {
|
type FileWoz struct {
|
||||||
version int
|
version int
|
||||||
info woz2Info
|
Info woz2Info
|
||||||
trackMap []uint8
|
trackMap []uint8
|
||||||
tracks [wozMaxTrack]disketteTrackWoz
|
tracks [wozMaxTrack]disketteTrackWoz
|
||||||
meta map[string]string
|
meta map[string]string
|
||||||
|
@ -80,20 +80,30 @@ const (
|
||||||
var headerWoz1 = []uint8{0x57, 0x4f, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A}
|
var headerWoz1 = []uint8{0x57, 0x4f, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A}
|
||||||
var headerWoz2 = []uint8{0x57, 0x4f, 0x5A, 0x32, 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) {
|
func (f *FileWoz) GetNextBitAndPosition(position uint32, positionMax uint32, quarterTrack int) (uint8, uint32, uint32) {
|
||||||
trackWoz := f.tracks[f.trackMap[quarterTrack]]
|
if positionMax == 0 {
|
||||||
|
// First unitialised use
|
||||||
if prevQuarterTrack != quarterTrack {
|
positionMax = ^uint32(0) // MaxUint32
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
position++
|
position++
|
||||||
position %= trackWoz.bitCount
|
position %= positionMax
|
||||||
return trackWoz.data[position/8] >> (7 - position%8) & 1, position
|
|
||||||
|
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 {
|
func isFileWoz(data []uint8) bool {
|
||||||
|
@ -107,8 +117,8 @@ func isFileWoz(data []uint8) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileWoz(data []uint8) (*fileWoz, error) {
|
func NewFileWoz(data []uint8) (*FileWoz, error) {
|
||||||
var f fileWoz
|
var f FileWoz
|
||||||
|
|
||||||
// Verify header. Note, the CRC is not verified
|
// Verify header. Note, the CRC is not verified
|
||||||
header := data[:len(headerWoz2)]
|
header := data[:len(headerWoz2)]
|
||||||
|
@ -147,9 +157,9 @@ func newFileWoz(data []uint8) (*fileWoz, error) {
|
||||||
}
|
}
|
||||||
switch f.version {
|
switch f.version {
|
||||||
case 1:
|
case 1:
|
||||||
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.info.woz1Info)
|
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.Info.woz1Info)
|
||||||
case 2:
|
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
|
// Read the optional META chunk
|
||||||
|
@ -211,7 +221,12 @@ func newFileWoz(data []uint8) (*fileWoz, error) {
|
||||||
return &f, nil
|
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]]
|
trackWoz := f.tracks[f.trackMap[quarterTrack]]
|
||||||
out := make([]uint8, 0, trackWoz.bitCount/8)
|
out := make([]uint8, 0, trackWoz.bitCount/8)
|
||||||
latch := uint8(0)
|
latch := uint8(0)
|
||||||
|
@ -227,21 +242,21 @@ func (f *fileWoz) dumpTrackAsNib(quarterTrack int) []uint8 {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileWoz) dump() {
|
func (f *FileWoz) dump() {
|
||||||
fmt.Printf("Woz image:\n")
|
fmt.Printf("Woz image:\n")
|
||||||
fmt.Printf(" Version: %v\n", f.info.Version)
|
fmt.Printf(" Version: %v\n", f.Info.Version)
|
||||||
fmt.Printf(" Disk type: %v\n", f.info.DiskType)
|
fmt.Printf(" Disk type: %v\n", f.Info.DiskType)
|
||||||
fmt.Printf(" Write protected: %v\n", f.info.WriteProtected)
|
fmt.Printf(" Write protected: %v\n", f.Info.WriteProtected)
|
||||||
fmt.Printf(" Synchronized: %v\n", f.info.Synchronized)
|
fmt.Printf(" Synchronized: %v\n", f.Info.Synchronized)
|
||||||
fmt.Printf(" Cleaned: %v\n", f.info.Cleaned)
|
fmt.Printf(" Cleaned: %v\n", f.Info.Cleaned)
|
||||||
fmt.Printf(" Creator: %v\n", string(f.info.Creator[:]))
|
fmt.Printf(" Creator: %v\n", string(f.Info.Creator[:]))
|
||||||
if f.info.Version >= 2 {
|
if f.Info.Version >= 2 {
|
||||||
fmt.Printf(" Disk sides: %v\n", f.info.DiskSides)
|
fmt.Printf(" Disk sides: %v\n", f.Info.DiskSides)
|
||||||
fmt.Printf(" Boot sector format: %v\n", f.info.BootSectorFormat)
|
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(" Optimal bit timing: %v ns\n", 125*int(f.Info.OptimalBitTiming))
|
||||||
fmt.Printf(" Compatible hardware: 0x%x\n", f.info.CompatibleHardware)
|
fmt.Printf(" Compatible hardware: 0x%x\n", f.Info.CompatibleHardware)
|
||||||
fmt.Printf(" Required RAM: %vKB\n", f.info.RequiredRAM)
|
fmt.Printf(" Required RAM: %vKB\n", f.Info.RequiredRAM)
|
||||||
fmt.Printf(" Largest track: %v blocks\n", f.info.LargestTrack)
|
fmt.Printf(" Largest track: %v blocks\n", f.Info.LargestTrack)
|
||||||
}
|
}
|
||||||
if f.meta != nil {
|
if f.meta != nil {
|
||||||
fmt.Printf(" Metadata:\n")
|
fmt.Printf(" Metadata:\n")
|
||||||
|
|
Loading…
Reference in New Issue