From 36faa6e9060d5ce96caaf28bf843ea611eece84f Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sat, 27 Jan 2024 17:20:53 +0100 Subject: [PATCH] Original BrainBoard card --- cardBrainBoard.go | 165 +++++++++++++++++++++++++++++++++ cardBrainBoard_test.go | 50 ++++++++++ resources/wozaniam_integer.rom | Bin 0 -> 32768 bytes screen/textToString.go | 2 + 4 files changed, 217 insertions(+) create mode 100644 cardBrainBoard.go create mode 100644 cardBrainBoard_test.go create mode 100644 resources/wozaniam_integer.rom diff --git a/cardBrainBoard.go b/cardBrainBoard.go new file mode 100644 index 0000000..d47a93e --- /dev/null +++ b/cardBrainBoard.go @@ -0,0 +1,165 @@ +package izapple2 + +import "fmt" + +/* + Brain board card for Apple II + +See: + http://www.willegal.net/appleii/brainboard.htm + http://www.willegal.net/appleii/bb-v5_3.1.pdf + +The Brain Board card has 2 banks of ROM to replace the main ROM + +A 27c256 ROM (32k) is used, with the following mapping: + 0x0000-0x00ff: lower card rom maps to $Csxx + 0x1000-0x37ff: lower bank rom maps to $D000 to $F7FF + 0x3800-0x3fff: F8 lower bank rom maps to $F800 to $FFFF + 0x4000-0x40ff: upper card rom maps to $Csxx + 0x5000-0x77ff: upper bank rom maps to $D000 to $F7FF + 0x7800-0x7fff: F8 upper bank rom maps to $F800 to $FFFF + +DIP SWitches_ + 1-ON : The range F8 can be replaced + 1-OFF: The range F8 is not replaced + + 3-ON ,4-OFF: The motherboard ROM can be used + 3-OFF,4-ON : The motherboard ROM is always replaced + + 5-ON ,6-OFF: The lower bank is used and mapped to Bank A + 5-OFF,6-ON : The lower bank is not used (A can be motherboards or uppers) + + 7-ON ,8-OFF: The upper bank is used and mapper to bank B + 7-OFF,8-ON : The upper bank is not used (B can be motherboards or lowers) + + +Switches and softswitches: + Up, $COsO - SS clear: A bank selected + Down, $COs1 - SS set: B bank selected + +*/ + +// CardBrainBoard represents a Brain Board card +type CardBrainBoard struct { + cardBase + isBankB bool + isMotherboardRomEnabled bool + + dip1_replaceF8 bool + dip34_useMotherboardRom bool + dip56_lowerInA bool + dip78_upperInB bool + + rom []uint8 +} + +func newCardBrainBoardBuilder() *cardBuilder { + return &cardBuilder{ + name: "Brain Board", + description: "Firmware card for Apple. It has two ROM banks", + defaultParams: &[]paramSpec{ + {"rom", "ROM file to load", "/wozaniam_integer.rom"}, + {"dips", "DIP switches, leftmost is DIP 1", "1-011010"}, + {"switch", "Bank selected at boot, 'up' or 'down'", "up"}, + }, + + buildFunc: func(params map[string]string) (Card, error) { + var c CardBrainBoard + var err error + + bank := paramsGetString(params, "switch") + if bank == "up" { + c.isBankB = false + } else if bank == "down" { + c.isBankB = true + } else { + return nil, fmt.Errorf("Invalid bank '%s', must be up or down", bank) + } + + dips, err := paramsGetDIPs(params, "dips", 8) + if err != nil { + return nil, err + } + c.dip1_replaceF8 = dips[1] + if dips[3] == dips[4] { + return nil, fmt.Errorf("DIP switches 3 and 4 must be different") + } + if dips[5] == dips[6] { + return nil, fmt.Errorf("DIP switches 5 and 6 must be different") + } + if dips[7] == dips[8] { + return nil, fmt.Errorf("DIP switches 7 and 8 must be different") + } + + c.dip34_useMotherboardRom = dips[3] + c.dip56_lowerInA = dips[5] + c.dip78_upperInB = dips[7] + + romFile := paramsGetPath(params, "rom") + data, _, err := LoadResource(romFile) + if err != nil { + return nil, err + } + if len(data) != 0x8000 { + return nil, fmt.Errorf("the ROM file for the Brainboard must be 32k") + } + + c.isMotherboardRomEnabled = true + c.rom = data + c.romCxxx = &c + return &c, nil + }, + } +} + +func (c *CardBrainBoard) updateState() { + isMotherboardRomEnabled := c.dip34_useMotherboardRom && + ((!c.dip56_lowerInA && !c.isBankB) || (!c.dip78_upperInB && c.isBankB)) + + if isMotherboardRomEnabled && !c.isMotherboardRomEnabled { + fmt.Print("ROM: main") + c.a.mmu.inhibitROM(nil) + } else if !isMotherboardRomEnabled && c.isMotherboardRomEnabled { + fmt.Print("ROM: brain") + c.a.mmu.inhibitROM(c) + } + + c.isMotherboardRomEnabled = isMotherboardRomEnabled +} + +func (c *CardBrainBoard) assign(a *Apple2, slot int) { + c.addCardSoftSwitchRW(0, func() uint8 { + c.isBankB = false + c.updateState() + return 0x55 + }, "BRAINCLEAR") + + c.addCardSoftSwitchRW(1, func() uint8 { + c.isBankB = true + c.updateState() + return 0x55 + }, "BRAINSET") + + c.cardBase.assign(a, slot) + c.updateState() +} + +func (c *CardBrainBoard) translateAddress(address uint16) uint16 { + if c.isBankB { + return address - 0xc000 + 0x4000 + } else { + return address - 0xc000 + } +} + +func (c *CardBrainBoard) peek(address uint16) uint8 { + return c.rom[c.translateAddress(address)] +} + +func (c *CardBrainBoard) poke(address uint16, value uint8) { + // Nothing +} + +func (c *CardBrainBoard) setBase(base uint16) { + // Nothing +} diff --git a/cardBrainBoard_test.go b/cardBrainBoard_test.go new file mode 100644 index 0000000..3eb6ff0 --- /dev/null +++ b/cardBrainBoard_test.go @@ -0,0 +1,50 @@ +package izapple2 + +import ( + "strings" + "testing" +) + +func buildBrainBoardTester(t *testing.T, conf string) *apple2Tester { + overrides := newConfiguration() + overrides.set(confS2, conf) + overrides.set(confS3, "empty") + overrides.set(confS4, "empty") + overrides.set(confS5, "empty") + overrides.set(confS6, "empty") + overrides.set(confS7, "empty") + + at, err := makeApple2Tester("2plus", overrides) + if err != nil { + t.Fatal(err) + } + return at +} + +func TestBrainBoardCardWozaniam(t *testing.T) { + at := buildBrainBoardTester(t, "brainboard,switch=up") + + at.terminateCondition = func(a *Apple2) bool { + return a.cpu.GetCycles() > 10_000_000 + } + at.run() + + text := at.getText() + if !strings.Contains(text, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@") { + t.Errorf("Expected screen filled with _@_@', got '%s'", text) + } +} + +func TestBrainBoardCardIntegerBasic(t *testing.T) { + at := buildBrainBoardTester(t, "brainboard,switch=down") + + at.terminateCondition = func(a *Apple2) bool { + return a.cpu.GetCycles() > 10_000_000 + } + at.run() + + text := at.getText() + if !strings.Contains(text, "APPLE ][\n>") { + t.Errorf("Expected APPLE ][' and '>', got '%s'", text) + } +} diff --git a/resources/wozaniam_integer.rom b/resources/wozaniam_integer.rom new file mode 100644 index 0000000000000000000000000000000000000000..18599cfe524d0755d9dd8cdfe71d98ecaf9f7d57 GIT binary patch literal 32768 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 F{{RwT2kHO- literal 0 HcmV?d00001 diff --git a/screen/textToString.go b/screen/textToString.go index d3e39d5..2c929e8 100644 --- a/screen/textToString.go +++ b/screen/textToString.go @@ -2,6 +2,7 @@ package screen import ( "fmt" + "strings" ) // RenderTextModeString returns the text mode contents ignoring reverse and flash @@ -22,6 +23,7 @@ func RenderTextModeString(vs VideoSource, is80Columns bool, isSecondPage bool, i char := text[l*columns+c] line += textMemoryByteToString(char, isAltText, isApple2e, false) } + line = strings.TrimRight(line, " ") content += fmt.Sprintf("%v\n", line) } return content