Skip to content

Commit

Permalink
Partial support for the Basis 108 clone
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanizag committed Jul 28, 2024
1 parent bd70722 commit 1938b90
Show file tree
Hide file tree
Showing 44 changed files with 528 additions and 219 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ frontend/*/*.dsk
frontend/*/*.po
frontend/*/*.2mg
frontend/*/*.hdv
frontend/*/*.zip
frontend/a2fyne/a2fyne
frontend/headless/headless
frontend/*/snapshot.gif
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- Apple //e with 128Kb of RAM
- Apple //e enhanced with 128Kb of RAM
- Base64A clone with 48Kb of base RAM and paged ROM
- Basis 108 clone (partial)
- Storage
- 16 Sector 5 1/4 diskettes. Uncompressed or compressed witth gzip or zip. Supported formats:
- NIB (read only)
Expand Down Expand Up @@ -228,6 +229,7 @@ The available pre-configured models are:
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
basis108: Basis 108
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
swyft: swyft
Expand Down
4 changes: 2 additions & 2 deletions a2audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func testA2AuditInternal(t *testing.T, model string, removeLangCard bool, cycles
if err != nil {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionTexts(at, messages, false, cycles)
at.terminateCondition = buildTerminateConditionTexts(messages, testTextMode40, cycles)
at.run()

text := at.getText()
text := at.getText(testTextMode40)
for _, message := range messages {
if !strings.Contains(text, message) {
t.Errorf("Expected '%s', got '%s'", message, text)
Expand Down
10 changes: 10 additions & 0 deletions apple2.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"sync/atomic"

"github.com/ivanizag/iz6502"
"github.com/ivanizag/izapple2/screen"
)

// Apple2 represents all the components and state of the emulated machine
Expand All @@ -12,6 +13,7 @@ type Apple2 struct {
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
video screen.VideoSource
cg *CharacterGenerator
cards [8]Card
tracers []executionTracer
Expand Down Expand Up @@ -88,6 +90,10 @@ func (a *Apple2) IsForceCaps() bool {
return a.forceCaps
}

func (a *Apple2) GetCgPageInfo() (int, int) {
return a.cg.getPage(), a.cg.getPages()
}

func (a *Apple2) RequestFastMode() {
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
atomic.AddInt32(&a.fastRequestsCounter, 1)
Expand All @@ -100,3 +106,7 @@ func (a *Apple2) ReleaseFastMode() {
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}

func (a *Apple2) GetVideoSource() screen.VideoSource {
return a.video
}
6 changes: 3 additions & 3 deletions apple2Run.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (a *Apple2) executionTrace() {

func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
pageZeroSymbols := map[uint16]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
Expand All @@ -145,8 +145,8 @@ func (a *Apple2) dumpDebugInfo() {
0xef: "JVAFOLDH", // Apple Pascal
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.data[k]
for _, k := range []uint16{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.peek(k)
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}

Expand Down
31 changes: 18 additions & 13 deletions apple2Tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,22 @@ func (at *apple2Tester) run() {
at.a.Run()
}

func (at *apple2Tester) getText() string {
return screen.RenderTextModeString(at.a, false, false, false, at.a.isApple2e)
type testTextModeFunc func(a *Apple2) string

var testTextMode40 testTextModeFunc = func(a *Apple2) string {
return screen.RenderTextModeString(a.video, false, false, false, a.hasLowerCase, false)
}

var testTextMode80 testTextModeFunc = func(a *Apple2) string {
return screen.RenderTextModeString(a.video, true, false, false, a.hasLowerCase, false)
}

func (at *apple2Tester) getText80() string {
return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e)
var testTextMode80AltOrder testTextModeFunc = func(a *Apple2) string {
return screen.RenderTextModeString(a.video, true, false, false, a.hasLowerCase, true)
}

func (at *apple2Tester) getText(textMode testTextModeFunc) string {
return textMode(at.a)
}

/*
Expand All @@ -66,12 +76,12 @@ func (at *apple2Tester) getText80() string {

const textCheckInterval = uint64(100_000)

func buildTerminateConditionText(at *apple2Tester, needle string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
func buildTerminateConditionText(needle string, textMode testTextModeFunc, timeoutCycles uint64) terminateConditionFunc {
needles := []string{needle}
return buildTerminateConditionTexts(at, needles, col80, timeoutCycles)
return buildTerminateConditionTexts(needles, textMode, timeoutCycles)
}

func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
func buildTerminateConditionTexts(needles []string, textMode testTextModeFunc, timeoutCycles uint64) terminateConditionFunc {
lastCheck := uint64(0)
found := false
return func(a *Apple2) bool {
Expand All @@ -81,12 +91,7 @@ func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool
}
if cycles-lastCheck > textCheckInterval {
lastCheck = cycles
var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
text := textMode(a)
for _, needle := range needles {
if !strings.Contains(text, needle) {
return false
Expand Down
39 changes: 8 additions & 31 deletions base64a.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package izapple2

import "fmt"

/*
Copam BASE64A adaptation.
*/
Expand All @@ -16,35 +14,14 @@ const (
)

func loadBase64aRom(a *Apple2) error {
// Load the 6 PROM dumps
romBanksBytes := make([][]uint8, base64aRomBankCount)
for j := range romBanksBytes {
romBanksBytes[j] = make([]uint8, 0, base64aRomBankSize)
}

for i := 0; i < base64aRomChipCount; i++ {
filename := fmt.Sprintf("<internal>/BASE64A_%X.BIN", 0xd0+i*0x08)
data, _, err := LoadResource(filename)
if err != nil {
return err
}
for j := range romBanksBytes {
start := (j * base64aRomWindowSize) % len(data)
romBanksBytes[j] = append(romBanksBytes[j], data[start:start+base64aRomWindowSize]...)
}
}

// Create paged ROM
romData := make([]uint8, 0, base64aRomBankSize*base64aRomBankCount)
for _, bank := range romBanksBytes {
romData = append(romData, bank...)
}
rom := newMemoryRangePagedROM(0xd000, romData, "Base64 ROM", base64aRomBankCount)

// Start with first bank active
rom.setPage(0)
a.mmu.physicalROM = rom
return nil
return loadMultiPageRom(a, []string{
"<internal>/BASE64A_D0.BIN",
"<internal>/BASE64A_D8.BIN",
"<internal>/BASE64A_E0.BIN",
"<internal>/BASE64A_E8.BIN",
"<internal>/BASE64A_F0.BIN",
"<internal>/BASE64A_F8.BIN",
})
}

func addBase64aSoftSwitches(io *ioC0Page) {
Expand Down
126 changes: 126 additions & 0 deletions boardBasis108.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package izapple2

import "github.com/ivanizag/izapple2/screen"

/*
Basis 108 clone
Manual: https://www.applefritter.com/files/Basis%201982%20basis%20108%20instruction%20manual.pdf
ROM: Two pages of 12 KB each. Page 0 sets the 80 column mode. Page 1 starts in 40 column mode.
Character ROM: Four pages, the inverse and flash characters are built from the normal ones. Pages:
0: Apple II characters (no lowercase)
1: German ASCII
2: ASCII (default)
3: APL symbols
Memory: Has a full 64KB extra RAM replacing both main and LC RAM. It can be mapped on 8kb blocks with
new softswitches.
Video: 80 columns are made by having a sideways static RAM.
The keyboard can generate interrupts.
Missing: second 64kb block, keyboard interrupts, Z80 emulation, Parallel an Serial.
*/

func loadBasis108Rom(a *Apple2) error {
return loadMultiPageRom(a, []string{
"<internal>/Basis108_D83_D0.BIN",
"<internal>/Basis108_D70_D8.BIN",
"<internal>/Basis108_D56_E0.BIN",
"<internal>/Basis108_D40_E8.BIN",
"<internal>/Basis108_D39_F0.BIN",
"<internal>/Basis108_D25_F8.BIN",
})
}

type videoBasis108 struct {
video
ram *memoryRangeBasis108
col80 bool
}

func newVideoBasis108(a *Apple2, ram *memoryRangeBasis108) *videoBasis108 {
var v videoBasis108
v.video = *newVideo(a)
v.ram = ram
return &v
}

// GetCurrentVideoMode returns the active video mode
func (v *videoBasis108) GetCurrentVideoMode() uint32 {
if v.col80 {
mode := screen.VideoText80AltOrder

isTextMode := v.a.io.isSoftSwitchActive(ioFlagText)
isHiResMode := v.a.io.isSoftSwitchActive(ioFlagHiRes)
if isTextMode {
mode |= screen.VideoText80
} else if isHiResMode {
mode |= screen.VideoHGR
} else {
mode |= screen.VideoDGR
}

isSecondPage := v.a.io.isSoftSwitchActive(ioFlagSecondPage)
if isSecondPage {
mode |= screen.VideoSecondPage
}

return mode
}

return v.video.GetCurrentVideoMode()
}

// GetTextMemory returns a slice to the text memory pages
func (v *videoBasis108) GetTextMemory(secondPage bool, ext bool) []uint8 {
return v.ram.getTextMemory(secondPage, ext)
}

func addBasis108SoftSwitches(io *ioC0Page, ram *memoryRangeBasis108, video *videoBasis108, cg *CharacterGenerator) {

// Character generator softswitches
io.addSoftSwitchW(0x00, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-OFF") // Inverse?
io.addSoftSwitchW(0x01, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-ON") // Flash?
io.addSoftSwitchW(0x02, func(_ uint8) { cg.setPage(cg.page & 0x02) }, "BASIS108-CG-SW2-OFF")
io.addSoftSwitchW(0x03, func(_ uint8) { cg.setPage(cg.page | 0x01) }, "BASIS108-CG-SW2-ON")
io.addSoftSwitchW(0x04, func(_ uint8) { cg.setPage(cg.page & 0x01) }, "BASIS108-CG-SW1-OFF")
io.addSoftSwitchW(0x05, func(_ uint8) { cg.setPage(cg.page | 0x02) }, "BASIS108-CG-SW1-ON")
io.addSoftSwitchW(0x06, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-OFF")
io.addSoftSwitchW(0x07, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-ON")

// Keyboard interrupts
io.addSoftSwitchW(0x08, buildNotImplementedSoftSwitchW(io), "BASIS108-KBDINT-OFF")
io.addSoftSwitchW(0x09, buildNotImplementedSoftSwitchW(io), "BASIS108-KBDINT-ON")

// 80 column softswitches
io.addSoftSwitchW(0x0A, func(_ uint8) { video.col80 = false }, "BASIS108-80COL-OFF")
io.addSoftSwitchW(0x0B, func(_ uint8) { video.col80 = true }, "BASIS108-80COL-ON")
io.addSoftSwitchW(0x0C, func(_ uint8) { ram.staticRam = false }, "BASIS108-STATICRAM-OFF")
io.addSoftSwitchW(0x0D, func(_ uint8) { ram.staticRam = true }, "BASIS108-STATICRAM-ON")

// Language card configuration
io.addSoftSwitchW(0x0E, buildNotImplementedSoftSwitchW(io), "BASIS108-LANG-ON")
io.addSoftSwitchW(0x0F, buildNotImplementedSoftSwitchW(io), "BASIS108-LANG-OFF")

// RAM bank softswitches
io.addSoftSwitchW(0x60, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM0000-BANK0")
io.addSoftSwitchW(0x61, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM0000-BANK1")
io.addSoftSwitchW(0x62, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM2000-BANK0")
io.addSoftSwitchW(0x63, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM2000-BANK1")
io.addSoftSwitchW(0x64, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM4000-BANK0")
io.addSoftSwitchW(0x65, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM4000-BANK1")
io.addSoftSwitchW(0x66, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM6000-BANK0")
io.addSoftSwitchW(0x67, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM6000-BANK1")
io.addSoftSwitchW(0x68, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM8000-BANK0")
io.addSoftSwitchW(0x69, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM8000-BANK1")
io.addSoftSwitchW(0x6A, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMA000-BANK0")
io.addSoftSwitchW(0x6B, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMA000-BANK1")
io.addSoftSwitchW(0x6C, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMD000-BANK0")
io.addSoftSwitchW(0x6D, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMD000-BANK1")
io.addSoftSwitchW(0x6E, buildNotImplementedSoftSwitchW(io), "BASIS108-RAME000-BANK0")
io.addSoftSwitchW(0x6F, buildNotImplementedSoftSwitchW(io), "BASIS108-RAME000-BANK1")
}
8 changes: 4 additions & 4 deletions cardBrainBoard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
}
at.run()

at.terminateCondition = buildTerminateConditionText(at, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@", false, 100_000)
at.terminateCondition = buildTerminateConditionText("_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@", testTextMode40, 100_000)

text := at.getText()
text := at.getText(testTextMode40)
if !strings.Contains(text, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@") {
t.Errorf("Expected screen filled with _@_@', got '%s'", text)
}
Expand All @@ -40,10 +40,10 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
func TestBrainBoardCardIntegerBasic(t *testing.T) {
at := buildBrainBoardTester(t, "brainboard,switch=down")

at.terminateCondition = buildTerminateConditionText(at, "APPLE ][\n>", false, 1_000_000)
at.terminateCondition = buildTerminateConditionText("APPLE ][\n>", testTextMode40, 1_000_000)
at.run()

text := at.getText()
text := at.getText(testTextMode40)
if !strings.Contains(text, "APPLE ][\n>") {
t.Errorf("Expected APPLE ][' and '>', got '%s'", text)
}
Expand Down
4 changes: 2 additions & 2 deletions cardCat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
if err != nil {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
at.terminateCondition = buildTerminateConditionText(banner, testTextMode80, cycles)
at.run()

text := at.getText80()
text := at.getText(testTextMode80)
if !strings.Contains(text, banner) {
t.Errorf("Expected '%s', got '%s'", banner, text)
}
Expand Down
2 changes: 1 addition & 1 deletion cardDan2Controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (s *cardDan2ControllerSlot) openFile() (*os.File, error) {
return nil, err
}

func (s *cardDan2ControllerSlot) status(unit uint8) error {
func (s *cardDan2ControllerSlot) status(_ uint8) error {
file, err := s.openFile()
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions cardDan2Controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ func TestDan2Controller(t *testing.T) {
t.Fatal(err)
}

at.terminateCondition = buildTerminateConditionText(at, "NEW VOL", true, 10_000_000)
at.terminateCondition = buildTerminateConditionText("NEW VOL", testTextMode40, 10_000_000)

at.run()

text := at.getText()
text := at.getText(testTextMode40)
if !strings.Contains(text, "NEW VOL") {
t.Errorf("Expected Bitsy Bye screen, got '%s'", text)
}
Expand Down
Loading

0 comments on commit 1938b90

Please sign in to comment.