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 0000000..18599cf Binary files /dev/null and b/resources/wozaniam_integer.rom differ 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