Showing posts with label trace. Show all posts
Showing posts with label trace. Show all posts

Sunday, April 14, 2019

A 6809 Single Board Computer: Instruction Trace/Step Function

In my JMON monitor for the 6502, I implemented a trace or step function where you can execute code one instruction at a time and see the results of execution on the CPU registers. This is very useful for debugging, particularly as this implementation supports stepping through ROM code, something that can't be done with breakpoints.

For my 6809 single board computer I wondered if I could do the same for the 6809 processor. After some thought, it looked feasible, although more challenging as the 6809 has a larger and more complex instruction set than the 6502.

The basic idea is to take the instruction to be executed and store it in a buffer in RAM. The values of all CPU registers from the previous instruction trace are restored and the instruction is executed. A jump instruction is placed right after the traced instruction that goes back to the trace program. After execution, the new register values can be displayed and saved. This allows running code that is in ROM, as it gets copied to RAM when executed.

To implement this requires knowing the length of each instruction, as they can vary on the 6809 from one to five bytes depending on the instruction and addressing mode. From my previously written disassembler I already had code that could determine the instruction length and even disassemble it.

A wrinkle in this approach is handling instructions which cause a change in the flow of execution, such as a JMP (jump) instruction which would not return if simply executed in the buffer. We need to handle this instruction as a special case. We can examine the destination address and update the saved program counter accordingly. We don't need to actually execute it since it changes no other registers than the PC. We do need to check for and handle both direct (8 bit) and extended (16-bit) jump instructions. For direct, the destination address needs to be calculated by combining the instruction operand with the current value of the direct page (DP) register.

Jump to subroutine (JSR) in another special case. Here we need to push the return address on the stack (using the saved value of the stack pointer that we trace with) and then calculate the new PC value.

Similarly, we can handle BRA/LBRA and BSR/LBSR like JMP and JSR but the effective address needs to be calculated by adding the offset to the current PC value. Again we don't need to execute the instruction, just update the new PC value.

The interrupt related instructions SYNC, CWAI, SWI, SWI2, and SWI3 can all be simulated by pushing registers on the stack and setting the PC to the appropriate interrupt vector.

RTS and RTI are simulated by pulling registers (in the case of RTI) or just the PC (for RTS) off the stack. One wrinkle is that in the 6809 we need to check the E (Entire) flag in the condition code register to know of we should restore all registers from the stack.

Conditional branches are a little more tricky. They can transfer control to two different places depending on whether the condition is true or not. These are handled by writing into the execution buffer both the instruction being traced as well as different jumps depending on whether the branch is taken or not. The branch destinations can update the PC accordingly. The code in the buffer looks like below:

XXXX XX 03           Bxx $03 (Taken)         ; Instruction being traced
XXXX 7E XX XX        JMP BranchNotTaken
XXXX 7E XX XX Taken  JMP BranchTaken

We execute the instruction in the buffer and let it perform the test. We need to handle all the possible branch instructions, both short and long versions.

TFR (transfer) and EXG (exchange) instructions are okay to run except the cases where the source or destination is the PC. We need to handle those cases manually since it would otherwise change the flow of control.

Similarly, PSHS/PHSU and PULS/PULU could potentially include the PC in the list of registers pushed or pulled. Currently I just check for this case, remove the PC from the list of registers, and warn the user in this case that it is not fully handled yet.

Indexed addressing poses a challenge: we need to handle an instruction that changes flow of control like JMP 1,X with an arbitrary index addressing mode. It might also produce side effects in the case of instructions like JMP 1,X++. These are handled using a trick: instead of JMP, we run a LEAU instruction with the same indexed operand. Then we examine value of U, which should be the new PC. Currently the code can't handle addressing modes that change the U register like JMP ,U++.

