diff --git a/Changes.txt b/Changes.txt index 1e0d4285c..513d31019 100644 --- a/Changes.txt +++ b/Changes.txt @@ -15,7 +15,8 @@ 6.7 to 7.0 (XXXXX XX, 202X) * Enhanced ROM launcher to allow multiple images per ROM - (TODO: controller support, doc) + + * Made heaps of additional images available for the ROM launcher * Added searching by filename for ROM launcher images @@ -31,6 +32,8 @@ * Fixed broken 7800 pause key support + * Added user defined CPU cycle timers to debugger (TODO: screenshots) + -Have fun! diff --git a/docs/debugger.html b/docs/debugger.html index 206a7bc2a..57be1b6ab 100644 --- a/docs/debugger.html +++ b/docs/debugger.html @@ -45,6 +45,7 @@
  • Pseudo-Registers
  • Watches
  • Traps
  • +
  • Timers
  • Save your work!
  • @@ -858,6 +859,23 @@ can remove a trap with "delTrap number", where the number comes from "listTraps" or by entering the identical trap again. You can get rid of all traps at once with the "clearTraps" command.

    +

    Timers

    + +

    Timers are used to measure cycles used between two timer points. They +measure the minimum, maximum and average cycles executed between their two +timer points. Start and end points can be set via prompt command or ROM +Disassembly settings dialog.

    + +

    Timer points can be defined with or without a bank. With a bank, they +are triggered in the given bank only, using all mirror addresses too. Without +a bank, the timer points are triggered in all bank, using the given adress +only.

    + +

    All timers can be shown in detail with "listTimers", a single timer +with "printTimer number". "resetTimers" allows resetting all timer statistics, +"delTimer number" removes a single timer. All timers can be deleted with the +"clearTimers" command.

    +

    Save your work!

    Stella offers several commands to save your work inside the debugger for @@ -945,6 +963,7 @@ Type "help 'cmd'" to see extended information about the given command.

    clearConfig - Clear DiStella config directives [bank xx] clearHistory - Clear the prompt history clearSaveStateIfs - Clear all saveState points + clearTimers - Clear all timers clearTraps - Clear all traps clearWatches - Clear all watches cls - Clear prompt area of text @@ -959,6 +978,7 @@ clearSaveStateIfs - Clear all saveState points delFunction - Delete function with label xx delSaveStateIf - Delete conditional saveState point <xx> delTrap - Delete trap <xx> + delTimer - Delete timer <xx> delWatch - Delete watch <xx> disAsm - Disassemble address xx [yy lines] (default=PC) dump - Dump data at address <xx> [to yy] [1: memory; 2: CPU state; 4: input regs] [?] @@ -994,8 +1014,10 @@ clearSaveStateIfs - Clear all saveState points pCol - Mark 'PCOL' range in disassembly pGfx - Mark 'PGFX' range in disassembly print - Evaluate/print expression xx in hex/dec/binary + printTimer - Print details of timer xx ram - Show ZP RAM, or set address xx to yy1 [yy2 ...] reset - Reset system to power-on state + resetTimers - Reset all timers' statistics rewind - Rewind state by one or [xx] steps/traces/scanlines/frames... riot - Show RIOT timer/input status rom - Set ROM address xx to yy1 [yy2 ...] @@ -1017,8 +1039,9 @@ clearSaveStateIfs - Clear all saveState points scanLine - Advance emulation by <xx> scanlines (default=1) step - Single step CPU [with count xx] stepWhile - Single step CPU while <condition> is true - swchb - Set SWCHB to value xx + swchb - Set SWCHB to value xx tia - Show TIA state + timer - Set a timer point trace - Single step CPU over subroutines [with count xx] trap - Trap read/write access to address(es) xx [yy] trapIf - On <condition> trap R/W access to address(es) xx [yy] @@ -1563,6 +1586,9 @@ matches the address of the disassembly line where the mouse was clicked.
  • Disassemble @ current line: Disassemble from the disassembly line where the mouse was clicked. This allows to fill gaps in the disassembly and is most useful for bankswitched ROMs.
  • +
  • Set timer @ current line: Set a timer point using the current +disassembly line's address and bank
  • +
  • Show tentative code: Allow DiStella to do a static analysis/disassembly.
  • Show PC addresses: Show Program Counter addresses as labels (where there diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index 60ef25da1..fa7852f70 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -577,6 +577,83 @@ const string& DebuggerParser::cartName() const return debugger.myOSystem.console().properties().get(PropType::Cart_Name); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerParser::printTimer(uInt32 idx, bool showHeader) +{ + if(idx >= debugger.m6502().numTimers()) + { + commandResult << red("invalid timer"); + return; + } + + const TimerMap::Timer& timer = debugger.m6502().getTimer(idx); + const bool banked = debugger.cartDebug().romBankCount() > 1; + ostringstream buf; + + if(!debugger.cartDebug().getLabel(buf, timer.from.addr, true)) + buf << " $" << setw(4) << Base::HEX4 << timer.from.addr; + string labelFrom = buf.str(); + + buf.str(""); + if(!debugger.cartDebug().getLabel(buf, timer.to.addr, true)) + buf << " $" << setw(4) << Base::HEX4 << timer.to.addr; + string labelTo = buf.str(); + + labelFrom = (labelFrom + " ").substr(0, banked ? 12 : 15); + labelTo = (labelTo + " ").substr(0, banked ? 12 : 15); + + if(showHeader) + { + if(banked) + commandResult << " #| From /Bk| To /Bk| Execs| Avg. | Min. | Max. |"; + else + commandResult << " #| From | To | Execs| Avg. | Min. | Max. |"; + } + commandResult << endl << Base::toString(idx) << "|" << labelFrom; + if(banked) + { + commandResult << "/" << setw(2) << setfill(' '); + if(timer.from.bank == TimerMap::ANY_BANK) + commandResult << "-"; + else + commandResult << dec << static_cast(timer.from.bank); + } + commandResult << "|"; + if(timer.isPartial) + commandResult << (banked ? " - " : " - ") + << "| -| -| -| -|"; + else + { + commandResult << labelTo; + if(banked) + { + commandResult << "/" << setw(2) << setfill(' '); + if(timer.to.bank == TimerMap::ANY_BANK) + commandResult << "-"; + else + commandResult << dec << static_cast(timer.to.bank); + } + commandResult << "|" + << setw(6) << setfill(' ') << dec << timer.execs << "|"; + if(!timer.execs) + commandResult << " -| -| -|"; + else + commandResult + << setw(6) << dec << timer.averageCycles() << "|" + << setw(6) << dec << timer.minCycles << "|" + << setw(6) << dec << timer.maxCycles << "|"; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerParser::listTimers() +{ + commandResult << "timers:" << endl; + + for(uInt32 i = 0; i < debugger.m6502().numTimers(); ++i) + printTimer(i, i == 0); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerParser::listTraps(bool listCond) { @@ -973,6 +1050,14 @@ void DebuggerParser::executeClearSaveStateIfs() commandResult << "all saveState points cleared"; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "clearTimers" +void DebuggerParser::executeClearTimers() +{ + debugger.m6502().clearTimers(); + commandResult << "all timers cleared"; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "clearTraps" void DebuggerParser::executeClearTraps() @@ -1085,6 +1170,17 @@ void DebuggerParser::executeDelSaveStateIf() commandResult << red("no such saveStateIf"); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "delTimer" +void DebuggerParser::executeDelTimer() +{ + const int index = args[0]; + if(debugger.m6502().delTimer(index)) + commandResult << "removed timer " << Base::toString(index); + else + commandResult << red("no such timer"); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "delTrap" void DebuggerParser::executeDelTrap() @@ -1623,6 +1719,16 @@ void DebuggerParser::executeListSaveStateIfs() commandResult << "no savestateifs defined"; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "listTimers" +void DebuggerParser::executeListTimers() +{ + if(debugger.m6502().numTimers()) + listTimers(); + else + commandResult << "no timers set"; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "listTraps" void DebuggerParser::executeListTraps() @@ -1732,6 +1838,13 @@ void DebuggerParser::executePrint() commandResult << eval(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "printTimer" +void DebuggerParser::executePrintTimer() +{ + printTimer(args[0]); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "ram" void DebuggerParser::executeRam() @@ -1757,6 +1870,14 @@ void DebuggerParser::executeReset() commandResult << "reset system"; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "resetTimers" +void DebuggerParser::executeResetTimers() +{ + debugger.m6502().resetTimers(); + commandResult << "all timers reset"; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "rewind" void DebuggerParser::executeRewind() @@ -2169,6 +2290,65 @@ void DebuggerParser::executeTia() commandResult << debugger.tiaDebug().toString(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "timer" +void DebuggerParser::executeTimer() +{ + const uInt32 romBankCount = debugger.cartDebug().romBankCount(); + + if(argCount < 2) + { + const uInt16 addr = !argCount ? debugger.cpuDebug().pc() : args[0]; + const uInt8 bank = !argCount ? debugger.cartDebug().getBank(addr) : TimerMap::ANY_BANK; + const uInt32 idx = debugger.m6502().addTimer(addr, bank); + commandResult << "set timer " << dec << idx << " " + << (debugger.m6502().getTimer(idx).isPartial ? "start" : "end"); + if(!argCount) + commandResult << " at $" << Base::HEX4 << (addr & TimerMap::ADDRESS_MASK); + if(romBankCount > 1 && !argCount) + commandResult << " + mirrors in bank #" << std::dec << static_cast(bank); + return; + } + else if(argCount > 4) + { + outputCommandError("too many arguments", myCommand); + return; + } + + // Detect if 2nd parameter is bank + if(argCount == 2 && args[0] >= 0x1000 && args[1] <= TimerMap::ANY_BANK) + { + const uInt16 addr = args[0]; + const uInt8 bank = args[1]; + if(bank >= romBankCount && bank != TimerMap::ANY_BANK) + { + commandResult << red("invalid bank"); + return; + } + const uInt32 idx = debugger.m6502().addTimer(addr, bank); + commandResult << "set timer " << dec << idx << " " + << (debugger.m6502().getTimer(idx).isPartial ? "start" : "end"); + return; + } + else + { + const uInt8 bankFrom = argCount >= 3 ? args[2] : TimerMap::ANY_BANK; + if(bankFrom >= romBankCount && bankFrom != TimerMap::ANY_BANK) + { + commandResult << red("invalid bank"); + return; + } + const uInt8 bankTo = argCount == 4 ? args[3] : bankFrom; + if(bankTo >= romBankCount && bankTo != TimerMap::ANY_BANK) + { + commandResult << red("invalid bank"); + return; + } + const uInt32 idx = debugger.m6502().addTimer(args[0], args[1], bankFrom, bankTo); + commandResult << "timer " << idx << " added"; + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trace" void DebuggerParser::executeTrace() @@ -2648,6 +2828,16 @@ DebuggerParser::CommandArray DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executeClearSaveStateIfs) }, + { + "clearTimers", + "Clear all timers", + "All timers cleared\nExample: clearTimers (no parameters)", + false, + false, + { Parameters::ARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeClearTimers) + }, + { "clearTraps", "Clear all traps", @@ -2778,6 +2968,16 @@ DebuggerParser::CommandArray DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executeDelSaveStateIf) }, + { + "delTimer", + "Delete timer ", + "Example: delTimer 0", + true, + false, + { Parameters::ARG_WORD, Parameters::ARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeDelTimer) + }, + { "delTrap", "Delete trap ", @@ -3035,6 +3235,16 @@ DebuggerParser::CommandArray DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executeListSaveStateIfs) }, + { + "listTimers", + "List timers", + "Lists all timers\nExample: listTimers (no parameters)", + false, + false, + { Parameters::ARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeListTimers) + }, + { "listTraps", "List traps", @@ -3146,6 +3356,16 @@ DebuggerParser::CommandArray DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executePrint) }, + { + "printTimer", + "Print statistics for timer ", + "Example: printTimer 0", + true, + false, + { Parameters::ARG_WORD, Parameters::ARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executePrintTimer) + }, + { "ram", "Show ZP RAM, or set address xx to yy1 [yy2 ...]", @@ -3166,6 +3386,16 @@ DebuggerParser::CommandArray DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executeReset) }, + { + "resetTimers", + "Reset all timers' statistics" , + "All timers resetted\nExample: resetTimers (no parameters)", + false, + false, + { Parameters::ARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeResetTimers) + }, + { "rewind", "Rewind state by one or [xx] steps/traces/scanlines/frames...", @@ -3404,6 +3634,16 @@ DebuggerParser::CommandArray DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executeTia) }, + { + "timer", + "Set a cycle counting timer from addresses xx to yy [banks aa bb]", + "Example: timer, timer 1000, timer 3000 3100, timer f000 f800 1", + false, + true, + { Parameters::ARG_WORD, Parameters::ARG_MULTI_BYTE }, + std::mem_fn(&DebuggerParser::executeTimer) + }, + { "trace", "Single step CPU over subroutines [with count xx]", diff --git a/src/debugger/DebuggerParser.hxx b/src/debugger/DebuggerParser.hxx index 9cd114ac8..d3e72ca04 100644 --- a/src/debugger/DebuggerParser.hxx +++ b/src/debugger/DebuggerParser.hxx @@ -101,7 +101,7 @@ class DebuggerParser std::array parms; std::function executor; }; - using CommandArray = std::array; + using CommandArray = std::array; static CommandArray commands; struct Trap @@ -142,6 +142,9 @@ class DebuggerParser void listTraps(bool listCond); string trapStatus(const Trap& trap); + void printTimer(uInt32 idx,bool showHeader = true); + void listTimers(); + // output the error with the example provided for the command void outputCommandError(const string& errorMsg, int command); @@ -162,6 +165,7 @@ class DebuggerParser void executeClearConfig(); void executeClearHistory(); void executeClearSaveStateIfs(); + void executeClearTimers(); void executeClearTraps(); void executeClearWatches(); void executeCls(); @@ -175,6 +179,7 @@ class DebuggerParser void executeDelBreakIf(); void executeDelFunction(); void executeDelSaveStateIf(); + void executeDelTimer(); void executeDelTrap(); void executeDelWatch(); void executeDisAsm(); @@ -200,6 +205,7 @@ class DebuggerParser void executeListConfig(); void executeListFunctions(); void executeListSaveStateIfs(); + void executeListTimers(); void executeListTraps(); void executeLoadAllStates(); void executeLoadConfig(); @@ -211,8 +217,10 @@ class DebuggerParser void executePCol(); void executePGfx(); void executePrint(); + void executePrintTimer(); void executeRam(); void executeReset(); + void executeResetTimers(); void executeRewind(); void executeRiot(); void executeRom(); @@ -236,6 +244,7 @@ class DebuggerParser void executeStepWhile(); void executeSwchb(); void executeTia(); + void executeTimer(); void executeTrace(); void executeTrap(); void executeTrapIf(); diff --git a/src/debugger/TimerMap.cxx b/src/debugger/TimerMap.cxx new file mode 100644 index 000000000..d392e227f --- /dev/null +++ b/src/debugger/TimerMap.cxx @@ -0,0 +1,148 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2022 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "TimerMap.hxx" + +/* + TODOs: + x unordered_multimap (not required for just a few timers) + o 13 vs 16 bit, use ADDRESS_MASK & ANY_BANK, when??? + ? timer line display in disassembly? (color, symbol,...?) +*/ + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 TimerMap::add(const uInt16 fromAddr, const uInt16 toAddr, + const uInt8 fromBank, const uInt8 toBank) +{ + const TimerPoint tpFrom(fromAddr, fromBank); + const TimerPoint tpTo(toAddr, toBank); + const Timer complete(tpFrom, tpTo); + + myList.push_back(complete); + myFromMap.insert(TimerPair(tpFrom, &myList.back())); + myToMap.insert(TimerPair(tpTo, &myList.back())); + + return size() - 1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 TimerMap::add(const uInt16 addr, const uInt8 bank) +{ + const uInt32 idx = size() - 1; + const bool isPartialTimer = size() && get(idx).isPartial; + const TimerPoint tp(addr, bank); + + if(!isPartialTimer) + { + const Timer partial(tp); + + myList.push_back(partial); + myFromMap.insert(TimerPair(tp, &myList.back())); + } + else + { + Timer& partial = myList[idx]; + + partial.setTo(tp); + myToMap.insert(TimerPair(tp, &partial)); + } + return size() - 1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool TimerMap::erase(const uInt32 idx) +{ + if(size() > idx) + { + const Timer* timer = &myList[idx]; + const TimerPoint tpFrom(timer->from); + const TimerPoint tpTo(timer->to); + + // Find address in from and to maps, TODO what happens if not found??? + const auto from = myFromMap.equal_range(tpFrom); + for(auto it = from.first; it != from.second; ++it) + if(it->second == timer) + { + myFromMap.erase(it); + break; + } + + const auto to = myToMap.equal_range(tpTo); + for(auto it = to.first; it != to.second; ++it) + if(it->second == timer) + { + myToMap.erase(it); + break; + } + + // Finally remove from list + myList.erase(myList.begin() + idx); + return true; + } + return false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimerMap::clear() +{ + myList.clear(); + myFromMap.clear(); + myToMap.clear(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimerMap::reset() +{ + for(auto it = myList.begin(); it != myList.end(); ++it) + it->reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimerMap::update(const uInt16 addr, const uInt8 bank, + const uInt64 cycles) +{ + // 13 bit timerpoint + if((addr & ADDRESS_MASK) != addr) + { + TimerPoint tp(addr, bank); // -> addr & ADDRESS_MASK + + // Find address in from and to maps + const auto from = myFromMap.equal_range(tp); + for(auto it = from.first; it != from.second; ++it) + if(!it->second->isPartial) + it->second->start(cycles); + + const auto to = myToMap.equal_range(tp); + for(auto it = to.first; it != to.second; ++it) + if(!it->second->isPartial) + it->second->stop(cycles); + } + + // 16 bit timerpoint + TimerPoint tp(addr, bank, false); // -> addr + + // Find address in from and to maps + const auto from = myFromMap.equal_range(tp); + for(auto it = from.first; it != from.second; ++it) + if(!it->second->isPartial) + it->second->start(cycles); + + const auto to = myToMap.equal_range(tp); + for(auto it = to.first; it != to.second; ++it) + if(!it->second->isPartial) + it->second->stop(cycles); +} diff --git a/src/debugger/TimerMap.hxx b/src/debugger/TimerMap.hxx new file mode 100644 index 000000000..95b6de2ea --- /dev/null +++ b/src/debugger/TimerMap.hxx @@ -0,0 +1,250 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2022 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIMER_MAP_HXX +#define TIMER_MAP_HXX + +#include +#include +#include + +#include "bspf.hxx" + +/** + This class handles debugger timers. Each timer needs a 'from' and a 'to' + address. + + @author Thomas Jentzsch +*/ +class TimerMap +{ + public: + static constexpr uInt8 ANY_BANK = 255; // timer breakpoint valid in any bank + + //private: + static constexpr uInt16 ADDRESS_MASK = 0x1fff; // either 0x1fff or 0xffff (not needed then) + + private: + struct TimerPoint + { + uInt16 addr{0}; + uInt8 bank{ANY_BANK}; + + explicit constexpr TimerPoint(uInt16 c_addr, uInt8 c_bank, bool mask = true) + : addr{c_addr}, bank{c_bank} + { + if(mask && bank != ANY_BANK) + addr = addr & ADDRESS_MASK; + } + TimerPoint() + : addr{0}, bank(ANY_BANK) {} + +#if 0 // unused + bool operator==(const TimerPoint& other) const + { + if(addr == other.addr) + { + if(bank == ANY_BANK || other.bank == ANY_BANK) + return true; + else + return bank == other.bank; + } + return false; + } +#endif + + bool operator<(const TimerPoint& other) const + { + if(bank == ANY_BANK || other.bank == ANY_BANK) + return addr < other.addr; + + return bank < other.bank || (bank == other.bank && addr < other.addr); + } + }; + + public: + struct Timer + { + TimerPoint from{}; + TimerPoint to{}; + bool isPartial{false}; + + uInt64 execs{0}; + uInt64 lastCycles{0}; + uInt64 totalCycles{0}; + uInt64 minCycles{ULONG_MAX}; + uInt64 maxCycles{0}; + bool isStarted{false}; + + explicit constexpr Timer(const TimerPoint& c_from, const TimerPoint& c_to) + : from{c_from}, to{c_to} + { + //if(to.bank == ANY_BANK) // TODO: check if this is required + //{ + // to.bank = from.bank; + // if(to.bank != ANY_BANK) + // to.addr &= ADDRESS_MASK; + //} + } + + Timer(uInt16 fromAddr, uInt16 toAddr, uInt8 fromBank, uInt8 toBank) + { + Timer(TimerPoint(fromAddr, fromBank), TimerPoint(fromAddr, fromBank)); + } + + Timer(const TimerPoint& tp) + { + if(!isPartial) + { + from = tp; + isPartial = true; + } + else + { + to = tp; + isPartial = false; + + //if(to.bank == ANY_BANK) // TODO: check if this is required + //{ + // to.bank = from.bank; + // if(to.bank != ANY_BANK) + // to.addr &= ADDRESS_MASK; + //} + } + } + + Timer(uInt16 addr, uInt8 bank) + { + Timer(TimerPoint(addr, bank)); + } + +#if 0 // unused + bool operator==(const Timer& other) const + { + cerr << from.addr << ", " << to.addr << endl; + if(from.addr == other.from.addr && to.addr == other.to.addr) + { + if((from.bank == ANY_BANK || other.from.bank == ANY_BANK) && + (to.bank == ANY_BANK || other.to.bank == ANY_BANK)) + return true; + else + return from.bank == other.from.bank && to.bank == other.to.bank; + } + return false; + } + + bool operator<(const Timer& other) const + { + if(from.bank < other.from.bank || (from.bank == other.from.bank && from.addr < other.from.addr)) + return true; + + if(from.bank == other.from.bank && from.addr == other.from.addr) + return to.bank < other.to.bank || (to.bank == other.to.bank && to.addr < other.to.addr); + + return false; + } +#endif + + void setTo(const TimerPoint& tp) + { + to = tp; + isPartial = false; + + //if(to.bank == ANY_BANK) // TODO: check if this is required + //{ + // to.bank = from.bank; + // if(to.bank != ANY_BANK) + // to.addr &= ADDRESS_MASK; + //} + } + + void reset() + { + execs = lastCycles = totalCycles = maxCycles = 0; + minCycles = ULONG_MAX; + } + + // Start the timer + void start(uInt64 cycles) + { + lastCycles = cycles; + isStarted = true; + } + + // Stop the timer and update stats + void stop(uInt64 cycles) + { + if(isStarted) + { + const uInt64 diffCycles = cycles - lastCycles; + + ++execs; + totalCycles += diffCycles; + minCycles = std::min(minCycles, diffCycles); + maxCycles = std::max(maxCycles, diffCycles); + isStarted = false; + } + } + + uInt32 averageCycles() const { + return execs ? std::round(totalCycles / execs) : 0; } + }; // Timer + + explicit TimerMap() = default; + + bool isInitialized() const { return myList.size(); } + + /** Add new timer */ + uInt32 add(const uInt16 fromAddr, const uInt16 toAddr, + const uInt8 fromBank, const uInt8 toBank); + uInt32 add(const uInt16 addr, const uInt8 bank); + + /** Erase timer */ + bool erase(const uInt32 idx); + + /** Clear all timers */ + void clear(); + + /** Reset all timers */ + void reset(); + + /** Get timer */ + const Timer& get(const uInt32 idx) const { return myList[idx]; } + uInt32 size() const { return static_cast(myList.size()); } + + /** Update timer */ + void update(const uInt16 addr, const uInt8 bank, + const uInt64 cycles); + + private: + using TimerList = std::deque; // makes sure that the element pointers do NOT change + using TimerPair = std::pair; + using FromMap = std::multimap; + using ToMap = std::multimap; + + TimerList myList; + FromMap myFromMap; + ToMap myToMap; + + // Following constructors and assignment operators not supported + TimerMap(const TimerMap&) = delete; + TimerMap(TimerMap&&) = delete; + TimerMap& operator=(const TimerMap&) = delete; + TimerMap& operator=(TimerMap&&) = delete; +}; + +#endif diff --git a/src/debugger/gui/RomListSettings.cxx b/src/debugger/gui/RomListSettings.cxx index 549999950..01c2bf25b 100644 --- a/src/debugger/gui/RomListSettings.cxx +++ b/src/debugger/gui/RomListSettings.cxx @@ -48,6 +48,13 @@ RomListSettings::RomListSettings(GuiObject* boss, const GUI::Font& font) "RunTo PC @ current line", RomListWidget::kRuntoPCCmd); wid.push_back(runtoPC); + // Toggle timer + ypos += buttonHeight + 4; + auto* setTimer = + new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, + "Set timer @ current line", RomListWidget::kSetTimerCmd); + wid.push_back(setTimer); + // Re-disassemble ypos += buttonHeight + 4; auto* disasm = @@ -154,6 +161,11 @@ void RomListSettings::handleCommand(CommandSender* sender, int cmd, int data, in sendCommand(cmd, _item, -1); break; } + case RomListWidget::kSetTimerCmd: + { + sendCommand(cmd, _item, -1); + break; + } case RomListWidget::kDisassembleCmd: { sendCommand(cmd, _item, -1); diff --git a/src/debugger/gui/RomListWidget.hxx b/src/debugger/gui/RomListWidget.hxx index 9a4c7e6b5..d9144b1ce 100644 --- a/src/debugger/gui/RomListWidget.hxx +++ b/src/debugger/gui/RomListWidget.hxx @@ -37,6 +37,7 @@ class RomListWidget : public EditableWidget // 'id' will be the Base::Format of the data kSetPCCmd = 'STpc', // 'data' will be disassembly line number kRuntoPCCmd = 'RTpc', // 'data' will be disassembly line number + kSetTimerCmd = 'STtm', kDisassembleCmd = 'REds', kTentativeCodeCmd = 'TEcd', // 'data' will be boolean kPCAddressesCmd = 'PCad', // 'data' will be boolean diff --git a/src/debugger/gui/RomWidget.cxx b/src/debugger/gui/RomWidget.cxx index 67b4dc8a4..3b183f95f 100644 --- a/src/debugger/gui/RomWidget.cxx +++ b/src/debugger/gui/RomWidget.cxx @@ -105,6 +105,11 @@ void RomWidget::handleCommand(CommandSender* sender, int cmd, int data, int id) runtoPC(data); break; + case RomListWidget::kSetTimerCmd: + // 'data' is the line in the disassemblylist to be accessed + setTimer(data); + break; + case RomListWidget::kDisassembleCmd: // 'data' is the line in the disassemblylist to be accessed disassemble(data); @@ -196,6 +201,20 @@ void RomWidget::runtoPC(int disasm_line) } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomWidget::setTimer(int disasm_line) +{ + const uInt16 address = getAddress(disasm_line); + + if(address != 0) + { + ostringstream command; + command << "timer #" << address << " " << instance().debugger().cartDebug().getBank(address); + const string& msg = instance().debugger().run(command.str()); + instance().frameBuffer().showTextMessage(msg); + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RomWidget::disassemble(int disasm_line) { diff --git a/src/debugger/gui/RomWidget.hxx b/src/debugger/gui/RomWidget.hxx index 1a63e56e8..2ccbb0548 100644 --- a/src/debugger/gui/RomWidget.hxx +++ b/src/debugger/gui/RomWidget.hxx @@ -51,6 +51,7 @@ class RomWidget : public Widget, public CommandSender void toggleBreak(int disasm_line); void setPC(int disasm_line); void runtoPC(int disasm_line); + void setTimer(int disasm_line); void disassemble(int disasm_line); void patchROM(int disasm_line, const string& bytes, Common::Base::Fmt base); diff --git a/src/debugger/module.mk b/src/debugger/module.mk index bc4205149..6356aad6a 100644 --- a/src/debugger/module.mk +++ b/src/debugger/module.mk @@ -8,7 +8,8 @@ MODULE_OBJS := \ src/debugger/CpuDebug.o \ src/debugger/DiStella.o \ src/debugger/RiotDebug.o \ - src/debugger/TIADebug.o + src/debugger/TIADebug.o \ + src/debugger/TimerMap.o MODULE_DIRS += \ src/debugger diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index 4ca269086..5132fea36 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -264,8 +264,10 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result) if(myBreakPoints.check(PC, bank)) { myLastBreakCycle = mySystem->cycles(); + const uInt32 flags = myBreakPoints.get(PC, bank); + // disable a one-shot breakpoint - if(myBreakPoints.get(PC, bank) & BreakpointMap::ONE_SHOT) + if(flags & BreakpointMap::ONE_SHOT) { myBreakPoints.erase(PC, bank); return; @@ -292,6 +294,9 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result) } } + if(myTimer.isInitialized()) + myTimer.update(PC, mySystem->cart().getBank(PC), mySystem->cycles()); + const int cond = evalCondBreaks(); if(cond > -1) { @@ -656,12 +661,12 @@ uInt32 M6502::addCondTrap(Expression* e, const string& name) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool M6502::delCondTrap(uInt32 brk) +bool M6502::delCondTrap(uInt32 idx) { - if(brk < myTrapConds.size()) + if(idx < myTrapConds.size()) { - Vec::removeAt(myTrapConds, brk); - Vec::removeAt(myTrapCondNames, brk); + Vec::removeAt(myTrapConds, idx); + Vec::removeAt(myTrapCondNames, idx); updateStepStateByInstruction(); @@ -691,4 +696,36 @@ void M6502::updateStepStateByInstruction() myStepStateByInstruction = !myCondBreaks.empty() || !myCondSaveStates.empty() || !myTrapConds.empty(); } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 M6502::addTimer(const uInt16 fromAddr, const uInt16 toAddr, + const uInt8 fromBank, const uInt8 toBank) +{ + return myTimer.add(fromAddr, toAddr, fromBank, toBank); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 M6502::addTimer(const uInt16 addr, const uInt8 bank) +{ + return myTimer.add(addr, bank); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool M6502::delTimer(const uInt32 idx) +{ + return myTimer.erase(idx); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void M6502::clearTimers() +{ + myTimer.clear(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void M6502::resetTimers() +{ + myTimer.reset(); +} + #endif // DEBUGGER_SUPPORT diff --git a/src/emucore/M6502.hxx b/src/emucore/M6502.hxx index 53c509cee..3bf9f7a24 100644 --- a/src/emucore/M6502.hxx +++ b/src/emucore/M6502.hxx @@ -31,6 +31,7 @@ class DispatchResult; #include "Expression.hxx" #include "TrapArray.hxx" #include "BreakpointMap.hxx" + #include "TimerMap.hxx" #endif #include "bspf.hxx" @@ -248,15 +249,25 @@ class M6502 : public Serializable // methods for 'trapif' handling uInt32 addCondTrap(Expression* e, const string& name); - bool delCondTrap(uInt32 brk); + bool delCondTrap(uInt32 idx); void clearCondTraps(); const StringList& getCondTrapNames() const; + // methods for 'timer' handling: + uInt32 addTimer(uInt16 fromAddr, uInt16 toAddr, + uInt8 fromBank, uInt8 toBank); + uInt32 addTimer(uInt16 addr, uInt8 bank); + bool delTimer(uInt32 idx); + void clearTimers(); + void resetTimers(); + uInt32 numTimers() const { return myTimer.size(); } + const TimerMap::Timer& getTimer(uInt32 idx) const { return myTimer.get(idx); } + void setGhostReadsTrap(bool enable) { myGhostReadsTrap = enable; } void setReadFromWritePortBreak(bool enable) { myReadFromWritePortBreak = enable; } void setWriteToReadPortBreak(bool enable) { myWriteToReadPortBreak = enable; } void setLogBreaks(bool enable) { myLogBreaks = enable; } - bool getLogBreaks() { return myLogBreaks; } + bool getLogBreaks() const { return myLogBreaks; } #endif // DEBUGGER_SUPPORT private: @@ -465,6 +476,9 @@ class M6502 : public Serializable StringList myCondSaveStateNames; vector> myTrapConds; StringList myTrapCondNames; + + TimerMap myTimer; + #endif // DEBUGGER_SUPPORT bool myGhostReadsTrap{false}; // trap on ghost reads diff --git a/src/os/macos/stella.xcodeproj/project.pbxproj b/src/os/macos/stella.xcodeproj/project.pbxproj index c35c4b674..593cd42e1 100644 --- a/src/os/macos/stella.xcodeproj/project.pbxproj +++ b/src/os/macos/stella.xcodeproj/project.pbxproj @@ -442,6 +442,8 @@ DC7A24D5173B1CF600B20FE9 /* Variant.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC7A24D4173B1CF600B20FE9 /* Variant.hxx */; }; DC7A24DF173B1DBC00B20FE9 /* FileListWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC7A24DD173B1DBC00B20FE9 /* FileListWidget.cxx */; }; DC7A24E0173B1DBC00B20FE9 /* FileListWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC7A24DE173B1DBC00B20FE9 /* FileListWidget.hxx */; }; + DC7C83D628EF2E080097B5AE /* TimerMap.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC7C83D428EF2E080097B5AE /* TimerMap.cxx */; }; + DC7C83D728EF2E080097B5AE /* TimerMap.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC7C83D528EF2E080097B5AE /* TimerMap.hxx */; }; DC8078DB0B4BD5F3005E9305 /* DebuggerExpressions.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC8078DA0B4BD5F3005E9305 /* DebuggerExpressions.hxx */; }; DC8078EA0B4BD697005E9305 /* UIDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC8078E60B4BD697005E9305 /* UIDialog.cxx */; }; DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC8078E70B4BD697005E9305 /* UIDialog.hxx */; }; @@ -1283,6 +1285,8 @@ DC7A24D4173B1CF600B20FE9 /* Variant.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Variant.hxx; sourceTree = ""; }; DC7A24DD173B1DBC00B20FE9 /* FileListWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileListWidget.cxx; sourceTree = ""; }; DC7A24DE173B1DBC00B20FE9 /* FileListWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileListWidget.hxx; sourceTree = ""; }; + DC7C83D428EF2E080097B5AE /* TimerMap.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimerMap.cxx; sourceTree = ""; }; + DC7C83D528EF2E080097B5AE /* TimerMap.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimerMap.hxx; sourceTree = ""; }; DC8078DA0B4BD5F3005E9305 /* DebuggerExpressions.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = DebuggerExpressions.hxx; sourceTree = ""; }; DC8078E60B4BD697005E9305 /* UIDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = UIDialog.cxx; sourceTree = ""; }; DC8078E70B4BD697005E9305 /* UIDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = UIDialog.hxx; sourceTree = ""; }; @@ -2393,6 +2397,8 @@ 2D30F8760868A4DB00938B9D /* TIADebug.hxx */, 2D6CC10308C811A600B8F642 /* TiaZoomWidget.cxx */, 2D6CC10408C811A600B8F642 /* TiaZoomWidget.hxx */, + DC7C83D428EF2E080097B5AE /* TimerMap.cxx */, + DC7C83D528EF2E080097B5AE /* TimerMap.hxx */, DC2874061F8F2278004BF21A /* TrapArray.hxx */, 2D60513708987A5400C6DE89 /* yacc */, ); @@ -2814,6 +2820,7 @@ DC816CF72572F92A00FBCCDA /* json_lib.hxx in Headers */, 2D91741B09BA90380026E9FF /* OSystem.hxx in Headers */, DC6A18F919B3E65500DEB242 /* CartMDMWidget.hxx in Headers */, + DC7C83D728EF2E080097B5AE /* TimerMap.hxx in Headers */, CFB521D82853A2590083B9CE /* CartBUSInfoWidget.hxx in Headers */, 2D91741F09BA90380026E9FF /* AboutBox.h in Headers */, DC84FC572677C64200E60ADE /* CartARMWidget.hxx in Headers */, @@ -3270,6 +3277,7 @@ DC44019E1F1A5D01008C08F6 /* ColorWidget.cxx in Sources */, 2D91749309BA90380026E9FF /* Paddles.cxx in Sources */, 2D91749409BA90380026E9FF /* Props.cxx in Sources */, + DC7C83D628EF2E080097B5AE /* TimerMap.cxx in Sources */, 2D91749509BA90380026E9FF /* PropsSet.cxx in Sources */, 2D91749709BA90380026E9FF /* Serializer.cxx in Sources */, 2D91749809BA90380026E9FF /* Switches.cxx in Sources */, diff --git a/src/os/windows/Stella.vcxproj b/src/os/windows/Stella.vcxproj index 24fcfec5a..5173c4d6f 100755 --- a/src/os/windows/Stella.vcxproj +++ b/src/os/windows/Stella.vcxproj @@ -857,6 +857,7 @@ true + @@ -2052,6 +2053,7 @@ true + true diff --git a/src/os/windows/Stella.vcxproj.filters b/src/os/windows/Stella.vcxproj.filters index 4b95e1541..2f4716885 100644 --- a/src/os/windows/Stella.vcxproj.filters +++ b/src/os/windows/Stella.vcxproj.filters @@ -1193,6 +1193,8 @@ Source Files + + Source Files\debugger @@ -2434,6 +2436,8 @@ Header Files + + Header Files\debugger @@ -2446,4 +2450,4 @@ Resource Files - \ No newline at end of file +