Merge branch 'master' into feature/filesystem

This commit is contained in:
Stephen Anthony 2022-10-06 21:32:09 -02:30
commit b5e484f0e7
16 changed files with 787 additions and 12 deletions

View File

@ -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!

View File

@ -45,6 +45,7 @@
<li><a href="#PseudoRegisters">Pseudo-Registers</a></li>
<li><a href="#Watches">Watches</a></li>
<li><a href="#Traps">Traps</a></li>
<li><a href="#Timers">Timers</a></li>
</ul>
</li>
<li><a href="#SaveWork">Save your work!</a></li>
@ -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.</p></p>
<h4><a name="Timers">Timers</a></h4>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</br>
<h3><a name="SaveWork">Save your work!</a></h3>
<p>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.</p>
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 &lt;xx&gt;
delTrap - Delete trap &lt;xx&gt;
delTimer - Delete timer &lt;xx&gt;
delWatch - Delete watch &lt;xx&gt;
disAsm - Disassemble address xx [yy lines] (default=PC)
dump - Dump data at address &lt;xx&gt; [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 &lt;xx&gt; scanlines (default=1)
step - Single step CPU [with count xx]
stepWhile - Single step CPU while &lt;condition&gt; 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 &lt;condition&gt; 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.</li>
<li><b>Disassemble @ current line</b>: 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.</li>
<li><b>Set timer @ current line</b>: Set a timer point using the current
disassembly line's address and bank</li>
<li><b>Show tentative code</b>: Allow DiStella to do a static analysis/disassembly.</li>
<li><b>Show PC addresses</b>: Show Program Counter addresses as labels (where there

View File

@ -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<uInt16>(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<uInt16>(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<int>(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 <xx>",
"Example: delTimer 0",
true,
false,
{ Parameters::ARG_WORD, Parameters::ARG_END_ARGS },
std::mem_fn(&DebuggerParser::executeDelTimer)
},
{
"delTrap",
"Delete trap <xx>",
@ -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 <xx>",
"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]",

View File

@ -101,7 +101,7 @@ class DebuggerParser
std::array<Parameters, 10> parms;
std::function<void (DebuggerParser*)> executor;
};
using CommandArray = std::array<Command, 104>;
using CommandArray = std::array<Command, 110>;
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();

148
src/debugger/TimerMap.cxx Normal file
View File

@ -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);
}

250
src/debugger/TimerMap.hxx Normal file
View File

@ -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 <cmath>
#include <map>
#include <deque>
#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<uInt32>(myList.size()); }
/** Update timer */
void update(const uInt16 addr, const uInt8 bank,
const uInt64 cycles);
private:
using TimerList = std::deque<Timer>; // makes sure that the element pointers do NOT change
using TimerPair = std::pair<TimerPoint, Timer*>;
using FromMap = std::multimap<TimerPoint, Timer*>;
using ToMap = std::multimap<TimerPoint, Timer*>;
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

View File

@ -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);

View File

@ -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

View File

@ -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)
{

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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<unique_ptr<Expression>> myTrapConds;
StringList myTrapCondNames;
TimerMap myTimer;
#endif // DEBUGGER_SUPPORT
bool myGhostReadsTrap{false}; // trap on ghost reads

View File

@ -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 = "<group>"; };
DC7A24DD173B1DBC00B20FE9 /* FileListWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileListWidget.cxx; sourceTree = "<group>"; };
DC7A24DE173B1DBC00B20FE9 /* FileListWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileListWidget.hxx; sourceTree = "<group>"; };
DC7C83D428EF2E080097B5AE /* TimerMap.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimerMap.cxx; sourceTree = "<group>"; };
DC7C83D528EF2E080097B5AE /* TimerMap.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimerMap.hxx; sourceTree = "<group>"; };
DC8078DA0B4BD5F3005E9305 /* DebuggerExpressions.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = DebuggerExpressions.hxx; sourceTree = "<group>"; };
DC8078E60B4BD697005E9305 /* UIDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = UIDialog.cxx; sourceTree = "<group>"; };
DC8078E70B4BD697005E9305 /* UIDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = UIDialog.hxx; sourceTree = "<group>"; };
@ -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 */,

View File

@ -857,6 +857,7 @@
<ClCompile Include="..\..\debugger\gui\TrakBallWidget.cxx">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-NoDebugger|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\debugger\TimerMap.cxx" />
<ClCompile Include="..\..\emucore\Bankswitch.cxx" />
<ClCompile Include="..\..\emucore\Cart3EPlus.cxx" />
<ClCompile Include="..\..\emucore\Cart3EX.cxx" />
@ -2052,6 +2053,7 @@
<ClInclude Include="..\..\debugger\gui\TrakBallWidget.hxx">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-NoDebugger|x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="..\..\debugger\TimerMap.hxx" />
<ClInclude Include="..\..\debugger\TrapArray.hxx">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-NoDebugger|x64'">true</ExcludedFromBuild>
</ClInclude>

View File

@ -1193,6 +1193,8 @@
</ClCompile>
<ClCompile Include="..\..\common\FSNodeREGULAR.cxx">
<Filter>Source Files</Filter>
<ClCompile Include="..\..\debugger\TimerMap.cxx">
<Filter>Source Files\debugger</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
@ -2434,6 +2436,8 @@
</ClInclude>
<ClInclude Include="..\..\common\FSNodeREGULAR.hxx">
<Filter>Header Files</Filter>
<ClInclude Include="..\..\debugger\TimerMap.hxx">
<Filter>Header Files\debugger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
@ -2446,4 +2450,4 @@
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>