Skip to main content

CPU event handlers

This is an advanced topic. Most programs will never need to implement a custom CPU event handler.

The Chombit microprocessor responds to CPU events by temporarily jumping to an event handler memory address, which contains assembly language instructions that perform some action. Afterwards, the handler can optionally restore the entire CPU state and resume executing the original program. The memory address of the handlers are stored in IO::CPU_FAIL_HANDLER, IO::CPU_TRACE_HANDLER, and IO::CPU_REQUEST_HANDLER.

Hybrix debugger handlers

When running your program in the Hybrix website development environment, the CPU event handlers can be intercepted by the debugger:

  • IO::CPU_FAIL_HANDLER: The debugger pauses the program before the event is triggered. If program is unpaused, the handler will be invoked. The Hybrix framework kernel's handler displays an error message and terminates the program.

  • IO::CPU_TRACE_HANDLER: The debugger writes the message to the website console, then triggers the CPU event. The Hybrix framework kernel's handler simply returns control to the program.

  • IO::CPU_REQUEST_HANDLER: If the request number is a debugger function, the action will be performed, then control is returned to the program without triggering the CPU event at all; IO::CPU_REQUEST_HANDLER is bypassed entirely. Otherwise, the IO::CPU_REQUEST_HANDLER is invoked normally. The Hybrix framework kernel's handler doesn't implement any REQUEST functions; it simply terminates the program with an error.

Event handler invocation

In order to manage critical problems such as a stack fault, the handler gets invoked without reliance on the CPU stack. Instead, the CPU state is saved in four special-purpose MMIO locations, in the following sequence:

  1. A parameter distinguishing the type of event is written to IO::CPU_EVENT_VALUE.
  2. The CPU flags byte is written to IO::CPU_EVENT_FLAGS.
  3. The IP register is written to IO::CPU_EVENT_IP. In the case of a CPU fault, CPU_EVENT_IP points to the instruction that failed; in all other cases, it points to the subsequent instruction.
  4. The FP register is written to IO::CPU_EVENT_FP.
  5. The IP register then jumps to the address read from IO::CPU_FAIL_HANDLER, IO::CPU_TRACE_HANDLER, or IO::CPU_REQUEST_HANDLER.

Each step requires 1 clock cycle, thus the handler invocation adds 5 clock cycles to the execution time of the instruction that caused the event.

Writing an event handler

The code sample below illustrates a typical recipe for implementing an event handler:

@EXAMPLE_EVENT_HANDLER:
MOVE FP, $D0_0000

# CHECK WHAT KIND OF EVENT WE NEED TO HANDLE
COMPARE I:28, 123 # $D0_001C = CPU_EVENT_VALUE

. . .
(YOUR CODE HERE)
. . .

# RESTORE THE ORIGINAL CPU STATE
PUSH I:20 # $D0_0014 = CPU_EVENT_IP
PUSH B:19 # $D0_0013 = CPU_EVENT_FLAGS
PUSH I:24 # $D0_0018 = CPU_EVENT_FP
POP FP
POP FLAGS
POP IP

Important points:

  • The $D0_0080$D0_00BF memory range is reserved for usage by the kernel.
  • By pointing the FP register to $D0_0000, the MMIO locations can be accessed directly without need for LOAD/STORE.
  • To perfectly restore the CPU state including flags, the handler needs to push some values onto the original program's stack.

I/O definitions

CLASS IO_HAND_CONTROLLER # SIZE 8
. . .
# CUSTOM HANDLING FOR "FAIL", "TRACE", "REQUEST" AND OTHER CPU EVENTS
VAR CPU_EVENT_FLAGS: BYTE LOCATED AT $D0_0013
VAR CPU_EVENT_IP: INT LOCATED AT $D0_0014
VAR CPU_EVENT_FP: INT LOCATED AT $D0_0018
VAR CPU_EVENT_VALUE: INT LOCATED AT $D0_001C
VAR CPU_FAIL_HANDLER: INT LOCATED AT $D0_0020
VAR CPU_TRACE_HANDLER: INT LOCATED AT $D0_0024
VAR CPU_REQUEST_HANDLER: INT LOCATED AT $D0_0028
. . .
END MODULE