[Oberon] Debugging FPGA oberon via serial line?
schierlm at gmx.de
Fri May 29 17:37:07 CEST 2020
Am 27.01.2019 um 00:20 schrieb Michael Schierl:
> From other (older) systems, I know it is possible to debug them via
> serial line, so I was thinking how much the FPGA oberon system needs to
> be changed (hardware side/software side) to accommodate this. RS232 is
> supported, so you could use one FPGA system (or emulator) to debug the
> other one. And I was wondering if anybody already tried doing something
> like this.
Yes I know this email is more than a year old, but I finally found the
time to clean up and publish the various debugging hacks I built so far:
No modifications of the hardware are needed (except that you need to
connect two (virtual) FPGAs via serial line), but the compiler needs
some heavy patching.
> - Set and remove software breakpoints while the system is running/idle
> - When software breakpoint or trap is hit, save processor state and
> allow debugging via serial port (-> suspended)
> - Read and write registers and memory while the system is suspended
> - Set and remove software breakpoints while the system is suspended
> - Continue execution when the system is suspended
All of this works.
> More advanced features would be single stepping (can be emulated by
> having a look at the instruction and setting software breakpoints at all
> following locations),
Is available, both StepInto and StepOver. Note that StepOver does not
check if the next instruction is a taken branch without link, so if you
StepOver it, you will probably never see it stopping again.
> or memory breakpoints. Also to be able to enter
> the debugger by pressing a hardware button on the FPGA (similar to the
> current reset functionality, but not resetting).
> And of course symbolic debugging :)
I added Symbolic debugging (on the same machine) previously in
but due to testing the kernel debugger, I found and fixed some bugs. I
which can be used to inspect stack frames on the same machine. The
kernel debugger can use both as well (and has the advantage that the
garbage collector cannot come and collect your precious heap object
while you are following some pointers along).
> Software breakpoints should be easy, just replace instruction by BL to a
> fixed address.
BL does not work, as it clobbers the flags, but a branch to an
individual trampoline for each breakpoint works fine.
> Restore the instruction before returning.
Unless the breakpoint got disabled, of course. And when it is still
active, set a temporary breakpoint on the next instruction to re-enable
> For catching
> TRAPs, overwrite the TRAP address.
Just add a breakpoint on the TRAP address 20H, so no extra code required.
Breakpoints can be set on raw addresses or on entry points of command
PROCEDURES. If you need anything else, just recompile the module and
call an empty command PROCEDURE where you want your breakpoint.
> Saving processor state might be tricky, especially SYSTEM.H and the
Flags are not very tricky (just store them first). SYSTEM.H is only
clobbered by multiply/divide instructions so it is quite easy.
> and maybe you have problems since you need at least one spare
> register to be able to save the registers to a fixed location in memory.
I also need the spare register to restore flags. As my compiler uses R13
for various other purposes (mostly lazyness), I decided to reserve R11
as a scratch register. R10 is used to point to the debug context, so
only 10 general registers are left. All Project Oberon modules except
one PROCEDURE (TextFrames.Open) can work with only 10 general registers,
so TextFrames.Mod also needs patching.
> You will also need to switch stack to a second stack before the stack is
> accessed by the code.
Apparently that is not required, as the code will in all cases advance
the stack pointer before using the stack.
> Probably solvable by using a special compiler that
> leaves a register or two unused.
Yes, two of them.
> Restoring SYSTEM.H and Flags is also
> not supported (except by searching for instructions that will result in
> the desired flags or results).
Tricky but possible for almost every value, except for SYSTEM.H =
0FFFFFFFFH or for both N and Z flag set at the same time. But I believe
these states cannot happen in practice.
Not sure if all FPGA implementations act exactly the same, so I added a
DebugStubTest.Mod where you can call DebugStubTest.TestRestoreFlags
which will try all supported combinations and whether the flags get
> Also the question is whether returning to
> the code will not destroy some flags again.
Returning by BR LNK would, but you can dynamically jump back (overwrite
the jump at the end of the stub before every return) using a direct jump.
> And you need to make sure
> not to set breakpoints or break at points where a BL has happened but
> the link register has not been saved yet.
As I don't use BL for breakpoints, that point solved itself automatically.
> So (similar to interrupt)
> probably some hardware support is needed for this. Entering the debugger
> by button also needs hardware support (since the current Reset button
> forgets the PC and some other state).
I did not add any "hardware" support, and in practice I did not need it
> Once in suspend mode, it might be hard to avoid allocating memory, since
> you cannot run the GC which might collect objects that are still used by
> the suspended program. But that should be feasible if the functions are
> basic enough to work with a fixed buffer (e.g. number of breakpoints is
Simple solution: More complex commands (like synchronizing module
metadata) are not available while suspended.
> I guess memory breakpoints are only possible with hardware support for
> them (I have no idea how to emulate them except emulating or setting
> breakpoints at all LOAD/STORE instructions).
-> No memory breakpoints.
> For symbolic debugging, the compiler would have to write some more debug
> symbols. For a start,
> annotates the code position for every instruction; I currently use it
> for creating combined source/assembly listings like
I did not use that at all. Setting breakpoints on command PROCEDURES is
enough for me, and those addresses are in the .rsc files already.
Disassembly listing will show the POS for every trap jump, so you have
some orientation in long disassembly listings, too.
> But you would probably also need some information about the stack layout
> (variable names and types) and register contents for every code
Or instead place markers on the stack that describe the stack layout.
More garbage on stack, but a lot easier than to annotate every code
> as well as information about module/global variables, and
> not-exported record types and record members.
Pragmatic approach: Add non-exported stuff to symbol files, prefix every
name by a "-". Works well for debugging, but has the disadvantage that
even those changes invalidate the symbol file signature. If you don't
want this, it is in a separate patch that you do not need to apply.
Which means you won't be able to inspect non-exported types or
non-exported fields of extension records. But you can't have your cake
and eat it too.
> And you would also need some client to utilize all this information so
> that you can do something like "Debugger.Inspect F.vwr.dsc" :)
I did not add this. You can inspect global variables or a pointer or a
stack frame, and it will print all variables (including arrays, but not
following pointers). To follow a pointer you need to explicitly click
it. Works well enough without parsing strings.
Probably still a bit buggy (although I do not know of any bugs right
now), but at least it works well enough to debug bugs in itself.
More information about the Oberon