diff --git a/README.md b/README.md index 03f6ab1..cdadd51 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Portable emulator of an Apple II+ or //e. Written in Go. - DiskII controller - 16Kb Language Card - 256Kb Saturn RAM - - 1Mb Memory Expansion Card + - 1Mb Memory Expansion Card (slinky) + - RAMWorks style expansion Card (up to 16MB additional) (Apple //e only) - ThunderClock Plus real time clock - Bootable hard disk card - Apple //e 80 columns with 64Kb extra RAM and optional RGB modes @@ -34,8 +35,9 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Double-Width High-Resolution graphics (Apple //e only) - Super High Resolution (VidHD only) - Mixed mode - - RGB card text 40 columns with 16 colors for foreground and background + - RGB card text 40 columns with 16 colors for foreground and background (mixable) - RGB card mode 11, mono 560x192 + - RGB card mode 12, ntsc 160*192 - RGB card mode 13, ntsc 140*192 (regular DHGR) - RGB card mode 14, mix of modes 11 and 13 on the fly - Displays: @@ -61,7 +63,7 @@ No installation required. [Download](https://github.com/ivanizag/apple2/releases Execute without parameters to have an emulated Apple //e Enhanced with 128kb booting DOS 3.3 ready to run Applesoft: -``` shell +``` terminal casa@servidor:~$ ./apple2sdl ``` @@ -71,7 +73,7 @@ casa@servidor:~$ ./apple2sdl Download a DSK or WOZ file or use an URL ([Asimov](https://www.apple.asimov.net/images/) is an excellent source) with the `-disk` parameter: -``` shell +``` terminal casa@servidor:~$ ./apple2sdl -disk "https://www.apple.asimov.net/images/games/action/karateka/karateka (includes intro).dsk" ``` @@ -82,7 +84,7 @@ casa@servidor:~$ ./apple2sdl -disk "https://www.apple.asimov.net/images/games/ac Download the excellent [Total Replay](https://archive.org/details/TotalReplay) compilation by [a2-4am](https://github.com/a2-4am/4cade). Run it with the `-hd` parameter: -``` shell +``` terminal casa@servidor:~$ ./apple2sdl -hd "Total Replay v3.0.2mg" ``` @@ -94,7 +96,7 @@ Displays super hi-res box art as seen with the VidHD card. To run text mode right on the terminal without the SDL2 dependency, use `apple2console`. It runs on the console using ANSI escape codes. Input is sent to the emulated Apple II one line at a time: -``` shell +``` terminal casa@servidor:~$ ./apple2console -model 2plus ############################################ @@ -145,7 +147,7 @@ Only valid on SDL mode ### Command line options -``` shell +```terminal -charRom string rom file for the character generator (default "") -disk string @@ -180,6 +182,8 @@ Only valid on SDL mode panic if a not implemented softswitch is used -profile generate profile trace to analyse with pprof + -ramworks int + memory to use with RAMWorks card, 0 for no card, max is 16384 (default 8192) -rgb emulate the RGB modes of the 80col RGB card for DHGR (default true) -rom string @@ -212,7 +216,7 @@ The only dependency is having a working Go installation on any platform. Run: -``` shell +``` terminal go get github.com/ivanizag/apple2/apple2console go build github.com/ivanizag/apple2/apple2console ``` @@ -223,7 +227,7 @@ Besides having a working Go installation, install the SDL2 developer files. Vali Run: -``` shell +``` terminal go get github.com/ivanizag/apple2/apple2sdl go build github.com/ivanizag/apple2/apple2sdl ``` @@ -232,7 +236,7 @@ go build github.com/ivanizag/apple2/apple2sdl To create executables for Linux and Windows without installing Go, SDL2 or the Windows cross compilation toosl, run: -``` shell +``` terminal cd docker ./build.sh ``` diff --git a/apple2Setup.go b/apple2Setup.go index 030f9f5..e3c3089 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -24,7 +24,7 @@ func newApple2e() *Apple2 { a.mmu = newMemoryManager(&a) a.cpu = core6502.NewNMOS6502(a.mmu) a.io = newIoC0Page(&a) - a.mmu.InitRAMalt() + a.mmu.initExtendedRAM(1) addApple2SoftSwitches(a.io) addApple2ESoftSwitches(a.io) @@ -37,7 +37,7 @@ func newApple2eEnhanced() *Apple2 { a.mmu = newMemoryManager(&a) a.cpu = core6502.NewCMOS65c02(a.mmu) a.io = newIoC0Page(&a) - a.mmu.InitRAMalt() + a.mmu.initExtendedRAM(1) addApple2SoftSwitches(a.io) addApple2ESoftSwitches(a.io) @@ -194,6 +194,10 @@ func (a *Apple2) AddRGBCard() { setupRGBCard(a) } +func (a *Apple2) AddRAMWorks(banks int) { + setupRAMWorksCard(a, banks) +} + // AddCardLogger inserts a fake card that logs accesses func (a *Apple2) AddCardLogger(slot int) { a.insertCard(&cardLogger{}, slot) diff --git a/apple2main.go b/apple2main.go index 080a80a..d28e5ca 100644 --- a/apple2main.go +++ b/apple2main.go @@ -69,6 +69,10 @@ func MainApple() *Apple2 { "memoryExpSlot", 4, "slot for the Memory Expansion card with 1GB. -1 for none") + ramWorksKb := flag.Int( + "ramworks", + 8192, + "memory to use with RAMWorks card, 0 for no card, max is 16384") thunderClockCardSlot := flag.Int( "thunderClockCardSlot", 5, @@ -253,6 +257,13 @@ func MainApple() *Apple2 { } } + if *ramWorksKb != 0 { + if *ramWorksKb%64 != 0 { + panic("Ramworks size must be a multiple of 64") + } + a.AddRAMWorks(*ramWorksKb / 64) + } + if *rgbCard { a.AddRGBCard() } diff --git a/cardRGB.go b/cardRGB.go index 0c7518c..37e47f9 100644 --- a/cardRGB.go +++ b/cardRGB.go @@ -22,7 +22,7 @@ Modes by RGB flags 1 and 2: 0-0: 560*192 mono 1-1: 140*192 ntsc 0-1: Mixed mode - 1-0: 160*192 ntsc (not supported) + 1-0: 160*192 ntsc */ diff --git a/cardRamWorks.go b/cardRamWorks.go new file mode 100644 index 0000000..b0d4b4b --- /dev/null +++ b/cardRamWorks.go @@ -0,0 +1,34 @@ +package apple2 + +/* +RAMWorks style card on the Apple IIe aus slot. + https://patents.google.com/patent/US4601018 + https://ae.applearchives.com/apple_e/ramworks_iii/ramworks_iii_basic_manual_1.pdf + +Diagnostics disks: + https://ae.applearchives.com/apple_e/ramworks_iii/ramworks_diagnostics.zip + +It's is like the extra 64kb on an Apple IIe 80col 64kb card, but with up to 256 banks +*/ + +func setupRAMWorksCard(a *Apple2, banks int) { + a.mmu.initExtendedRAM(banks) + + ssr := func(_ *ioC0Page) uint8 { + return a.mmu.extendedRAMBlock + } + + ssw := func(_ *ioC0Page, value uint8) { + a.mmu.setExtendedRAMActiveBlock(value) + } + + // Does not have a slot assigned + a.io.addSoftSwitchR(0x71, ssr, "RAMWORKSR") + a.io.addSoftSwitchR(0x73, ssr, "RAMWORKSR") + a.io.addSoftSwitchR(0x75, ssr, "RAMWORKSR") + a.io.addSoftSwitchR(0x77, ssr, "RAMWORKSR") + a.io.addSoftSwitchW(0x71, ssw, "RAMWORKSW") + a.io.addSoftSwitchW(0x73, ssw, "RAMWORKSW") + a.io.addSoftSwitchW(0x75, ssw, "RAMWORKSW") + a.io.addSoftSwitchW(0x77, ssw, "RAMWORKSW") +} diff --git a/cardSaturn.go b/cardSaturn.go index 2855ac6..5bb413e 100644 --- a/cardSaturn.go +++ b/cardSaturn.go @@ -114,7 +114,7 @@ func (c *cardSaturn) ssAction(ss uint8) { } func (c *cardSaturn) applyState() { - c.a.mmu.setLanguageRAMBlock(c.activeBlock) + c.a.mmu.setLanguageRAMActiveBlock(c.activeBlock) c.a.mmu.setLanguageRAM(c.readState, c.writeState == lcWriteEnabled, c.altBank) } diff --git a/memoryManager.go b/memoryManager.go index fa13713..89a6007 100644 --- a/memoryManager.go +++ b/memoryManager.go @@ -12,19 +12,23 @@ type memoryManager struct { apple2 *Apple2 // Main RAM area: 0x0000 to 0xbfff - physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb - physicalMainRAMAlt *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb. Additional + physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb // Slots area: 0xc000 to 0xcfff - cardsROM [8]memoryHandler //0xcs00 to 0xcsff. 256 bytes for each card + cardsROM [8]memoryHandler //0xcs00 to 0xcSff. 256 bytes for each card cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. 2048 bytes for each card physicalROMe memoryHandler // 0xc100 to 0xcfff, Zero or 4kb in the Apple2e - // Upper area: 0xd000 to 0xffff - physicalROM [4]memoryHandler // 0xd000 to 0xffff, 12 Kb. Up to four banks - physicalDRAM []memoryHandler // 0xd000 to 0xdfff, 4KB. Up to 8 banks. - physicalDAltRAM []memoryHandler // 0xd000 to 0xdfff, 4KB. Up to 8 banks. - physicalEFRAM []memoryHandler // 0xe000 to 0xffff, 8KB. Up to 8 banks. + // Upper area ROM: 0xd000 to 0xffff + physicalROM [4]memoryHandler // 0xd000 to 0xffff, 12 Kb. Up to four + + // Language card upper area RAM: 0xd000 to 0xffff. One bank for regular LC cards, up to 8 with Saturn + physicalLangRAM []*memoryRange // 0xd000 to 0xffff, 12KB. Up to 8 banks. + physicalLangAltRAM []*memoryRange // 0xd000 to 0xdfff, 4KB. Up to 8 banks. + + // Extended RAM: 0x0000 to 0xffff (with 4Kb moved from 0xc000 to 0xd000 alt). One bank for extended Apple 2e card, up to 256 with RamWorks + physicalExtRAM []*memoryRange // 0x0000 to 0xffff. 60Kb, 0xc000 to 0xcfff not used. Up to 256 banks + physicalExtAltRAM []*memoryRange // 0xd000 to 0xdfff, 4Kb. Up to 256 banks. // Configuration switches, Language cards lcSelectedBlock uint8 // Language card block selected. Usually, allways 0. But Saturn has 8 @@ -40,6 +44,7 @@ type memoryManager struct { slotC3ROMActive bool // Apple2e slot 3 ROM shadow intCxROMActive bool // Apple2e slots internal ROM shadow activeSlot uint8 // Active slot owner of 0xc800 to 0xcfff + extendedRAMBlock uint8 // Block used for entended memory for RAMWorks cards // Configuration switches, Base64A romPage uint8 // Active ROM page @@ -97,23 +102,35 @@ func (mmu *memoryManager) accessCArea(address uint16) memoryHandler { return mmu.cardsROMExtra[mmu.activeSlot] } -func (mmu *memoryManager) accessLCArea(address uint16) memoryHandler { - block := mmu.lcSelectedBlock +func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler { if mmu.altZeroPage { - block = 1 - } - if address <= addressLimitDArea { - if mmu.lcAltBank { - return mmu.physicalDAltRAM[block] + // Use extended RAM + block := mmu.extendedRAMBlock + if mmu.lcAltBank && address <= addressLimitDArea { + return mmu.physicalExtAltRAM[block] } - return mmu.physicalDRAM[block] + return mmu.physicalExtRAM[mmu.extendedRAMBlock] } - return mmu.physicalEFRAM[block] + + // Use language card + block := mmu.lcSelectedBlock + if mmu.lcAltBank && address <= addressLimitDArea { + return mmu.physicalLangAltRAM[block] + } + return mmu.physicalLangRAM[block] } -func (mmu *memoryManager) getPhysicalMainRAM(alt bool) *memoryRange { - if alt { - return mmu.physicalMainRAMAlt +func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler { + if ext { + return mmu.physicalExtRAM[mmu.extendedRAMBlock] + } + return mmu.physicalMainRAM +} + +func (mmu *memoryManager) getVideoRAM(ext bool) *memoryRange { + if ext { + // The video memory uses the first extended RAM block, even with RAMWorks + return mmu.physicalExtRAM[0] } return mmu.physicalMainRAM } @@ -142,7 +159,7 @@ func (mmu *memoryManager) accessRead(address uint16) memoryHandler { return mmu.accessCArea(address) } if mmu.lcActiveRead { - return mmu.accessLCArea(address) + return mmu.accessUpperRAMArea(address) } return mmu.physicalROM[mmu.romPage] } @@ -171,7 +188,7 @@ func (mmu *memoryManager) accessWrite(address uint16) memoryHandler { return mmu.accessCArea(address) } if mmu.lcActiveWrite { - return mmu.accessLCArea(address) + return mmu.accessUpperRAMArea(address) } return mmu.physicalROM[mmu.romPage] } @@ -205,19 +222,24 @@ func (mmu *memoryManager) setCardROMExtra(slot int, mh memoryHandler) { mmu.cardsROMExtra[slot] = mh } -func (mmu *memoryManager) initLanguageRAM(groups int) { - mmu.physicalDRAM = make([]memoryHandler, groups) - mmu.physicalDAltRAM = make([]memoryHandler, groups) - mmu.physicalEFRAM = make([]memoryHandler, groups) - for i := 0; i < groups; i++ { - mmu.physicalDRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x1000)) - mmu.physicalDAltRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x1000)) - mmu.physicalEFRAM[i] = newMemoryRange(0xe000, make([]uint8, 0x2000)) +func (mmu *memoryManager) initLanguageRAM(groups uint8) { + // Apple II+ language card or Saturn (up to 8 groups) + mmu.physicalLangRAM = make([]*memoryRange, groups) + mmu.physicalLangAltRAM = make([]*memoryRange, groups) + for i := uint8(0); i < groups; i++ { + mmu.physicalLangRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x3000)) + mmu.physicalLangAltRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x1000)) } } -func (mmu *memoryManager) InitRAMalt() { - mmu.physicalMainRAMAlt = newMemoryRange(0, make([]uint8, 0xc000)) +func (mmu *memoryManager) initExtendedRAM(groups int) { + // Apple IIe 80 col card with 64Kb style RAM or RAMWorks (up to 256 banks) + mmu.physicalExtRAM = make([]*memoryRange, groups) + mmu.physicalExtAltRAM = make([]*memoryRange, groups) + for i := 0; i < groups; i++ { + mmu.physicalExtRAM[i] = newMemoryRange(0, make([]uint8, 0x10000)) + mmu.physicalExtAltRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x1000)) + } } // Memory configuration @@ -235,11 +257,19 @@ func (mmu *memoryManager) setLanguageRAM(readActive bool, writeActive bool, altB mmu.lcAltBank = altBank } -func (mmu *memoryManager) setLanguageRAMBlock(block uint8) { - block = block % uint8(len(mmu.physicalDRAM)) +func (mmu *memoryManager) setLanguageRAMActiveBlock(block uint8) { + block = block % uint8(len(mmu.physicalLangRAM)) mmu.lcSelectedBlock = block } +func (mmu *memoryManager) setExtendedRAMActiveBlock(block uint8) { + if int(block) >= len(mmu.physicalExtRAM) { + // How does the real hardware reacts? + block = 0 + } + mmu.extendedRAMBlock = block +} + // TODO: complete save and load func (mmu *memoryManager) save(w io.Writer) error { err := mmu.physicalMainRAM.save(w) diff --git a/screenHiRes.go b/screenHiRes.go index 9351b4d..fc72d1d 100644 --- a/screenHiRes.go +++ b/screenHiRes.go @@ -31,7 +31,7 @@ func getHiResLine(a *Apple2, line int, isSecondPage bool, auxMem bool) []uint8 { } address += getHiResLineOffset(line) - return a.mmu.getPhysicalMainRAM(auxMem).subRange(address, address+hiResLineBytes) + return a.mmu.getVideoRAM(auxMem).subRange(address, address+hiResLineBytes) } func snapshotHiResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA { diff --git a/screenSuperHiRes.go b/screenSuperHiRes.go index ad3435f..e54c77a 100644 --- a/screenSuperHiRes.go +++ b/screenSuperHiRes.go @@ -24,8 +24,10 @@ func snapshotSuperHiResMode(a *Apple2) *image.RGBA { size := image.Rect(0, 0, shrWidth, shrHeight) img := image.NewRGBA(size) + videoRAM := a.mmu.getVideoRAM(true) + // Load the palettes - paletteMem := a.mmu.physicalMainRAMAlt.subRange(shrColorPalettesAddress, shrColorPalettesAddressEnd) + paletteMem := videoRAM.subRange(shrColorPalettesAddress, shrColorPalettesAddressEnd) colors := make([]color.Color, palettesCount) iMem := 0 for i := 0; i < palettesCount; i++ { @@ -46,13 +48,13 @@ func snapshotSuperHiResMode(a *Apple2) *image.RGBA { // Build the lines for y := 0; y < shrHeight; y++ { - controlByte := a.mmu.physicalMainRAMAlt.peek(shrScanLineControlAddress + uint16(y)) + controlByte := videoRAM.peek(shrScanLineControlAddress + uint16(y)) is640Wide := (controlByte & 0x80) != 0 isColorFill := (controlByte & 0x20) != 0 paletteIndex := (controlByte & 0x0f) << 4 lineAddress := shrPixelDataAddress + uint16(shrWidthBytes*y) - lineBytes := a.mmu.physicalMainRAMAlt.subRange(lineAddress, uint16(lineAddress+shrWidthBytes)) + lineBytes := videoRAM.subRange(lineAddress, uint16(lineAddress+shrWidthBytes)) if is640Wide { // Line is 640 pixels, two bits per pixel diff --git a/screenText.go b/screenText.go index 7eed9da..332c6ed 100644 --- a/screenText.go +++ b/screenText.go @@ -20,7 +20,7 @@ const ( ) func snapshotText40Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA { - text := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage) + text := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage) return renderTextMode(a, text, nil /*colorMap*/, light) } @@ -30,13 +30,13 @@ func snapshotText80Mode(a *Apple2, isSecondPage bool, light color.Color) *image. } func snapshotText40RGBMode(a *Apple2, isSecondPage bool) *image.RGBA { - text := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage) - colorMap := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage) + text := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage) + colorMap := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage) return renderTextMode(a, text, colorMap, nil) } func snapshotText40RGBModeColors(a *Apple2, isSecondPage bool) *image.RGBA { - colorMap := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage) + colorMap := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage) return renderTextMode(a, nil /*text*/, colorMap, nil) } @@ -49,8 +49,8 @@ func getTextCharOffset(col int, line int) uint16 { } func getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 { - text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage) - text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage) + text40Columns := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage) + text40ColumnsAlt := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage) // Merge the two 40 cols to return 80 cols text80Columns := make([]uint8, 2*len(text40Columns)) @@ -165,7 +165,7 @@ func DumpTextModeAnsi(a *Apple2) string { if is80Columns { text = getText80FromMemory(a, isSecondPage) } else { - text = getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage) + text = getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage) } columns := len(text) / textLines