UART Monitor Studies

Been doing some digging into how the uart monitor works currently.

 

Will paste a few ascii diagrams I was accumulating on how the components work together…

 

Main Components

+--------------------------------------------------------------------------------------------+ | machine.vhdl                          | |                                                       | | +------------------------------------------------------------------------------------+ | | | monitor_top.v = uart_monitor                            | | | |                                                       | | | | +--------------+                                                       | | | | | monitor_ctrl |                                                       | | | | +--------------+                                                       | | | |                                                       | | | | +--------------+                                                       | | | | | monitor_bus |                                                       | | | | +--------------+                                                       | | | | 1024 x 16 bytes  1024 x 8 bytes      16 x 8 bytes       | | | | +--------------+ +---------------+ +---------------+ +--------------+ | | | | | monitor_mem | | asym_ram_sdp | | asym_ram_sdp | | asym_ram_sdp | | | | | | (monitor.a65)| | (historyram0) | | (historyram1) | | (cpustateram)| | | | | +--------------+ +---------------+ +---------------+ +--------------+ | | | | | | | | +--------------+ | | | | | cpu6502 | | | | | +--------------+                                                      | | | |                                                       | | | +------------------------------------------------------------------------------------+ | |                                                       | +--------------------------------------------------------------------------------------------+
  • The uart-monitor is presently written in verilog, with the parent file named "monitor_top.v"

  • It defines a component called "uart_monitor"

  • "machine.vhdl" makes use of this uart_monitor component, feeds various signals between it and the 4510 cpu.

  • Inside the uart-monitor is a 6502 cpu component (also written in verilog).

  • It runs the code that resides within the "monitor_mem" component (which is just the compiled assembly contained within "monitor.a65")

  • There are various other ram components (e.g., historyram0/1 and cpustateram) that get mapped in a way dictated by the early comment found in “monitor.a65

; monitor memory map ; ; $0000-$00ff - zero page ; $0100-$01ff - stack ; ; $7000-$7fff - CPU State History (16 entries, 8 bytes each) ; $8000-$8017 - history memory read window (read only) ; ; $9000-$97ff - monitor hardware control registers ; ; $f000-$ffff - 4K Monitor ROM/RAM, also mirrored at $0000-$0fff
  • The actual mapping logic is orchestrated by the “monitor_bus” component.

cpustateram

         +-------------------+          (clock) clkA | | clkB (clock)          -------> <-------          | |          (cpu_state_write) weA | | enaB (1)          ------> <-------          | |          (1) enaA | |          ------> |         { | |          (0 [15:0]) | |    (monitor_cpu_state[15:0]) | |    (monitor_memory_access_address[31:0])| |         } --> diA[63:0] | cpustateram | doB[7:0] (cpu_state_rdata[7:0])          -------------> --------------->       | |       (cpu_state_write_index) addrA[3:0] | | addrB[6:0] (cpu_address_next[6:0])       ------------> <--------------       | |       +-------------------+

This ‘cpustateram' component gets mapped to that $7000-$7fff region (from the 6502 cpu’s addressing perspective).

It's a dual-port ram, where I've put:

  • stuff on the left relating to signals wanting to write data into the ram.

  • stuff on the right relating to signals wanting to read data from the ram.

So on the left-side (write), seems like the idea is to write a big chunk of cpu-state related information within a single clock cycle.

  • monitor_cpu_state (16-bit, one byte is the internal cpu-state details, and the other byte is the currently read byte, I think)

  • monitor_memory_access_address (32-bit), the current address being accessed.

On the right-side (read), this is to let this 6502 monitor cpu read out this data via an 8-bit bus (a byte at a time).

So, the assembly code within "monitor.a65" will read from $7000-$7fff to try retrieve this cpu-state trace history.

This seems to be the data that is used to generate the output from the uart-monitor's z command. I.e., this stuff:

Signals relating to cpustateram

This was me deep-diving even further to learn more about the signals that relate to cpustateram and how that interact with other components.

historyram0 and historyram1

I haven’t dived too deep into historyram0 and historyram1 as yet, though from my initial gleaming of it, historyram was trying to be more ambitious with the volume of data it captured in each trace entry (current x,y,z,a and a lot more).

Hence the reason why the left-side (write) has a data-bus size of 128-bits (can write 16-bytes per clock cycle).

I believe it relates to using the z xx command. I.e., z with a hex-byte parameter. I think the parameter is the index of the trace-entry within the ram to read from, I believe it will then print register-information just as is done with the r command, but making use of the trace-entry values for these register values had at the time of the trace.

Deciphering the 'J' command

The 'J' command gets succinctly described in the manual as:

J : DEBUGMON
Format: j [value]
Usage: Display, and optionally set, internal signals of the matrix mode monitor
interface.

I’ve been studying various files to get a better feel for what this really does…

'J' without parameter

'J' with parameter

'J' without parameter

'J' with parameter

In “monitor.a65” and the debug_cmd: section, I saw that:

If you type J in isolation (with no [value] param), it then prints out the current value of two internal variables, as two hex-bytes placed side-by-side.

  • These internal variables are called:

    • mon_trace_step

    • mon_trace

Example 1:

  • On power-up, typing J

  • It will return 0000
    I.e.:

    • mon_trace_step = 00

    • mon_trace = 00

 

