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 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.
+
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