A final challenge is PCR relative index addressing. If we move the instruction to the buffer to execute it, the PC relative address is now wrong. I thought about this, and it should be possible to adjust the offset based on the difference between the original instruction location and the address of the buffer where it will be run. This would get a little complicated, so I didn't implement it (yet). For now I just ignore it and display a message at run time that it is not supported.

After working out most of the logic as pseudocode, I implemented and debugged it. I started with the basic instructions and then added all of the special cases, testing them one at a time. Once done, I tested it with some smaller complete programs.

Finally, I was able to integrate it into the "combined" ROM which also contains the ASSIST09 monitor, disassembler, and Microsoft Basic. I added the trace command as a new ASSIST09 "T" command. It took some shuffling but I was able to just get them all to fit in the 8K ROM.

It hasn't been tested exhaustively, and it can't run BASIC because it uses some PC relative instructions, but it seems to work quite well. Here is some sample output:

>T D000
D000  81 30        CMPA  #$30 
PC=D002 A=FF B=FF X=FFFF Y=FFFF S=FFDF U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D002  25 04        BCS   $D008 
PC=D004 A=FF B=FF X=FFFF Y=FFFF S=FFDF U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D004  81 3C        CMPA  #$3C 
PC=D006 A=FF B=FF X=FFFF Y=FFFF S=FFDF U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D006  25 C0        BCS   $CFC8 
PC=D008 A=FF B=FF X=FFFF Y=FFFF S=FFDF U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D008  30 1F        LEAX  $1F ,X
PC=D00A A=FF B=FF X=FFFE Y=FFFF S=FFDF U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D00A  34 50        PSHS  U,X
PC=D00C A=FF B=FF X=FFFE Y=FFFF S=FFDB U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D00C  0F 41        CLR   $41 
PC=D00E A=FF B=FF X=FFFE Y=FFFF S=FFDB U=FFFF DP=FF CC=11111000 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D00E  CE C0 E7     LDU   #$C0E7 
PC=D011 A=FF B=FF X=FFFE Y=FFFF S=FFDB U=C0E7 DP=FF CC=01111110 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D011  0F 42        CLR   $42 
PC=D013 A=FF B=FF X=FFFE Y=FFFF S=FFDB U=C0E7 DP=FF CC=01111110 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT  
D013  33 4A        LEAU  $0A ,U
PC=D015 A=FF B=FF X=FFFE Y=FFFF S=FFDB U=C0F1 DP=FF CC=01011001 (EFHINZVC)
PRESS TO CONTINUE, TO QUIT 

References

  1. https://github.com/jefftranter/6809/tree/master/sbc/trace
  2. https://github.com/jefftranter/6809/tree/master/sbc/combined
  3. https://github.com/jefftranter/6809/tree/master/sbc/disasm
  4. https://github.com/jefftranter/6502/tree/master/asm/jmon

Saturday, July 14, 2012

JMON Trace Function


I've implemented one of the features missing from JMON, and one that I had been relying on Krusader's mini monitor for: an instruction trace function. This function allows you to single step through a program, see a disassembly of the current instruction, and see the values of the hardware registers. This feature is almost indispensable for debugging code. Krusader does this but I wanted to write my own.

I can think of at least three ways to single step code. One is a hardware solution. The Apple 1 manual actually shows a circuit that does this, and presumably Woz used this circuit to debug the Apple 1. This approach is very useful for bringing up new hardware that may not yet be working, but it is complex and expensive.

A second approach is the use the breakpoint instruction (BRK). By inserting a BRK instruction in code, when executed it can jump to the break vector which can run code which shows the current values of the registers. You can then replace the BRK with the original instruction and place a BRK at the next instruction, and continue execution. A disadvantage of this approach is that it only works for code in RAM and not ROM, since the program memory must be written to with the BRK instruction.

A third approach, and the one I used, is to look at the next instruction to be executed, copy it to a buffer, and execute it in place. The instruction can be followed by code that jumps back into the trace code in the monitor program. This approach has the advantage of working even for code that is in ROM.

