public MemoryController(string name) { _name = name; _bkmTable = new BookmarkEntry[256]; LoadBookmarkROM(); Reset(); }
/// <summary> /// Load the BKM16 ROM image from disk. /// </summary> private void LoadBookmarkROM() { // BKM is a lookup table with an 8-bit index, returning an 8-bit value FileStream fs = new FileStream(Paths.BuildPROMPath("bkm16emu.rom"), FileMode.Open); for (int i = 0; i < 256; i++) { // Split the result byte into fields once, rather than on every lookup :-) _bkmTable[i] = new BookmarkEntry((byte)fs.ReadByte()); } fs.Close(); #if TRACING_ENABLED if (Trace.TraceOn) { Trace.Log(LogType.EmuState, "Initialized BKM ROM lookup table."); } #endif }
/// <summary> /// Sets bookmarks for the next cycle, and modifies the current one if necessary. /// WARNING: THIS IS WHERE THE SAUSAGE IS MADE. /// </summary> private void UpdateBookmarks(MemoryCycle nextCycle) { if (nextCycle == MemoryCycle.None) { // If no active or pending op, reset our bookmark if (!_current.Active && !_pending.Active) { _bookmark = 0; } } else { // This microinstruction specifies a new memory request: initialize // the next bookmark value based on the request type int book = (int)nextCycle; // // Special cases for RasterOp // if (RasterOp.Instance.Enabled) { if (Tstate == 0) { // First: we're allowed to issue Store4/4R in T0, ahead of the // usual T3. So we tweak the cycle type to index the bookmark // ROM with the modified timings. if (nextCycle == MemoryCycle.Store4R) { book = 0x2; // "RopStore4R" } else if (nextCycle == MemoryCycle.Store4) { book = 0x4; // "RopStore4" } } else if (Tstate == 3) { // // Second: Fetch4/4Rs are issued back-to-back (in the correct t3) // but must NOT introduce the possible CPU abort of a WaitT2 state; // MDI must remain valid AND the index values must count down correctly // for the operation in progress, so that after the t0,t1 complete the // next op's four words arrive in the four subsequent Tstates. This // introduces two additional fake cycle types, as with the case above. Ugh.. // if (_current.CycleType == MemoryCycle.Fetch4R && nextCycle == MemoryCycle.Fetch4R) { _bookmark = book = 0x1; // "RopFetch4R" } else if (_current.CycleType == MemoryCycle.Fetch4 && nextCycle == MemoryCycle.Fetch4) { _bookmark = book = 0x3; // "RopFetch4" } } } // For RasterOp special cases, use the modified bookmark for the entire cycle _nextBookmark = book; // // Special cases for indirect or overlapped Fetches (non-RasterOp) // if (MemoryBoard.Instance.IsFetch(nextCycle)) { // // Back-to-back Fetch or Fetch2 requests present unique timing challenges // (and allow a small performance boost by eliminating some wait states). // To accommodate this with as little embarrassment as possible, we use a // transitional bookmark value to cover the overlap. For a Fetch, this may // terminate the op early, invalidating one or more time slots where MDI is // valid (and forcing a CPU wait so that incorrect data is not returned). // In other cases we have to let the current op retire normally but drop // immediately into a WaitT2 (rather than WaitT3) for the new op. There's // no pretty way to deal with this... // // Gory details in the comments below. Look away now for "plausible deniability". // if (_current.CycleType == MemoryCycle.Fetch || _current.CycleType == MemoryCycle.Fetch2) { book = 0x6; // "IndFetch" covers the overlap... _bookmark = book; // ...force immediate switch for the (t2,t3)... _nextBookmark = (int)nextCycle; // ...but switch back to the real cycle type in Request() } else if (_current.CycleType == MemoryCycle.Fetch4 && !RasterOp.Instance.Enabled) { book = 0x7; // "IndFetch4" is for the specific case of a RefillOp _bookmark = book; // followed immediately by another Fetch; can't clobber the _nextBookmark = (int)nextCycle; // last index word, or the last two OpFile bytes are screwed } } // Get a new set of flags -- these may modify the current cycle! BookmarkEntry flags = GetBookmarkEntry(book, _nextState); #if DEBUG // If the Recognize flag is not set, we're really out in left field... // ... but all of this can go away entirely once things are fully debugged. if (!flags.Recognize) { Console.WriteLine("-->\t{0} queue: Recognize not set for new {1} request in T{2}!", _name, nextCycle, Tstate); // If the Abort flag isn't set either, our BKM16 ROM is buggy; force an // abort and just hope for the best? if (!flags.Abort) { Console.WriteLine("-->\tForced abort in T{0} due to new request in wrong cycle.", Tstate); Console.WriteLine("\tFlags: {0}", flags); DumpQueue(); flags.Abort = true; // PERQSystem.Instance.Break(); } } #endif // If the done flag is set, retire the current op (may be early, // if a Fetch is overlapped) if (flags.Complete) { #if TRACING_ENABLED if (Trace.TraceOn) { Trace.Log(LogType.MemoryState, "{0} queue: Terminated {1}", _name, _current); } #endif _current.Clear(); } // Set the wait and next state based on the new flags _wait = flags.Abort; _nextState = flags.NextState; } }