Services - tools - models - for embedded software development
Embecosm divider strip
Prev  Next

4.3.3.  Top Level Behavior

The RSP server initialization, rsp_init () is called from the main simulator initialization, sim_init () in toplevel-support.c.

The main simulation initialization is also modified to start the processor stalled on a TRAP exception if RSP debugging is enabled. This ensures that the handler will be called initially.

The main loop of Or1ksim, called after initialization, is in the function exec_main () in cpu/or32/execute.c.

If RSP debugging is enabled in the Or1ksim configuration, the code to interact with the RSP client (handle_rsp ()) is called at the start of each iteration, but only if the processor is stalled. The handler is called repeatedly until an interaction with the client unstalls the processor (i.e. a step or continue function.

void
exec_main ()
{
  long long time_start;

  while (1)
    {
      time_start = runtime.sim.cycles;
      if (config.debug.enabled)
        {
          while (runtime.cpu.stalled)
            {
              if (config.debug.rsp_enabled)
                {
                  handle_rsp ();
                }

              ...
        

Since interaction with the client can only occur when the processor is stalled, BREAK signals (i.e. ctrl-C) cannot be intercepted.

It would be possible to poll the connection on every instruction iteration, but the performance overhead on the simulator would be unacceptable.

An implementation to pick up BREAK signals should use event driven I/O - i.e. with a signal handler for SIGIO. An alternative is to poll the interface less frequently when the CPU is not stalled. Since Or1ksim executes at several MIPS, polling every 100,000 cycles would mean a response to ctrl-C of less than 100ms, while adding no significant overhead.

4.3.3.1.  Exception handling

The RSP interface will only pick up those exceptions which cause the processor to stall. These are the exceptions routed to the debug interface, rather than through their exception vectors, and are specified in the Debug Stop Register (set during initialization). In the present implementation, only TRAP exceptions are picked up this way, allowing the debugger to process memory based breakpoints. However an alternative implementation could allow the debugger to see all exceptions.

Exceptions will be processed at the start of each iteration by handle_rsp (). However the handler needs to know which signal caused the exception. This is achieved by modifying the main debug unit exception handling function (debug_ignore_exception () in debug/debug-unit.c) to call rsp_exception () if RSP is enabled for any exception handled by the debug unit. This function stores the exception (translated to a GDB target signal) in rsp.sigval.

int
debug_ignore_exception (unsigned long  except)
{
  int           result = 0;
  unsigned long dsr    = cpu_state.sprs[SPR_DSR];

  switch (except)
    {
    case EXCEPT_RESET:    result = (dsr & SPR_DSR_RSTE);  break;
    case EXCEPT_BUSERR:   result = (dsr & SPR_DSR_BUSEE); break;

  ...

  cpu_state.sprs[SPR_DRR] |= result;
  set_stall_state (result != 0);

  if (config.debug.rsp_enabled && (0 != result))
    {
      rsp_exception (except);
    }

  return  (result != 0);

}       /* debug_ignore_exception () */
	  

For almost all exceptions, this approach is suitable. However TRAP exceptions due to single stepping are taken at the end of each instruction execution and do not use the standard exception handling mechanism.

The exec_main () function already includes code to handle this towards the end of the main loop. This is extended with a call to rsp_exception () if RSP debugging is enabled.

      if (config.debug.enabled)
        {
          if (cpu_state.sprs[SPR_DMR1] & SPR_DMR1_ST)
            {
              set_stall_state (1);

              if (config.debug.rsp_enabled)
                {
                  rsp_exception (EXCEPT_TRAP);
                }
            }
        }
	  
Embecosm divider strip