From 7a39ca8b6e48c23bda639a38d6507760d18332e6 Mon Sep 17 00:00:00 2001
From: Thomas Jentzsch
Date: Thu, 6 Oct 2022 16:55:00 +0200
Subject: [PATCH 1/6] added user defined timers to debugger (TODO: screenshots)
---
Changes.txt | 5 +-
docs/debugger.html | 28 ++-
src/debugger/DebuggerParser.cxx | 240 ++++++++++++++++++++++++++
src/debugger/DebuggerParser.hxx | 11 +-
src/debugger/gui/RomListSettings.cxx | 12 ++
src/debugger/gui/RomListWidget.hxx | 1 +
src/debugger/gui/RomWidget.cxx | 19 ++
src/debugger/gui/RomWidget.hxx | 1 +
src/debugger/module.mk | 1 +
src/emucore/M6502.cxx | 47 ++++-
src/emucore/M6502.hxx | 18 +-
src/os/windows/Stella.vcxproj | 2 +
src/os/windows/Stella.vcxproj.filters | 6 +
13 files changed, 381 insertions(+), 10 deletions(-)
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/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..e67f42eab 100644
--- a/src/debugger/module.mk
+++ b/src/debugger/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
src/debugger/DiStella.o \
src/debugger/RiotDebug.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..9d793e33c 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/windows/Stella.vcxproj b/src/os/windows/Stella.vcxproj
index edca552c0..b5492efa3 100755
--- a/src/os/windows/Stella.vcxproj
+++ b/src/os/windows/Stella.vcxproj
@@ -856,6 +856,7 @@
true
+
@@ -2051,6 +2052,7 @@
true
+
true
diff --git a/src/os/windows/Stella.vcxproj.filters b/src/os/windows/Stella.vcxproj.filters
index 45326fdf0..071b641e8 100644
--- a/src/os/windows/Stella.vcxproj.filters
+++ b/src/os/windows/Stella.vcxproj.filters
@@ -1194,6 +1194,9 @@
Source Files\sdl_blitter
+
+ Source Files\debugger
+
@@ -2435,6 +2438,9 @@
Header Files
+
+ Header Files\debugger
+
From 2a6fdcdb3d2f6a8493bc1b14c922fe338b002bbe Mon Sep 17 00:00:00 2001
From: Stephen Anthony
Date: Thu, 6 Oct 2022 12:33:45 -0230
Subject: [PATCH 2/6] Fixed typo in module.mk.
---
src/debugger/module.mk | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/debugger/module.mk b/src/debugger/module.mk
index e67f42eab..09dd87979 100644
--- a/src/debugger/module.mk
+++ b/src/debugger/module.mk
@@ -8,7 +8,7 @@ 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 += \
From ad8e5eb3f7e1ba6b6667bfcb0bf642d46fd2b290 Mon Sep 17 00:00:00 2001
From: Thomas Jentzsch
Date: Thu, 6 Oct 2022 17:04:12 +0200
Subject: [PATCH 3/6] ...and the usual missing new files
---
src/debugger/TimerMap.cxx | 148 ++++++++++++++++++++++
src/debugger/TimerMap.hxx | 249 ++++++++++++++++++++++++++++++++++++++
2 files changed, 397 insertions(+)
create mode 100644 src/debugger/TimerMap.cxx
create mode 100644 src/debugger/TimerMap.hxx
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..8fa3427a2
--- /dev/null
+++ b/src/debugger/TimerMap.hxx
@@ -0,0 +1,249 @@
+//============================================================================
+//
+// 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