mirror of
https://github.com/freewilll/apple2-go.git
synced 2024-06-01 20:41:27 +00:00
Added disk controller and disk images ...
With this, DOS3.3 is booting
This commit is contained in:
parent
7ec43f0299
commit
100c44cb11
|
@ -8,6 +8,7 @@ import (
|
|||
"mos6502go/cpu"
|
||||
"mos6502go/keyboard"
|
||||
"mos6502go/mmu"
|
||||
"mos6502go/utils"
|
||||
"mos6502go/video"
|
||||
)
|
||||
|
||||
|
@ -18,8 +19,9 @@ const (
|
|||
)
|
||||
|
||||
var showInstructions *bool
|
||||
var disableBell *bool
|
||||
var disableFirmwareWait *bool
|
||||
var resetKeysDown bool
|
||||
var breakAddress *uint16
|
||||
|
||||
func reset() {
|
||||
bootVector := 0xfffc
|
||||
|
@ -44,18 +46,28 @@ func update(screen *ebiten.Image) error {
|
|||
keyboard.Poll()
|
||||
checkResetKeys()
|
||||
|
||||
cpu.Run(*showInstructions, nil, *disableBell, 1024000/60)
|
||||
cpu.Run(*showInstructions, breakAddress, *disableFirmwareWait, 1024000/60)
|
||||
return video.DrawTextScreen(screen)
|
||||
}
|
||||
|
||||
func main() {
|
||||
showInstructions = flag.Bool("show-instructions", false, "Show instructions code while running")
|
||||
disableBell = flag.Bool("disable-bell", false, "Disable bell")
|
||||
disableFirmwareWait = flag.Bool("disable-wait", false, "Ignore JSRs to firmware wait at $FCA8")
|
||||
breakAddressString := flag.String("break", "", "Break on address")
|
||||
diskImage := flag.String("image", "", "Disk Image")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
breakAddress = utils.DecodeCmdLineAddress(breakAddressString)
|
||||
|
||||
cpu.InitInstructionDecoder()
|
||||
mmu.InitRAM()
|
||||
mmu.InitApple2eROM()
|
||||
mmu.InitIO()
|
||||
|
||||
if *diskImage != "" {
|
||||
mmu.ReadDiskImage(*diskImage)
|
||||
}
|
||||
|
||||
cpu.Init()
|
||||
|
||||
|
|
|
@ -530,7 +530,7 @@ func nmi(cycles *int) {
|
|||
*cycles += 7
|
||||
}
|
||||
|
||||
func Run(showInstructions bool, breakAddress *uint16, disableBell bool, wantedCycles int) {
|
||||
func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool, wantedCycles int) {
|
||||
cycles := 0
|
||||
|
||||
for {
|
||||
|
@ -569,6 +569,7 @@ func Run(showInstructions bool, breakAddress *uint16, disableBell bool, wantedCy
|
|||
|
||||
if breakAddress != nil && State.PC == *breakAddress {
|
||||
fmt.Printf("Break at $%04x\n", *breakAddress)
|
||||
PrintInstruction(true)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
@ -591,8 +592,9 @@ func Run(showInstructions bool, breakAddress *uint16, disableBell bool, wantedCy
|
|||
value := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8
|
||||
cycles += 6
|
||||
|
||||
if disableBell && value == 0xfca8 {
|
||||
if disableFirmwareWait && value == 0xfca8 {
|
||||
State.PC += 3
|
||||
State.A = 0
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
233
mmu/disk.go
Normal file
233
mmu/disk.go
Normal file
|
@ -0,0 +1,233 @@
|
|||
package mmu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const tracksPerDisk = 35
|
||||
const sectorsPerTrack = 16
|
||||
const imageLength = tracksPerDisk * sectorsPerTrack * 0x100
|
||||
|
||||
// Each sector has
|
||||
// Address field prologue 0x003 bytes
|
||||
// Volume, Track, Sector, Checksum 0x008 bytes
|
||||
// Address field epilogue 0x003 bytes
|
||||
// Data prologue 0x003 bytes
|
||||
// 2-bits 0x056 bytes
|
||||
// 6-bits 0x100 bytes
|
||||
// checksum 0x001 byte
|
||||
// Data epilogue 0x003 bytes
|
||||
const diskSectorBytes = 3 + 8 + 3 + 3 + 0x56 + 0x100 + 1 + 3
|
||||
const trackDataBytes = sectorsPerTrack * diskSectorBytes
|
||||
|
||||
var dos33SectorInterleaving [16]uint8
|
||||
var translateTable62 [0x40]uint8 // Conversion of a 6 bit byte to a 8 bit "disk" byte
|
||||
|
||||
type sector struct {
|
||||
data [0x100]uint8
|
||||
}
|
||||
|
||||
type track struct {
|
||||
sectors [sectorsPerTrack]sector
|
||||
}
|
||||
|
||||
type disk struct {
|
||||
tracks [tracksPerDisk]track
|
||||
}
|
||||
|
||||
var image disk // A loaded disk image
|
||||
var TrackData [trackDataBytes]uint8 // Converted image data as it it returned by the disk controller for a single track
|
||||
|
||||
func InitDiskImage() {
|
||||
// Map DOS 3.3 sector interleaving
|
||||
// Physical sector -> Logical sector
|
||||
dos33SectorInterleaving[0x0] = 0x0
|
||||
dos33SectorInterleaving[0x1] = 0x7
|
||||
dos33SectorInterleaving[0x2] = 0xe
|
||||
dos33SectorInterleaving[0x3] = 0x6
|
||||
dos33SectorInterleaving[0x4] = 0xd
|
||||
dos33SectorInterleaving[0x5] = 0x5
|
||||
dos33SectorInterleaving[0x6] = 0xc
|
||||
dos33SectorInterleaving[0x7] = 0x4
|
||||
dos33SectorInterleaving[0x8] = 0xb
|
||||
dos33SectorInterleaving[0x9] = 0x3
|
||||
dos33SectorInterleaving[0xa] = 0xa
|
||||
dos33SectorInterleaving[0xb] = 0x2
|
||||
dos33SectorInterleaving[0xc] = 0x9
|
||||
dos33SectorInterleaving[0xd] = 0x1
|
||||
dos33SectorInterleaving[0xe] = 0x8
|
||||
dos33SectorInterleaving[0xf] = 0xf
|
||||
|
||||
// Zero disk image data
|
||||
for t := 0; t < tracksPerDisk; t++ {
|
||||
for s := 0; s < sectorsPerTrack; s++ {
|
||||
for i := 0; i < 0x100; i++ {
|
||||
image.tracks[t].sectors[s].data[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a 6 bit "byte" to a 8 bit "disk" byte
|
||||
translateTable62 = [0x40]uint8{
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadDiskImage(path string) {
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to read ROM: %s", err))
|
||||
}
|
||||
|
||||
if len(bytes) != imageLength {
|
||||
panic(fmt.Sprintf("Disk image has invalid length %d, expected %d", len(bytes), imageLength))
|
||||
}
|
||||
|
||||
pos := 0
|
||||
for t := 0; t < tracksPerDisk; t++ {
|
||||
for s := 0; s < sectorsPerTrack; s++ {
|
||||
for i := 0; i < 0x100; i++ {
|
||||
image.tracks[t].sectors[s].data[i] = bytes[pos]
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode a byte into two 4-bit bytes with odd-even encoding. This is used
|
||||
// for the sector and data headers
|
||||
func oddEvenEncode(data uint8) (uint8, uint8) {
|
||||
bit0 := (data & 0x01) >> 0
|
||||
bit1 := (data & 0x02) >> 1
|
||||
bit2 := (data & 0x04) >> 2
|
||||
bit3 := (data & 0x08) >> 3
|
||||
bit4 := (data & 0x10) >> 4
|
||||
bit5 := (data & 0x20) >> 5
|
||||
bit6 := (data & 0x40) >> 6
|
||||
bit7 := (data & 0x80) >> 7
|
||||
|
||||
l := 0xaa | (bit7 << 6) | (bit5 << 4) | (bit3 << 2) | (bit1)
|
||||
h := 0xaa | (bit6 << 6) | (bit4 << 4) | (bit2 << 2) | (bit0)
|
||||
return l, h
|
||||
}
|
||||
|
||||
// Convert 8 bit bytes to 0x56 2-bit sections and 0x100 6 bit sections
|
||||
func makeSectorData(s sector) (data [0x56 + 0x100]uint8) {
|
||||
twoBitPos := 0x0
|
||||
for i := 0; i < 0x100; i++ {
|
||||
b := s.data[i]
|
||||
bit0 := b & 0x1
|
||||
bit1 := (b & 0x2) >> 1
|
||||
data[twoBitPos] = (data[twoBitPos] >> 2) | (bit0 << 5) | (bit1 << 4)
|
||||
data[i+0x56] = b >> 2
|
||||
|
||||
twoBitPos++
|
||||
if twoBitPos == 0x56 {
|
||||
twoBitPos = 0x0
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the bits for 2 remainders from the 256 divide by 3 are in the right place.
|
||||
data[0x54] = (data[0x54] >> 2)
|
||||
data[0x55] = (data[0x55] >> 2)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func clearTrackData() {
|
||||
for i := 0; i < trackDataBytes; i++ {
|
||||
TrackData[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func MakeTrackData(armPosition uint8) {
|
||||
// Tracks are present on even arm positions.
|
||||
track := armPosition / 2
|
||||
|
||||
// If it's an odd arm position or a track beyond the image, zero the data
|
||||
if (armPosition >= (tracksPerDisk * 2)) || ((armPosition % 2) == 1) {
|
||||
clearTrackData()
|
||||
return
|
||||
}
|
||||
|
||||
DriveState.BytePosition = 0 // Point the head at the first sector
|
||||
|
||||
// For each sector, encode the data and add it to TrackData
|
||||
for physicalSector := 0; physicalSector < sectorsPerTrack; physicalSector++ {
|
||||
logicalSector := dos33SectorInterleaving[physicalSector]
|
||||
offset := int(physicalSector) * diskSectorBytes
|
||||
|
||||
volume := uint8(254) // Volume numbers aren't implemented
|
||||
checksum := volume ^ track ^ uint8(physicalSector)
|
||||
|
||||
volL, volH := oddEvenEncode(volume)
|
||||
trL, trH := oddEvenEncode(track)
|
||||
seL, seH := oddEvenEncode(uint8(physicalSector))
|
||||
csL, csH := oddEvenEncode(checksum)
|
||||
|
||||
// Address field prologue
|
||||
TrackData[offset+0] = 0xd5
|
||||
TrackData[offset+1] = 0xaa
|
||||
TrackData[offset+2] = 0x96
|
||||
|
||||
// Volume, track, sector and checksum
|
||||
TrackData[offset+3] = volL
|
||||
TrackData[offset+4] = volH
|
||||
TrackData[offset+5] = trL
|
||||
TrackData[offset+6] = trH
|
||||
TrackData[offset+7] = seL
|
||||
TrackData[offset+8] = seH
|
||||
TrackData[offset+9] = csL
|
||||
TrackData[offset+10] = csH
|
||||
|
||||
// Address epilogue
|
||||
TrackData[offset+11] = 0xde
|
||||
TrackData[offset+12] = 0xaa
|
||||
TrackData[offset+13] = 0xeb
|
||||
|
||||
// Data field prologue
|
||||
TrackData[offset+14] = 0xd5
|
||||
TrackData[offset+15] = 0xaa
|
||||
TrackData[offset+16] = 0xad
|
||||
|
||||
sectorData := makeSectorData(image.tracks[track].sectors[logicalSector])
|
||||
|
||||
// a is the previous byte's value
|
||||
a := uint8(0)
|
||||
for i := 0; i < 0x56+0x100; i++ {
|
||||
a ^= sectorData[i]
|
||||
b := translateTable62[a]
|
||||
TrackData[offset+17+i] = b
|
||||
a = sectorData[i]
|
||||
}
|
||||
|
||||
// Set the checksum byte
|
||||
TrackData[offset+17+0x56+0x100] = translateTable62[a]
|
||||
|
||||
// Data epilogue
|
||||
TrackData[offset+17+0x56+0x100+1] = 0xde
|
||||
TrackData[offset+17+0x56+0x100+2] = 0xaa
|
||||
TrackData[offset+17+0x56+0x100+3] = 0xeb
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Read a byte from the disk head and spin the disk along
|
||||
func ReadTrackData() (result uint8) {
|
||||
result = TrackData[DriveState.BytePosition]
|
||||
|
||||
DriveState.BytePosition++
|
||||
if DriveState.BytePosition == trackDataBytes {
|
||||
DriveState.BytePosition = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
116
mmu/io.go
116
mmu/io.go
|
@ -5,7 +5,9 @@ import (
|
|||
"mos6502go/keyboard"
|
||||
)
|
||||
|
||||
// Adapted from
|
||||
// https://mirrors.apple2.org.za/apple.cabi.net/Languages.Programming/MemoryMap.IIe.64K.128K.txt
|
||||
// https://github.com/cmosher01/Apple-II-Platform/blob/master/asminclude/iorom.asm
|
||||
|
||||
const (
|
||||
KEYBOARD = 0xC000 // keyboard data (latched) (RD-only)
|
||||
|
@ -66,11 +68,61 @@ const (
|
|||
CLSAPPLE = 0xC062 // closed apple (option) key data
|
||||
|
||||
PDLTRIG = 0xC070 // trigger paddles
|
||||
|
||||
// Slot 6 Drive IO
|
||||
S6CLRDRVP0 = 0xC0E0 // stepper phase 0 (Q0)
|
||||
S6SETDRVP0 = 0xC0E1 //
|
||||
S6CLRDRVP1 = 0xC0E2 // stepper phase 1 (Q1)
|
||||
S6SETDRVP1 = 0xC0E3 //
|
||||
S6CLRDRVP2 = 0xC0E4 // stepper phase 2 (Q2)
|
||||
S6SETDRVP2 = 0xC0E5 //
|
||||
S6CLRDRVP3 = 0xC0E6 // stepper phase 3 (Q3)
|
||||
S6SETDRVP3 = 0xC0E7 //
|
||||
S6MOTOROFF = 0xC0E8 // drive motor (Q4)
|
||||
S6MOTORON = 0xC0E9 //
|
||||
S6SELDRV1 = 0xC0EA // drive select (Q5)
|
||||
S6SELDRV2 = 0xC0EB //
|
||||
S6Q6L = 0xC0EC // read (Q6)
|
||||
S6Q6H = 0xC0ED // WP sense
|
||||
S6Q7L = 0xC0EE // WP sense/read (Q7)
|
||||
S6Q7H = 0xC0EF // write
|
||||
)
|
||||
|
||||
var DriveState struct {
|
||||
Drive uint8
|
||||
Spinning bool
|
||||
Phase uint8
|
||||
ArmPosition uint8
|
||||
BytePosition int
|
||||
Q6 bool
|
||||
Q7 bool
|
||||
}
|
||||
|
||||
func InitIO() {
|
||||
// Empty slots that aren't yet implemented
|
||||
emptySlot(3)
|
||||
emptySlot(4)
|
||||
emptySlot(7)
|
||||
|
||||
// Initialize slot 6 drive
|
||||
DriveState.Drive = 1
|
||||
DriveState.Spinning = false
|
||||
DriveState.Phase = 0
|
||||
DriveState.ArmPosition = 0
|
||||
DriveState.BytePosition = 0
|
||||
DriveState.Q6 = false
|
||||
DriveState.Q7 = false
|
||||
|
||||
InitDiskImage()
|
||||
}
|
||||
|
||||
func driveIsreadSequencing() bool {
|
||||
return (!DriveState.Q6) && (!DriveState.Q7)
|
||||
}
|
||||
|
||||
// Handle soft switch addresses where both a read and a write has a side
|
||||
// effect and the return value is meaningless
|
||||
func readWrite(address uint16) bool {
|
||||
func readWrite(address uint16, isRead bool) bool {
|
||||
switch address {
|
||||
case CLRTEXT:
|
||||
panic("CLRTEXT not implemented")
|
||||
|
@ -81,19 +133,73 @@ func readWrite(address uint16) bool {
|
|||
case TXTPAGE2:
|
||||
return true
|
||||
fmt.Println("TXTPAGE2 not implemented")
|
||||
// panic("TXTPAGE2 not implemented")
|
||||
return true
|
||||
case CLRHIRES:
|
||||
return true
|
||||
case SETHIRES:
|
||||
panic("SETIRES not implemented")
|
||||
|
||||
// Drive stepper motor phase change
|
||||
case S6CLRDRVP0, S6SETDRVP0, S6CLRDRVP1, S6SETDRVP1, S6CLRDRVP2, S6SETDRVP2, S6CLRDRVP3, S6SETDRVP3:
|
||||
if ((address - S6CLRDRVP0) % 2) == 1 {
|
||||
// When the magnet coil is energized, move the arm by half a track
|
||||
phase := int8(address-S6CLRDRVP0) / 2
|
||||
change := int8(DriveState.Phase) - phase
|
||||
if change < 0 {
|
||||
change += 4
|
||||
}
|
||||
|
||||
if change == 1 { // Inward
|
||||
if DriveState.ArmPosition > 0 {
|
||||
DriveState.ArmPosition--
|
||||
}
|
||||
} else if change == 3 { // Outward
|
||||
if DriveState.ArmPosition < 79 {
|
||||
DriveState.ArmPosition++
|
||||
}
|
||||
}
|
||||
|
||||
DriveState.Phase = uint8(phase)
|
||||
MakeTrackData(DriveState.ArmPosition)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
case S6MOTOROFF:
|
||||
DriveState.Spinning = false
|
||||
return true
|
||||
case S6MOTORON:
|
||||
DriveState.Spinning = true
|
||||
return true
|
||||
case S6SELDRV1:
|
||||
DriveState.Drive = 1
|
||||
return true
|
||||
case S6SELDRV2:
|
||||
DriveState.Drive = 2
|
||||
return true
|
||||
case S6Q6L:
|
||||
if !isRead {
|
||||
DriveState.Q6 = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case S6Q6H:
|
||||
DriveState.Q6 = true
|
||||
return true
|
||||
case S6Q7L:
|
||||
DriveState.Q7 = false
|
||||
return true
|
||||
case S6Q7H:
|
||||
DriveState.Q7 = true
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ReadIO(address uint16) uint8 {
|
||||
if readWrite(address) {
|
||||
if readWrite(address, true) {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -136,6 +242,8 @@ func ReadIO(address uint16) uint8 {
|
|||
case SPEAKER:
|
||||
// Speaker not implemented
|
||||
// Not printing anything since this will generate a lot of noise
|
||||
case S6Q6L:
|
||||
return ReadTrackData()
|
||||
default:
|
||||
panic(fmt.Sprintf("TODO read %04x\n", address))
|
||||
}
|
||||
|
@ -144,7 +252,7 @@ func ReadIO(address uint16) uint8 {
|
|||
}
|
||||
|
||||
func WriteIO(address uint16, value uint8) {
|
||||
if readWrite(address) {
|
||||
if readWrite(address, false) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -60,12 +60,6 @@ func readApple2eROM() {
|
|||
for i := 0x0; i < 0x3000; i++ {
|
||||
PhysicalMemory.UpperROM[i] = bytes[i+0x1000]
|
||||
}
|
||||
|
||||
// Empty slots that aren't yet implemented
|
||||
emptySlot(3)
|
||||
emptySlot(4)
|
||||
emptySlot(6)
|
||||
emptySlot(7)
|
||||
}
|
||||
|
||||
func InitApple2eROM() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user