/* * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package jace.core; import jace.apple2e.SoftSwitches; import jace.config.Reconfigurable; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; /** * RAM is a 64K address space of paged memory. It also manages sets of memory * listeners, used by I/O as well as emulator add-ons (and cheats). RAM also * manages cards in the emulator because they are tied into the MMU memory * bankswitch logic. * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public abstract class RAM implements Reconfigurable { public PagedMemory activeRead; public PagedMemory activeWrite; public List<RAMListener> listeners; public List<RAMListener>[] listenerMap; public List<RAMListener>[] ioListenerMap; public Optional<Card>[] cards; // card 0 = 80 column card firmware / system rom public int activeSlot = 0; protected final Computer computer; /** * Creates a new instance of RAM * * @param computer */ public RAM(Computer computer) { this.computer = computer; listeners = new ArrayList<>(); cards = new Optional[8]; for (int i = 0; i < 8; i++) { cards[i] = Optional.empty(); } refreshListenerMap(); } public void setActiveCard(int slot) { if (activeSlot != slot) { activeSlot = slot; configureActiveMemory(); } else if (!SoftSwitches.CXROM.getState()) { configureActiveMemory(); } } public int getActiveSlot() { return activeSlot; } public Optional<Card>[] getAllCards() { return cards; } public Optional<Card> getCard(int slot) { if (slot >= 1 && slot <= 7) { return cards[slot]; } return Optional.empty(); } public void addCard(Card c, int slot) { removeCard(slot); cards[slot] = Optional.of(c); c.setSlot(slot); c.attach(); } public void removeCard(Card c) { c.suspend(); c.detach(); removeCard(c.getSlot()); } public void removeCard(int slot) { cards[slot].ifPresent(Card::suspend); cards[slot].ifPresent(Card::detach); cards[slot] = Optional.empty(); } abstract public void configureActiveMemory(); public void write(int address, byte b, boolean generateEvent, boolean requireSynchronization) { byte[] page = activeWrite.getMemoryPage(address); if (page == null) { if (generateEvent) { callListener(RAMEvent.TYPE.WRITE, address, 0, b, requireSynchronization); } } else { int offset = address & 0x0FF; byte old = page[offset]; if (generateEvent) { page[offset] = callListener(RAMEvent.TYPE.WRITE, address, old, b, requireSynchronization); } else { page[offset] = b; } } } public void writeWord(int address, int w, boolean generateEvent, boolean requireSynchronization) { write(address, (byte) (w & 0x0ff), generateEvent, requireSynchronization); write(address + 1, (byte) (w >> 8), generateEvent, requireSynchronization); } public byte readRaw(int address) { // if (address >= 65536) return 0; return activeRead.getMemoryPage(address)[address & 0x0FF]; } public byte read(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSyncronization) { // if (address >= 65536) return 0; byte value = activeRead.getMemoryPage(address)[address & 0x0FF]; // if (triggerEvent || ((address & 0x0FF00) == 0x0C000)) { if (triggerEvent || (address & 0x0FFF0) == 0x0c030) { value = callListener(eventType, address, value, value, requireSyncronization); } return value; } public int readWordRaw(int address) { int lsb = 0x00ff & readRaw(address); int msb = (0x00ff & readRaw(address + 1)) << 8; return msb + lsb; } public int readWord(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSynchronization) { int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization); int msb = (0x00ff & read(address + 1, eventType, triggerEvent, requireSynchronization)) << 8; int value = msb + lsb; return value; } private void mapListener(RAMListener l, int address) { if ((address & 0x0FF00) == 0x0C000) { int index = address & 0x0FF; List<RAMListener> ioListeners = ioListenerMap[index]; if (ioListeners == null) { ioListeners = new ArrayList<>(); ioListenerMap[index] = ioListeners; } if (!ioListeners.contains(l)) { ioListeners.add(l); } } else { int index = address >> 8; List<RAMListener> otherListeners = listenerMap[index]; if (otherListeners == null) { otherListeners = new ArrayList<>(); listenerMap[index] = otherListeners; } if (!otherListeners.contains(l)) { otherListeners.add(l); } } } private void addListenerRange(RAMListener l) { if (l.getScope() == RAMEvent.SCOPE.ADDRESS) { mapListener(l, l.getScopeStart()); } else { int start = 0; int end = 0x0ffff; if (l.getScope() == RAMEvent.SCOPE.RANGE) { start = l.getScopeStart(); end = l.getScopeEnd(); } for (int i = start; i <= end; i++) { mapListener(l, i); } } } private void refreshListenerMap() { listenerMap = new ArrayList[256]; ioListenerMap = new ArrayList[256]; listeners.stream().forEach((l) -> { addListenerRange(l); }); } public RAMListener observe(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) { return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(address); } @Override protected void doEvent(RAMEvent e) { handler.handleEvent(e); } }); } public RAMListener observe(RAMEvent.TYPE type, int address, boolean auxFlag, RAMEvent.RAMEventHandler handler) { return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(address); } @Override protected void doEvent(RAMEvent e) { if (isAuxFlagCorrect(e, auxFlag)) { handler.handleEvent(e); } } }); } public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) { return addListener(new RAMListener(type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(addressStart); setScopeEnd(addressEnd); } @Override protected void doEvent(RAMEvent e) { handler.handleEvent(e); } }); } public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, boolean auxFlag, RAMEvent.RAMEventHandler handler) { return addListener(new RAMListener(type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(addressStart); setScopeEnd(addressEnd); } @Override protected void doEvent(RAMEvent e) { if (isAuxFlagCorrect(e, auxFlag)) { handler.handleEvent(e); } } }); } private boolean isAuxFlagCorrect(RAMEvent e, boolean auxFlag) { if (e.getAddress() < 0x0100) { if (SoftSwitches.AUXZP.getState() != auxFlag) { return false; } } else if (SoftSwitches.RAMRD.getState() != auxFlag) { return false; } return true; } public RAMListener addListener(final RAMListener l) { boolean restart = computer.pause(); if (listeners.contains(l)) { return l; } listeners.add(l); addListenerRange(l); if (restart) { computer.resume(); } return l; } public void removeListener(final RAMListener l) { boolean restart = computer.pause(); listeners.remove(l); refreshListenerMap(); if (restart) { computer.resume(); } } public byte callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue, boolean requireSyncronization) { List<RAMListener> activeListeners; if (requireSyncronization) { computer.getCpu().suspend(); } if ((address & 0x0FF00) == 0x0C000) { activeListeners = ioListenerMap[address & 0x0FF]; if (activeListeners == null && t.isRead()) { if (requireSyncronization) { computer.getCpu().resume(); } return computer.getVideo().getFloatingBus(); } } else { activeListeners = listenerMap[(address >> 8) & 0x0ff]; } if (activeListeners != null) { RAMEvent e = new RAMEvent(t, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, address, oldValue, newValue); activeListeners.stream().forEach((l) -> { l.handleEvent(e); }); if (requireSyncronization) { computer.getCpu().resume(); } return (byte) e.getNewValue(); } if (requireSyncronization) { computer.getCpu().resume(); } return (byte) newValue; } abstract protected void loadRom(String path) throws IOException; abstract public void attach(); abstract public void detach(); abstract public void performExtendedCommand(int i); }