Example 2:

  • Type t1 (to turn trace-mode on), then type J

  • It will return 0011
    I.e.:

    • mon_trace_step = 00

    • mon_trace = 11

NOTE: ‘J' with parameter only works on my `uartmon_fixes’ branch presently.

If you type J xx (where ‘xx’ is a hexadecimal byte value):

  • It sets an the internal variable called mon_trace with the xx hex value you specify

  • It then prints out the current values of two internal variables, just as it does when 'J' is run without parameters.

Example 3:

Since our prior example had set mon_trace to be 11 (which turned trace mode on), let’s turn trace mode off, but instead of using the t0 command, let’s do it via J xx:

  • type J 00

  • It will return 0000
    I.e.:

    • mon_trace_step = 00

    • mon_trace = 00

  • You can confirm that trace mode has indeed turned off by typing the r command repeatedly, and seeing the PC address move around

 

Example 4:

Let’s now do the opposite. Let’s turn on trace-mode, but instead of using t1, let’s do it via J xx:

  • type J 11

  • It will return 0011

  • You can confirm that trace mode has turned on by typing r command repeatedly, and seeing the PC address has halted

What is the ‘mon_trace’ variable?

When you set the value of this mon_trace variable, it actually sets a byte register inside monitor_ctrl.v called mem_trace_reg with the value you specify.

This register turns out to be a bitfield consisting of:

BIT

FIELD NAME

PURPOSE

BIT

FIELD NAME

PURPOSE

0

monitor_mem_trace_mode

This signal is an output from monitor_ctrl with then is routed as an output from uart_monitor (monitor_top.v), into machine.vhdl, and then finally gs4510.vhdl.

Best I can fathom is that this bit dictates to the CPU if it needs to halt or not.

1

monitor_flag_en

This signal dictates whether the cpu will be permitted to halt if a flag-breakpoint has been hit or not.

I.e., you may have set a flagwatch via the E xxyy command (where xx is a hex bitfield of flags to break on if set, and yy is a hex bitfield of flags to break on if cleared).

The E xxyy command will turn this monitor_flag_en bit on, but if you were to want to temporarily disable flag-breakpoints, you could use the J xx command to do so (via this bit1 field)

2

history_write

I ‘think’ this a flag to dictate whether trace info is written into the historyram0/1 components or not.

If the bit is set, after 1023 trace entries of the ram have been filled, no more trace entries are recorded, and this bit will be automatically reset.

3

history_write_continuous

This flag is set (in conjunction with history_write), then trace entries will be recorded perpetually. Once the 1023rd entry is filled, following trace entries will be written from entry position#0 and onwards (looping in a circular fashion).

4

monitor_irq_inhibit

This bit is output from monitor_ctrl and routed to gs4510.vhdl to inhibit irqs being set on the CPU (sounds like it would be handy while step-debugging, and not wanting IRQs to interfere with your debugging journey).

5

monitor_hyper_trap

(unused) Has been hardcoded inside monitor_ctrl.v to always be 1 (on).

The signal gets routed to machine.vhdl where it is ANDed with a signal called hyper_trap to become hyper_trap_combined, and then fed into gs4510.vhdl, to dictate whether hyper-traps are performed or not. (as this bit is hardcoded to 1, hyper traps always occur, they can’t be disabled).

6

monitor_watch_en

This bit dictates whether the monitor is allowed to halt the cpu if it detects that the user’s watchpoint (data-breakpoint) has hit.

Such watchpoints can be set via the W [address] command. Hence this bit6 will enable/disable this command from halting the CPU on such an event.

7

monitor_break_en

This bit dictates whether the monitor is allowed to halt the cpu if it detects that the user’s breakpoint address has hit.

Such breakpoints can be set via the B [address] command. Hence this bit7 will enable/disable this command from halting the CPU on such an event.

 

What is the ‘mon_trace_step’ variable?

The mon_trace_step variable seems to be only writable by the uart-monitor system itself. We can not write to it via the J command, but simply keep track of what its value currently is. It appears to be bitfield containing various other internal monitor signals (some which seem closely related to those in mon_trace).

This register turns out to be a bitfield consisting of:

BIT

FIELD NAME

PURPOSE

BIT

FIELD NAME

PURPOSE

0

monitor_break_matched

A flag to indicate whether a breakpoint hit (and genuinely causes the CPU to halt)

1

monitor_watch_matched

A flag to monitor whether a watchpoint (data breakpoint) hit (and genuinely causes the CPU to halt)

2

monitor_flag_matched

A flag to monitor whether a flag breakpoint hit (and genuinely causes the CPU to halt)

3

monitor_watch_en

Seems to be an exact replica of mon_trace - bit 6 (not needed?)

4

monitor_break_en

Seems to be an exact replica of mon_trace - bit 7

5

monitor_watch_match

This is a signal that is input into uart_monitor component from cpu4510.vhdl. I assume it is set high when a watchpoint (data breakpoint) is hit.

(this can go high even if the CPU wasn’t halted as a consequence)

6

0

Unused. Hardcoded to 0

7

monitor_mem_trace_toggle

This is an output signal from monitor_ctrl that gets routed into gs4510.vhdl. I haven’t dived into this deeply yet, but I think it relates to single stepping (running for one step, then halting again?)