////////////////////////////////////////////////////////////////////////////////
// CPU16x4k.h -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// This is the base class for an 8 bit CPU with 16 address lines. The
// memory address range of 64k is divided into 16 chunks of 4k each.
// These chunks can be mapped into the derived CPU's address space very
// quickly. There are also separate mappings for read and write access
// which allows the emulation of ROM. Implementing the MMU as a separate
// chip would be too slow.

class CPU16x4k : public CPU {

  // memory control blocks
  struct MCB {
    MCB* pNext;
    byte* pbMem;
    int iUsedMask;
  };
  MCB* pMCBRoot;

  // common function for allocating traps
  global byte AllocTrap(Object* pObject, bpfn bpfnOnRead, pfnb pfnbOnWrite, int iStart, int iEnd);

  // dummies for unused registers
  global byte ReadNoReg();
  global void WriteNoReg(byte);

protected:

  // pointers to the 4k chunks
  byte* apbReadMapping[16];
  byte* apbWriteMapping[16];

  // traps
  // 0 = no trap
  // 1 = beginning of new 4k chunk for adjusting the CPU's Program Counter
  // 2-31 = code trap (fetch only)
  // 32-239 = IO traps (read, write, fetch)
  // 240-255 = IO traps with dummy reads (CPU throws away data)
  #define CPU16x4k_iIOStart 32
  #define CPU16x4k_iDummyStart 240
  Object* apObject[256];
  bpfn abpfnOnRead[256];
  pfnb apfnbOnWrite[256];

public:

  // constructor
  global CPU16x4k() {
    pMCBRoot = NULL;
    memset(apbReadMapping, 0, sizeof apbReadMapping);
    memset(apbWriteMapping, 0, sizeof apbWriteMapping);
    memset(apObject, 0, sizeof apObject);
    memset(abpfnOnRead, 0, sizeof abpfnOnRead);
    memset(apfnbOnWrite, 0, sizeof apfnbOnWrite);
  }

  // destructor
  global virtual ~CPU16x4k() {
    MCB* p = pMCBRoot;
    while (p != NULL) {
      MemFree(p->pbMem);
      MCB* pSaveNext = p->pNext;
      MemFree(p);
      p = pSaveNext;
    }
  }

  // allocate and free emulator memory in 4k chunks, maximum 64k at once
  global byte* AllocMem(int iSize);
  global void FreeMem(byte* pbMem, int iSize);

  // map memory into page 0..15
  inline void MapMem(int iPage, byte* pbReadMem, byte* pbWriteMem) {
    apbReadMapping[iPage] = pbReadMem - iPage * 4096;
    apbWriteMapping[iPage] = pbWriteMem - iPage * 4096;
  }
  inline void MapReadMem(int iPage, byte* pbMem) {
    apbReadMapping[iPage] = pbMem - iPage * 4096;
  }
  inline void MapWriteMem(int iPage, byte* pbMem) {
    apbWriteMapping[iPage] = pbMem - iPage * 4096;
  }

  // allocate and free traps for registers and breakpoints
  inline byte AllocCodeTrap(Object* pObject, bpfn bpfnOnFetch) {
    return AllocTrap(pObject, bpfnOnFetch, NULL, 2, CPU16x4k_iIOStart);
  }
  inline byte AllocIOTrap(Object* pObject, bpfn bpfnOnRead, pfnb pfnbOnWrite) {
    return AllocTrap(pObject, bpfnOnRead, pfnbOnWrite, CPU16x4k_iIOStart, CPU16x4k_iDummyStart);
  }
  inline byte AllocIOTrapWithDummyReads(Object* pObject, bpfn bpfnOnRead, pfnb pfnbOnWrite) {
    return AllocTrap(pObject, bpfnOnRead, pfnbOnWrite, CPU16x4k_iDummyStart, 256);
  }
  global void FreeTrap(byte bTrap);

  // set, clear and query traps
  inline void SetTrap(byte* pbMem, byte bTrap) {
    pbMem[65536] = bTrap;
  }
  inline byte GetTrap(byte* pbMem) {
    return pbMem[65536];
  }
  inline void ClearTrap(byte* pbMem) {
    pbMem[65536] = 0;
  }

  // map and unmap whole chips including shadow registers
  global void MapChip(Chip* pChip, byte* pbMem, int iSize);
  global void UnmapChip(Chip* pChip, byte* pbMem, int iSize);
};
