This document describes the major modifications and additions to the monitor program made by me.
The new monitor V4.8 is based on V3, which includes the faster 9600 Baud software UART.
All existing features are still available, most new ones use the REG key as a prefix, which
is displayed as SHIFT
on the LEDs.
Though the new features take advantage of the LCD and a terminal connection, they also work
with the bare board.
- Register editing
- Disassembling opcode on LCD
- Dumping info to the terminal
- Interfacing to user programs
- Error handling
- Stepping and breakpoints
All data and address registers can be modified, too. To do this press the DATA button when displaying a register. The display now shows dots and the new value can be entered with the hex keys.
To exit register input, press any other key, or REG to display/edit another register.
Press DATA once to input a new long value, press DATA twice to input a word value (leaving the upper bits intact) or press DATA thrice to input a byte value (dito).
The dots indicate which part of the data register will be changed.
Address register now also display all 32 bits like data registers, but only the entire value can be input.
A7 is the user stack pointer when in user mode or the system stack pointer when in system mode.
When displaying or editing an address register you can go to the memory byte pointed to by pressing the ADDR key. Now its contents becomes the current address and you can modify it or start program execution etc.
For example, if you want to dump the stack to the terminal, press REG, A7, ADDR, and DUMP.
The status register cannot be modified. If you really want to do so, you can edit monitor variable
at 002ea
where the status register is stored.
The format of status register display can be selected with the monitor variable show_sr
at
002fa
. Possible formats:
Value | Name | Part | Format |
---|---|---|---|
0 | classic | CCR only | using 0/1 for XNZVC |
1 | numeric | entire SR | 16 bit hexadecimal |
2 | symbolic | entire SR | formatted as in terminal dump, see below |
The instruction at the current address is displayed as disassembly on the LCD automatically. The details of the disassembly format are described at the end of this document.
When advancing through the memory forward (via the + key), the start of the instruction in memory can be determined correctly, so even for multiword instructions you can navigate through each byte of the instruction with the + and - keys and even modify the operands and instantly see the effect in the disassembly.
However, when you go beyond a multiword instruction with + and immediately back with -, the beginning of the instruction is lost and you see the operand interpreted as an instruction. In this case, you have to go back far enough with the - key to synchronize again.
If you want to control the LCD yourself and avoid that the monitor overwrites the LCD
with the disassembly, you can switch monitor output off by writing 0 into monitor variable
disasm_on_lcd
at address 00270
.
The monitor currently assumes a 16x2 LCD, but other sizes can be used, too, and you have to set the
size in lcd_width
at 00300
and lcd_lines
at 00302
once after power-up.
By pressing the keys REG and DUMP you can dump the registers to the terminal. Typical output looks like
D0: 00000001 00000001 00000000 F79FA9DE FFF77FF9 FD652CBE C7F8DF59 9FF6FF7E
A0: 00001A5E 00001A70 9EFF9B7B FF8FEFFB 7FFEEFF6 FCBE3AFE 0001FFAC 00020000
SR: A704 TS7--Z-- USP: 0001FC00 SSP: 00020000
0000045C: 670A BEQ.S $000468
The first line shows the data registers D0-D7, the second line the address registers A0-A6 and the currently active stack pointer, the third line the status register numerically and in symbolic form (in the example: trace and system mode, interrupt level 7, zero flag) and both stack pointers. The fourth line shows the PC and the current instruction as machine code and disassembly.
By pressing the keys REG and REL you can list machine code starting at the current PC as disassembly to the terminal. This will advance the current (displayed) address accordingly, so pressing those keys again will list the following instructions, but the saved PC is not modified, so you can return to it with the PC key.
The number of instructions listed can be set with the monitor variable disasm_lines
at address 0026c
(see monitor4x.inc
)
Similarily, the number of lines output by the standard DUMP command can be set with the
monitor variable hexdump_lines
at address 0026e
.
If a breakpoint is set at the instruction disassembled, an asterisk is printed after the address.
By pressing the keys REG and LOAD you can list all dynamic breakpoints (see below) as disassembly (one line per breakpoint) to the terminal.
By pressing the keys REG and + you can send a line feed to the terminal.
There are several useful routines in ROM, which should be available to the user. However, when recompiling the monitor, their entry addresses change and user programs using them would have to be modified accordingly. So I put a jump table at the very beginning of the monitor code which delegates to the actual ROM routines. The position of the jump table itself will stay fixed in future versions of the monitor, so user programs don't have to be adapted.
In the directory include
are files for C monitor4x.h
and assembler monitor4x.inc
defining symbolic names for the monitor routines and for useful system variables.
Look in the examples
directory how those routines can be called from your program.
The functions defined here use the jump table mentioned above, so your programs using them will still work without re-compilation with future versions of the monitor.
There are some monitor variables (also defined in the include files mentioned above) which allow configuration of monitor behavior. These include LCD size, SR display mode, number of output lines for dumping, and UART speed.
These variables are defaulted at power-up and you can modify them simply with the memory editor, or by a user program. Their contents survive RESET.
In C, these variables are pointers, so they must be accessed with the *
operator.
Be careful not to modify other RAM locations in the range 00200
to 003ff
!
The original CSTART library C:/Ide68k/Lib/cstart.asm
defines basic OS functions like
__getch
and __putch
to be used by the simulator. I extended it to call the terminal
functions for sending and receiving single characters when running on the 68008 Kit.
The new CSTART library is now at Lib/cstart_sbc.asm
which you should use in your
C project files instead of the original.
This new CSTART determines if it's running on the Ide68k simulator or on the actual 68008 Kit and
calls the proper I/O routines accordingly. When you use it in your C .prj
file instead of
the old CSTART library, the compiled HEX file will run on the Ide68k simulator as well as
on the actual 68008 Kit.
The new CSTART also contains C entries to monitor routines via the jump table mentioned above. These entries could have been put into their own library, but have been included here for simplicity. Of course these routines won't work in the Ide68k simulator.
The original library std68k.lib
uses TRAP #0
to implement setjmp/longjmp
.
This conflicts with 68008 Kit use of TRAP #0
to return to the monitor.
So the library has been patched to use TRAP #2
instead and is now at Lib/std68k_patched.lib
.
You need this patch only if
- you use
setjmp/longjmp
at all in your C code - you want to single step through
setjmp/longjmp
Have a look at the sample project examples/test_setjmp.prj
.
Handlers for all standard exceptions have been added to avoid crashes. They simply display the exception type encountered. You can press the PC key to see the instruction (or the next depending on exception type) which caused the exception. Handled are:
Exception | Number | LED display | Actually used for |
---|---|---|---|
Bus error | 2 | Err bUS |
|
Address error | 3 | Err Addr |
|
Illegal instruction | 4 | Err ILLE |
|
Division by zero | 5 | Err div0 |
|
CHK out of bounds | 6 | Err CHk |
|
TRAPV overflow | 7 | Err trPv |
|
Privilege violation | 8 | Err Priv |
|
Trace | 9 | Stepping | |
Line A emulation | 10 | Err LinA |
|
Line F emulation | 11 | Err LinF |
|
Interrupt level 2 | 26 | IRQ, 100 Hz tick | |
other interrupts | 24-31 | Err Intr |
|
TRAP #0 | 32 | Return to monitor | |
TRAP #1 | 33 | Static breakpoint | |
TRAP #3 | 35 | Dynamic breakpoint | |
other traps | 32-47 | Err trAP |
Since those exception vectors are in RAM you can still redefine them for your needs.
Even with those handlers your program can still crash, especially when it's running in system mode and the stack pointer is moved beyond RAM.
Please note, that all stepping functions and breakpoints work transparently in both system and user mode, using the correct stack pointer.
The STEP key lets you execute one single instruction at a time. When you step a JSR
or
BSR
instruction, it goes into the subroutine and lets you execute every single instruction.
(We call this stepping into)
Since V4.5 you can also step with the USER key, but this steps over subroutine calls and stops at the instruction thereafter.
In fact, stepping stops when the stack pointer has reached the level (or higher) it had before pressing USER. So when an instruction doesn't modify the stack pointer, stepping over stops immediately. However, when the instruction pushes something onto the stack, stepping continues until the data is popped off the stack, regardless if it was a return address or something completely different.
For example, in this code fragment (taken from examples/test9.asm
)
00000410: 3603 MOVE.W D3,D3
00000412: 4878 04D2 PEA $04D2.W
00000416: 42A7 CLR.L -(A7)
00000418: 610E BSR.S $000428
0000041A: 3804 MOVE.W D4,D4
0000041C: DEFC 0008 ADDA.W #$0008,A7
00000420: 3A05 MOVE.W D5,D5
stepping over at 00418
will step through the subroutine call and stop at 0041A
. However,
stepping over at 00412
will step through the parameter pushes, the call, and the parameter
cleanup in 0041C
and stop at 00420
, thus covering the entire call sequence.
If during stepping over a subroutine a trap is encountered, your program will stop there as usual.
Note that stepping over is much slower than normal execution, since a trace exception is handled after each processor instruction.
For your convenience, you can also skip over TRAP #0
or TRAP #1
(see below) instructions
with the USER key. Pressing STEP here would enter the trap handler, which is probably
not what you want to do.
The new step out command (since V4.6) is invoked by pressing REG USER and steps until a subroutine returns and stops after the original call. (Or more generally, stops, when the stack pointer increases)
So in the example above, when you stepped into the subroutine at 00418
by pressing STEP,
before V4.6 you would have to step through the entire subroutine in single steps, but now you can
press REG USER to do this automatically and the monitor will stop at 0041A
, after
the call.
When you are in the main program (so the stack is empty) step out displays an error message
toP SP
.
The new step continue command (since V4.7) is invoked by pressing REG STEP and starts program execution at the current address like GO but in trace mode, so it is much slower. This command has been added for completeness and may occasionally be useful since it stops at dynamic breakpoints (see below) in ROM, in contrast to GO.
You can use TRAP #1
instructions (or in C the _trap(1)
pseudo-function) to include breakpoints
in your code. On default, these breakpoints are disabled and your program skips over them. But
by the key combination REG COPY you can enable/disable them (displaying trP1 On
or
trP1 OFF
) and the monitor is invoked when your program encounters an active breakpoint,
just like TRAP #0
.
When you step through your program, and you see that the next instruction is a TRAP
,
you should step over it with the USER key, the STEP key would enter the trap handler.
If you accidently do so, you still can recover by
- pressing GO in a
TRAP #0
or enabledTRAP #1
, which actually executes the trap, so it stops afterwards - manually stepping through a disabled
TRAP #1
with STEP (just 3 instructions until afterRTE
), don't use GO here, it would start program execution and thus miss the instruction after theTRAP #1
Since V4.7 you can use dynamic breakpoints, too. These breakpoints can be set ad hoc at arbitrary memory addresses and program execution stops when a dynamic breakpoint is encountered.
You can set a breakpoint by pressing REG INS in the memory editor. A small square appears between address and data on the LED to indicate a breakpoint. Note that breakpoints can only be set at even addresses. You should also make sure that you set a breakpoint at the first byte of an actual instruction and not at an operand, otherwise strange effects may result when the monitor patches the code.
Press REG INS again to remove the breakpoint at the current address. Or press REG DEL to remove all dynamic breakpoints at once.
A maximum of 8 breakpoints can be set, they survive RESET and downloading programs. Breakpoints are not copied or moved when you use the commands COPY, INS or DEL.
When you try to set a breakpoint and the small square doesn't appear, either all 8 breakpoints have already been set or you are at an odd address.
Press REG LOAD to list all breakpoints to the terminal. Breakpoints are marked with an asterisk after the address in disassembler listings.
The monitor handles dynamic breakpoints in 2 different ways:
When you press GO, the breakpoint locations are patched with TRAP #3
instructions and
execution starts at full speed until encountering the trap, which restores the original
instructions.
When you step over, step out or step continue, the program is executed in trace mode and breakpoints are checked by the monitor after each step. Since no code patching is needed here, breakpoints even work in ROM code.
Command | Key(s) | Executes | Speed | Breakpoint impl. | Stop at BP in ROM |
---|---|---|---|---|---|
Go | GO | indefinitely | fast | TRAP #3 |
no |
Step into | STEP | one instruction | - | - | - |
Step over | USER | while SP<orig. | slow | monitor check | yes |
Step out | REG USER | until SP>orig. | slow | monitor check | yes |
Step continue | REG STEP | indefinitely | slow | monitor check | yes |
The monitor now is able to compute both 8 bit and 16 bit relative addresses. The principle is when you are editing an odd address, REL computes and writes an 8 bit offset and when you are editing an even address, REL computes a 16 bit offset, which is written to the current and the following byte.
This works for all 68000 instructions which use relative addressing, including Bcc
(both
8 and 16 bit), DBcc
, and the addressing modes PC-relative with offset (d16,PC)
and
PC-relative with index and offset (d8,PC,Xn)
.
For example, you are at address 006AC
and want to input a branch BGE
to address 00A80
.
You type ADDR, 6, A, C, DATA, 6, C, which inserts the upper byte
of the opcode forBGE
. The LCD shows BGE.S
and a random address. Now go to the next address
with + (an odd address) and press REL. Enter the destination address 00A80
and
press GO. The LED now shows the 8 bit offset D2
at address 006AD
, but the LCD shows
BGE.S $000680
, which is clearly wrong. The reason is that the distance to the target is too
big to fit into 8 bits, so you have to use a 16 bit branch.
So (still at the same address 006AD
) enter 00
(which marks a long branch in 68000
machine code) and press + to go to the next address 006AE
(an even address).
Now press REL and enter the destination again. The LCD now shows the correct instruction
BGE.W $000A80
(note the .W
here) and the LED shows 03
at 006AE
and D2
at 006AF
which is the correct 16 bit offset.
When you are in the memory editor and the (even) address displayed contains a (32 bit) pointer, you can set the current address to the pointer by pressing REG ADDR.
The old monitor had system variables at 01f000
and the system stack at 010000
, in the middle
of the RAM address range. To have the maximum amount of RAM available for user programs, I changed
the memory layout as follows:
- All global variables have been moved to memory below
000400
(the starting address for user code). This memory region is normally used for user interrupt vectors, which the 68008 Kit doesn't use. Since the monitor variable area starts at000200
, the first 64 user interrupt vectors from000100
to0001ff
could still be used by additional hardware without interference. - The initial system stack pointer has been moved to
020000
, the top of RAM. So if your program runs in system mode, the SSP can grow from there and use the entire available RAM. - The initial user stack pointer has been moved to
01fc00
, 1 kByte below the SSP. The monitor routines just need about 1/2 kByte of stack space, so the system stack will not run into the user stack range below. So if you decide to run your code in user mode, you'll have plenty of stack space below the system stack space.
This gives you a contiguous block of 126 kByte RAM in user mode or 127 kByte RAM in system mode.
A lot of former global variables have been made local, the automaton states have been given symbolic names and code duplications (register display) have been removed.
The duplicate user registers have been removed and the correct stack pointer (user or system) is displayed now depending on machine mode.
Key code mapping now via table.
Many comments added.
A crude 7 segment font for the full alphabet has been created and text strings can now be
displayed on the LED. See test_led.asm
how to use it. Most characters work fairly well, but
a few like K,M,W,X are really ugly, but at least guessable...
The disassembler uses standard Motorola syntax and supports only 68000/68008 instructions, no
later 68k instruction sets or addressing modes are supported. Illegal instructions or
adressing modes are marked with a question mark ?
or a caret ^
to indicate which part of
the instruction is illegal.
.?
indicates an invalid size field:E9E2 ASL.? D4,D2
^
indicates an invalid size for an address register:D008 ADD.B ^A0,D0
?ea
indicates a reserved effective address bit pattern:D0BE ADD.L ?ea,D0
?
before an operand indicates an illegal addressing mode for this instruction:41C0 LEA ?D0,A0
?op
indicates an undefined opcode:4100 DC.W $4100 ; ?op
Undefined opcodes or line A and line F instructions are written as DC.W
pseudo-instructions.
PC-relative addresses and branch targets show the resulting absolute address.
All addresses which are actually output to the address bus are displayed with 24 bit/6 hex digits. These are branch targets, PC-relative addresses and long absolute addresses.
The disassembler has been tested with all 216 possible opcodes and verified against the actual behavior of the 68008 chip to each possible opcode and works correctly.
- Fix: Display correct disassembly after LOAD and COPY
- New: Monitor variable
show_sr
at002fa
to switch SR and CCR display - New: Key REG COPY to toggle
TRAP #1
breakpoints - New:
monitor_scan
function to read keyboard - New: Compute 8 and 16 bit offsets for all branches and PC-relative addressing modes via REL
- Fix: Error handler on odd PC
- New: Display memory pointed to by address register
- New: Step over subroutines and traps with USER key
- New: Enter partial data into data register
- New: display format for SR; 0, 1, or 2 in
002fa
- New: variable LCD size in
lcd_width
at00300
andlcd_lines
at00302
- New: flag
lcd_present
at00304
- New: variable shift size for INS and DEL block move at
00306
- New: Step out of subroutine call
- Fix: Compatibility patch for
setjmp/logjmp
instd68k.lib
- Fix: avoid duplicates when including
monitor4x.h
twice - Fix: use macros instead of consts for monitor variables in C
- Enh: more documentation, also as PDF
- New: dynamic breakpoints
- Fix: disassembler TRAP syntax
- New: print newline to terminal
- New: Step continue
- Fix: handlers for unused traps and interrupts
- Fix: clean up project structure and prepare for GIT
- New: go to pointer address displayed
- Enh: documentation
- Dumping to terminal
- DUMP dump memory to terminal (as before)
- REG REL list disassembly to terminal
- REG DUMP dump registers to terminal
- REG LOAD list dynamic breakpoints to terminal
- REG + print a newline to terminal
- Register editing
- REG Xn DATA input new (long) value for register Xn
- REG Dn DATA DATA input new word value for data register Dn
- REG Dn DATA DATA DATA input new byte value for data register Dn
- REG An ADDR set current memory address from address register An
- REG ADDR set current memory address from pointer in memory
- Stepping
- STEP step into subroutines and traps (as before)
- USER step over subroutines and traps
- REG STEP step continue
- REG USER step out of a subroutine
- Breakpoints
- REG COPY toggle all
TRAP #1
static breakpoints - REG INS toggle dynamic breakpoint at current address
- REG DEL delete all dynamic breakpoints
- REG LOAD list dynamic breakpoints to terminal
- REG COPY toggle all
- Dumping to terminal
- ⎙HEX dump memory to terminal
- SHIFT ⎙ASM list disassembly to terminal
- SHIFT ⎙REG dump registers to terminal
- SHIFT ⎙BRK list dynamic breakpoints to terminal
- SHIFT ⎙LF print a newline to terminal
- Register editing
- SHIFT Xn EDIT input new (long) value for register Xn
- SHIFT Dn EDIT EDIT input new word value for data register Dn
- SHIFT Dn EDIT EDIT EDIT input new byte value for data register Dn
- SHIFT An USE_A set current memory address from address register An
- SHIFT USE_A set current memory address from pointer in memory
- Stepping
- INTO step into subroutines and traps
- OVER step over subroutines and traps
- SHIFT CONT step continue
- SHIFT OUT step out of a subroutine
- Breakpoints
- SHIFT ±TRP1 toggle all
TRAP #1
static breakpoints - SHIFT ±BRK toggle dynamic breakpoint at current address
- SHIFT ⨯BRK delete all dynamic breakpoints
- SHIFT ⎙BRK list dynamic breakpoints to terminal
- SHIFT ±TRP1 toggle all