Design and Implementation of a Hierarchical Exception Handling Extension to SystemC Prashant Arora
Rajesh K. Gupta
Department of Information and Computer Science University of California, Irvine Irvine, CA 92697
Department of Information and Computer Science University of California, Irvine Irvine, CA 92697
[email protected]
[email protected]
ABSTRACT
Exception handling constructs are a fundamental part of most system-level design languages. This paper describes the design and implementation of exception handling constructs as an extension to existing C++ libraries for modeling hardware, in particular SystemC. A hierarchy of exception handlers is proposed shadowing the design hierarchy. To study the eectiveness of these extensions, support for exceptions is added to an existing model of the DLX pipeline. The ease of these additions illustrates the utility of the proposed constructs. Categories and Subject Descriptors
B.6 Hardware]: Logic Design B.6.3 Logic Design]: Design Aids|hardware description languages General Terms
Design, Languages Keywords
Exception handling, SystemC 1.
The purpose of this work is to implement exception handling constructs in existing C++ class libraries for modeling hardware. SystemC is a C++ class library with an underlying light-weight scheduler to enable hardware modeling in C++ 8]. However, as of now, there are not many system-level design features in SystemC the classes enable only RTL and behavioral-level descriptions. In this paper, we propose exception handling extensions to SystemC. The same avor of features can be implemented in other popular class libraries like Cynlib 2]. An important feature of SystemC is that no extensions to the C++ syntax have been made all features needed for hardware modeling are provided through classes and function calls. We adopted this principle when extending the SystemC language to add exception handling constructs. Thus all the extensions are conned to within the syntax of C++. This saves us from writing a compiler/preprocessor that can handle our extensions. Further all the tools (compilers, debuggers etc.) that are normally used in software development can be used even after our exception handling extensions to SystemC. 2. PREVIOUS WORK
INTRODUCTION
Exception handling constructs are a fundamental part of most system-level design languages. However there are often dierent interpretations and meanings given to the term exception. Consequently, the group of features found under the term exception handling varies. The underlying common motivation behind these constructs is to enable a clear separation of the normal state of the system and the state to handle unusual scenarios. What situations can be termed as unusual or not normal is open to interpretation. E.g. exceptions cover the gamut from global reset to hardware failure like parity error. Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, to republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. CASES’00, November 17-19, 2000, San Jose, California. Copyright 2000 ACM 1-58113-338-3/00/0011 .. 5.00
$
As mentioned earlier, several SDLs provide constructs for exception handling. Some of the languages we examined for their exception handling features are SpecCharts, Statecharts, SpecC and Esterel. Below we summarize some of the important points about the exception handling features of these languages. SpecCharts is a visual front-end for VHDL with features like concurrent and sequential substates 4]. The exception handling features implemented in SpecCharts are limited to a special kind of transition, Transition Immediate (TI). A TI arc is taken when the condition associated with the arc is true on taking this arc, all the sub-behaviors that are part of the state associated with the arc are terminated immediately and control transfers to the new state. E.g. on a battery-low signal in a wrist watch, control should jump from any normal state to an alarm state. This can be represented with just one TI arc in SpecCharts. Transitions are however limited to the same level of hierarchy. Statecharts are a hierarchical and concurrent extension to state diagrams 5]. A state is allowed to have concurrent or sequential sub-states. In addition, almost arbitrary transitions are allowed between states. E.g. control may transfer from a sub-state to a super-state thus achieving restart for
X
X e2
e1
Y
Z
void main(void) { try {x.main();} trap(e1) {y.main();} trap(e2) {z.main();} }
e2
e1
Y
Z
void main(void) { try {x.main();} interrupt(e1) {y.main();} interrupt(e2) {z.main();} }
Figure 1: Exception handling constructs in SpecC
the whole super-state. Because of the exibility allowed in transitions in Statecharts, it is possible to model several kinds of exception scenarios unfortunately, there is no clear path to implementation for a lot of the Statecharts features. So they are primarily of modeling interest. SpecC is a system description language based on a C-like syntax 9]. The system is viewed as a hierarchical network of behaviors interconnected by channels. exception handling support is provided using the try-trap and try-interrupt constructs. The try-trap construct is similar to the TI arc in SpecCharts. E.g. in Figure 1, behavior X including subbehaviors is immediately aborted on event e1 or e2. Control is transferred to Y or Z. The try-interrupt construct can be used to model interrupts. Here control is transferred back to X automatically after servicing the interrupt. Execution resumes where it was left o. Priority between exception events can be dened based on the order in which these traps are listed. Esterel provides some interesting exception handling features through its trap mechanism 1]. A body can explicitly exit on a trap condition. A corresponding handler can then be invoked for the trap. Traps can be nested or concurrent and the handlers can correspondingly be prioritized or parallel. Traps can be valued to pass additional information to the handlers. Another interesting feature is that the handler is invoked one `instant' after the body is aborted this is akin to saying that the transition to the handler takes place at a clock edge and is thus synchronous. From the above discussion, it can be observed that the features and the exception situations they support are of two types: Implicit exceptions, where a signal, event or condition is monitored continuously over a body of code, for example, reset. This is suited for events/signals external to the body and of a global nature. Explicit exceptions, where an error condition is detected within the body and is explicitly thrown for it to be handled. The TI transition in SpecCharts is an example of the former, while the trap mechanism in Esterel is an example of the latter kind of exception modeling construct. Having made this distinction, we note that SystemC already provides support for the former kind of exception handling mechanism through the global watching construct. A process body can be watched for particular conditions and control is transferred to the beginning of the process when
these conditions occur. It was felt that an explicit throwcatch mechanism for exceptions might also be a useful addition. The rest of the paper describes such an exception handling extension to SystemC. It is well known that C++ provides exceptions of its own 7]. An exception object of any class can be thrown and subsequently the stack is unwound until one of the functions in the call hierarchy `catches' the exception. However this kind of mechanism in case of SystemC is limited in its utility to function call hierarchies arising out of the process entry functions. Exceptions thrown from within entry blocks cannot be caught by user routines. A design in SystemC is composed of modules and processes. A module is a collection of processes (we use version 0.9 of SystemC) all the behavior is contained in the processes, inside entry functions, which are the leaf nodes in the design hierarchy. Modules are used to model hierarchy. We can see that since we are modeling hardware using processes and modules in SystemC, we are dealing with a dierent hierarchy, the design hierarchy. It seems natural that exceptions be propagated through the design hierarchy. Our implementation of exception handling provides a mechanism to propagate exceptions through the SystemC design hierarchy. 3. HIERARCHICAL EXCEPTION HANDLING: FEATURES AND SEMANTICS
Synchronous processes are the most common kind of processes that arise in hardware design they are sensitive to a clock edge. In the discussion below we deal with synchronous processes only, i.e., we restrict the use of our exception handling features to within synchronous processes, although the design can have other kinds of processes also. We extend the SystemC sc sync class to allow a synchronous process to have a special method called excp handler. Further we allow the process to explicitly raise an exception from within entry. When a process raises an exception of a particular type (and the process does not throw that kind of exception), control is transferred to the excp handler routine at the next clock edge the process is sensitive to. The handlers for the dierent kinds of exceptions are separated using SC CATCH macros. The appropriate SC CATCH body is then executed. A dummy example is provided below to illustrate the syntax of exceptions. The process psum raises an exception if sum is 0. This causes control to transfer to the local excp handler function. void psum::entry() { int sum while (true) { scanf("%d%d", &a, &b) if (a==-1 && b==-1) { printf("Exiting...\n") exit(0) } sum=a+b if (sum==0) { raise_sc_excp(1) } else { printf("Time %f: sum=%d\n", sc_time_stamp(), sum)
wait() } } } void psum::excp_handler() { SC_CATCH(1) { a=a+1 wait() b=b+1 wait() excp_done(RESTART) } }
catches exceptions of type 1 and 2 and forks o exception handling blocks in the two underlying processes, using the EXCP FORK macro. void sdmod::excp_handler() { SC_CATCH(1) { // handle locally // schedules eh blocks for all // underlying processes EXCP_FORK(SYNC) return } SC_CATCH(2) { EXCP_FORK(NOSYNC) return } SC_CATCH(SC_OTHERS) { printf("Unexpected excp kind, exiting\n") exit(1) } }
When the exception has been handled locally, the excp handler routine can choose to return back to normal processing. This can be done by executing excp done(). This causes control to return to the entry function at the next clock cycle i.e. the transition is synchronous. In addition, by passing a parameter to the excp done function (RESTART/NORESTART) we can either restart the entry function on return from the The following code fragment shows the excp handler up exception handler or continue where we had left o. routine for process psum. A similar routine is needed for A process can also elect not to handle an exception loprocess pdi. On completion of excp handler up routines, cally. In this case, it must explicitly declare what kinds control is transfered back to the entry functions. of exceptions it wants to throw. These are declared in the void psum::excp_handler_up() { constructor of the process using the SC THROW macro, as SC_CATCH(2) { shown in the code fragment below. When an exception is a=a+1 raised by the entry process that is not handled locally (i.e. wait() it is thrown), the exception is transferred to the parent modb=b+1 ule of the process. A module can also dene a excp handler wait() method. wait() wait() wait() wait() excp_done(NORESTART)
struct pdiff: public sc_sync { int a, b void entry() void excp_handler() void excp_handler_up() pdiff(sc_clock_edge& ce, const char* name, sc_excp_module* mod) :sc_sync(ce, name, mod) { SC_THROW(1) SC_THROW(2) } }
It was felt that since an exception would be thrown by a process when it requires handling by coordination with other processes, the best way to handle the exception would be in a distributed manner. For this reason, when an exception handler for a module is called, there is no behavior in it. Instead, if the module catches an exception that has been thrown, it forks o exception handling routines in the underlying processes. The entry functions for all underlying processes lose control at the next clock, control is transferred to another special method called excp handler up in each process. This is the exception handling routine corresponding to a process invoked from up in the hierarchy. This routine is needed to allow the designer to deal dierently with local and global exceptions. It must SC CATCH all exceptions that are caught at its parent modules. The following piece of code shows the excp handler routine for module sdmod that contains processes psum and pdi. It
} }
The fork at the module exception handler can be of SYNC or NOSYNC type. In case of a SYNC forking, all excp handler up routines are synchronized on completion i.e. the entry methods get control back at the same clock cycle for all processes. In case of NOSYNC, the excp handler up routines can complete at dierent times and the entry methods can get rescheduled at dierent times. We assume all processes under one exception handling hierarchy have the same clock. This seems like a reasonable assumption since we can expect exceptions not to propagate across clock boundaries. 3.1 Priorities
Dierent processes under the same module can throw exceptions during the same clock cycle. These exceptions can be prioritized using the sequence in which the SC CATCH macros appear in the body of the exception handler for the module. The exception whose SC CATCH macro appears rst has higher priority. In the same clock cycle, an exception can be caught higher in the hierarchy than another exception. In this case, the exception caught highest in the hierarchy is chosen since it is of a more global nature than other exceptions the other exceptions are ignored.
sc_module
sc_excp_block
sc_excp_module
sc_sync
Figure 2: Class Hierarchy for Exception Handling Enabled Classes
3.2 Preemption
Exceptions caught higher in the design hierarchy can be expected to be of a more important nature than exceptions caught locally. So we allow an exception that is caught higher up in the hierarchy to preempt the handler for a lower-level exception that is already being handled. The state associated with a preempted exception is lost, i.e. we cannot return back to the preempted exception handler. We don't allow exceptions to be raised from within exception handling routines. This seems like a reasonable assumption since the exceptions are explicit. 3.3 Exception type
Currently all exceptions are of type int. This allows use of macros or enumerated types to represent the type of the exception. 4.
IMPLEMENTATION
In order to implement the exception handling scheme described above, we made changes and additions to the SystemC source code. In order to localize the changes as far as possible, we derived separate exception classes from the SystemC classes as shown in Figure 2. The sc sync class with exception support has the same name as the regular sc sync class. This was done to minimize name changes in the source code. The excp handler and excp handler up functions are virtual functions like entry. Fields are added for an exception thread, apart from the thread for entry, in the sc sync class. An additional eld is used to indicate the mode a process is in. If the mode is exception handling, the exception thread is invoked when the process is scheduled else the entry thread is invoked. SystemC doesn't maintain a design hierarchy as modules have no signicance other than to cluster processes. For our purposes, we create a design hierarchy with links between a module and its constituent processes/modules. The scheduler code is also changed to eect scheduling of exception handlers. At the end of each simulation tick, the exceptions generated during the tick are collected and propagated. This information is used to schedule the appropriate exception handlers at the next tick. 5.
CASE STUDY: DLX PIPELINE
We used a DLX pipeline modeled in SystemC to experiment with our exception handling mechanism 3]. Examples of unusual situations in a processor pipeline are exceptions
(note that the use of our exception handling mechanisms are not limited to modeling processor exceptions as claried earlier, we use the term exception in a more general sense). We introduced four kinds of exceptions in the existing model of the DLX pipeline: Instruction memory page fault, illegal opcode, divide by zero error and data memory page fault. A dummy DIV instruction has been introduced it is assumed to take one cycle to complete for illustrative purposes. Page faults are introduced randomly. Our model of the DLX pipeline implements each stage (fetch, decode etc.) as a separate process. A module sits on top of these processes and instantiates them. In order to implement precise exceptions in the DLX pipeline, the exceptions should be handled in the order of the instructions, i.e., an exception caused by an earlier instruction should be handled rst regardless of which exception is detected rst 6]. To achieve this, the code for the exception is passed through the pipeline to the write-back stage. All exceptions are handled in the write-back stage. For example, when an illegal opcode is detected, the ID stage process passes the exception code to the id ex pipeline register. It then raises an EXCP DETECTED exception and switches to the local exception handler, where it waits forever (the exception handler will eventually be preempted). This is because it should ignore the following instructions until the exception is handled. The following stages also detect the exception and switch to their local exception handlers. When the exception reaches the write-back stage, it is thrown, since, clearly, handling the exception requires the coordination of all stages. The exception handler routine for the top-level module catches the exception and forks of excp handler up routines in all the stages. These routines preempt the excp handler routines since they are forked o at a higher level in the design hierarchy. The following fragment of code outlines the code for the ID stage of the pipeline. void id_stage_c::entry(void) { unsigned excp wait() wait() while (true) { if_id_format_c if_id_value= if_id_reg.read() // check for excps if (if_id_value.excp!=NO_EXCP) { // An exception from the previous stage // propagate excp to next stage id_ex_format_c id_ex_value(0, NULL, 0, if_id_value.excp) id_ex_reg.write(id_ex_value) raise_sc_excp(EXCP_DETECTED) } else { // decode instruction ... // An illegal opcode is detected if (excp!=NO_EXCP) { raise_sc_excp(EXCP_DETECTED) } else {
wait() } } } } void id_stage_c::excp_handler(void) { SC_CATCH(EXCP_DETECTED) { while (true) wait() } } void id_stage_c::excp_handler_up(void) { SC_CATCH(SC_OTHERS) { wait() // for the trap instruction // to be introduced // propagate the trap instruction if_id_format_c if_id_value= if_id_reg.read() instruction_c* new_instruction= new trap_instruction(if_id_value. instruction_register) id_ex_format_c id_ex_value(0, new_instruction, 0, NO_EXCP ) id_ex_reg.write(id_ex_value) // wait for trap to propagate wait() wait() wait() wait() excp_done(NORESTART) } }
In order to handle the exception, a trap instruction is introduced into the pipeline by the IF stage excp handler up method. The trap is then propagated through the pipeline where it is acted upon by the excp handler up routines of the dierent stages. In the MEM stage, the Interrupt Vector Table is accessed to get the location of the next PC. In the WB stage, this new value is written to the PC. At this point normal execution is resumed by the IF stage. We use the NOSYNC option when forking o the exception handlers as the stages need to restart at dierent times. NORESTART is used for all stages as the restarting PC is hard coded to be zero.
The above example illustrates that our exception handling mechanism provides a clean separation of normal and exception blocks. This modeling style allows the designer to code the normal functionality rst and add exceptional conditions as an afterthought. 6. CONCLUSIONS
We have described the implementation of a hierarchical exception handling mechanism as an extension to SystemC. The mechanism is exible and allows a clean separation of normal and exception handling code. An example of exceptions in the DLX pipeline has been coded using the described mechanism. This example illustrates the utility of the proposed mechanism. 7. ACKNOWLEDGMENTS
The authors acknowledge nancial support received through NSF CCR-9806898 and DARPA/ITO through contract DABT6398-C-0045. 8. REFERENCES
1] G. Berry. The Esterel Language Primer. INRIA. Version 5.21, Release 2.0. 2] CynApps. Systems Modeling with Cynlib, 1999. Release 1.1. 3] F. Doucet, V. Sinha, C. Siska, and R. K. Gupta. System-on-chip modeling using objects and their relationships. Technical report, Information and Computer Science, UC Irvine, November 1999. 4] D. D. Gajski, F. Vahid, S. Narayan, and J. Gong. Specication and Design of Embedded Systems. Prentice Hall, Englewood Clis, NJ, 1994. 5] D. Harel. Statecharts: A visual formalism for complex systems. Science of Computer Programming, 8(3):231{74, June 1987. 6] J. L. Hennessy and D. A. Patterson. Computer Architecture : A Quantitative Approach. Morgan Kaufman Publishers, second edition, 1996. 7] B. Stroustrup. The C++ Programming Language. Addison-Wesley, third edition, 1997. 8] Synopsys Inc. SystemC Reference Manual. Release 0.9. 9] J. Zhu, R. Doemer, and D. D. Gajski. Syntax and semantics of the specc language. In Proceedings of the Synthesis and System Integration of Mixed Technologies, December 1997. Osaka, Japan.