To make this work there are a couple of wrinkles. Some instructions change the flow of control (e.g. JMP and JSR) and would jump out of the trace code. To trace these I simulate instead of execute them, doing the equivalent of what the instruction would do. For example. a JMP instruction just needs to update the value of the program counter. These special instructions are JMP, JSR, RTS, RTI, and BRK. Similar are the branch instructions. Initially I thought I would simulate these, looking at the CPU registers to determine whether the branch was taken or not. This would get a little complex as there are about 8 branch instructions to simulate, each checking a different condition. Instead I use a simpler approach that can use the same code for all branches. I execute the branch instruction in the buffer, but I adjust the branch destination so it jumps to a known location in the trace code. Whether the branch is taken or not, it stays under control of the trace code.

My trace function produces similar output to Krusader's mini monitor. You can set the register values use the Register command, including the Program Counter. The "." command traces/executes one instruction.

The breakpoint feature of JMON works in conjunction with this. If a breakpoint is hit, it updates the register values and you can single step as desired. You can also use the Go command to execute from the current program counter address.

It took a little work to get all of this correct for all instructions. It leveraged the previous work on the disassembler and mini assembler. For example, I needed to know the length of each instruction and this information was already available in tables used by the disassembler.

A sample session is shown below. Commands entered by the user are in bold. The actual trace commands "." are not echoed.

JMON MONITOR 0.99 BY JEFF TRANTER
? R
A-00 X-00 Y-00 S-0140 P-00 ........
0000   00          BRK
A-00 X-00 Y-00 S-0140 P-00 ........
PC-6000
? R
A-00 X-00 Y-B0 S-0140 P-00 ........
6000   EA          NOP
A-Esc
? A-00 X-00 Y-B0 S-0140 P-30 ..-B....
6001   A9 01       LDA   #$01
? A-01 X-00 Y-B0 S-0140 P-30 ..-B....
6003   A0 01       LDY   #$01
? A-01 X-00 Y-01 S-0140 P-30 ..-B....
6005   A2 01       LDX   #$01
? A-01 X-01 Y-01 S-0140 P-30 ..-B....
6007   08          PHP
? A-01 X-01 Y-01 S-013F P-30 ..-B....
6008   68          PLA
? A-30 X-01 Y-01 S-0140 P-30 ..-B....
6009   38          SEC
? A-30 X-01 Y-01 S-0140 P-31 ..-B...C
600A   18          CLC
? A-30 X-01 Y-01 S-0140 P-30 ..-B....
600B   69 01       ADC   #$01
? A-31 X-01 Y-01 S-0140 P-30 ..-B....
600D   F8          SED
? A-31 X-01 Y-01 S-0140 P-38 ..-BD...
600E   69 10       ADC   #$10
? A-41 X-01 Y-01 S-0140 P-38 ..-BD...
6010   D8          CLD
? A-41 X-01 Y-01 S-0140 P-30 ..-B....
6011   4C 17 60    JMP   $6017
? A-41 X-01 Y-01 S-0140 P-30 ..-B....
6017   20 15 60    JSR   $6015
? A-41 X-01 Y-01 S-013E P-30 ..-B....
6015   EA          NOP
? A-41 X-01 Y-01 S-013E P-30 ..-B....
6016   60          RTS
? A-41 X-01 Y-01 S-0140 P-30 ..-B....
601A   A9 28       LDA   #$28


As part of my testing I modified the code to stay in trace mode and ran the Woz monitor and Apple BASIC under the trace code. They executed correctly, although a little slower than normal.

The code supports 65C02 instructions without any extra coding except the BBR and BBS commands which would need some additional branch instruction support. Some 65816 instructions should work but the new instructions which change flow of control would fail and it doesn't understand the variable length of instructions depending on the CPU mode (but it would be quite straightforward to add).

This new trace function should prove useful for debugging future code I write. As always, the code can be found here.