76 M. Meredith 5.2 C++ Support Because SystemC is a class library implemented in C++, the advantages of high- level C++ constructs are available to hardware designers working in SystemC. Cynthesizer supports a large number of these constructs but, just as there are SystemVerilog constructs that are only intended for verification, there are C++ constructs that are only appropriate for modeling and testbench construction, not for synthesis. 5.2.1 Synthesizable High-Level C++ Constructs The C++ constructs that are within the synthesizable subset can be used in ways that give SystemC synthesis advantages unattainable in any other hardware design language. • Encapsulation:C++ classes can be used in SystemC synthesis to manage the complexity inherent in hardware design. Algorithmic functionality can be captured in a class for reuse. Functions providing a public API for use of the algorithm can be made externally avail- able using the C++ “public” access control. Internal computation functions and storage of internal state needed by the algorithm can be made private. Interface functionality can be encapsulated as discussed earlier creating a modular, reusable interface. Modular interfaces expose a transaction-level func- tion call interface to the designer which allows them to be used without requiring the designer to be expert in the details of the interface protocol. • Construction of custom data types: Operator overloading is a C++ technique whereby a class can provide a custom implementation for such built-in opera- tors as “*” (multiply) and “+” (add). This allows the construction of user defined datatypes such as for complex arithmetic, and matrix arithmetic. Arithmetic oper- ations can be performed on these datatypes using conventional C++ syntax, e.g., a = b + c; which promotes ease-of-use and improves a reader’s ability to understand the code. • Development of configurable IP:C++ provides template specialization as a way to write a single body of code which can represent a wide range of behaviors depending on the specific template parameters selected. As a simple example, template specialization can be used to build a filter class that can operate on any given datatype, including user-defined custom datatypes. A more sophisticated example is the cynw float parameterized floating-point class that Forte has developed. It allows the user to specify template parameters to choose the exponent and mantissa widths as well and configure options such as normalization and rounding behaviors. 5 High-Level SystemC Synthesis with Forte’s Cynthesizer 77 Supported C++ Constructs Arithmetic operators Integer data types serutcurtS srotarepo lacigoL References Classes Statically-determinable pointers Inheritance if-else statements Operator overloading switch-case statements Inferring memories from arrays do, while, and for loops Inferring registers from arrays break and continue statements Template classes and functions Template specialization 5.2.2 Non-Synthesizable C++ Constructs One characteristic of the synthesis process is that it uses the source code of the high- level design without access to information that can only be determined at simulation time. In other words, the synthesis process can only take advantage of language features that can be resolved statically and information that can be determined by inspection of the source code. This is the source of most of the restrictions on C++ constructs that can be used: • Pointer arithmetic: In the processor-based execution environment in which the C and C++ languages were originally envisioned, all variables, structures, arrays and other storage elements are defined to exist within a single uniform address space. A hardware implementation may include multiple separate memories of dif- ferent kinds as well as storage elements implemented directly with flip-flops. Clearly, in this environment making decisions based on the value of the address of a variable is meaningless. Consequently, pointer arithmetic is not supported for SystemC synthesis. • Pointer dereferencing: Similarly, accessing a specific storage element by its address assumes a processor-based execution environment. Therefore, in general, passing pointers and dereferencing them is not supported for SystemC synthesis. Nevertheless, under some circumstances the target of the pointer can be unam- biguously determined by a static analysis of the source code. For instance, if the address of an array is passed directly to a subroutine it is usually possible to statically determine the identity of the array in question. In such cases the use of the pointer will be supported by synthesis. 78 M. Meredith • Dynamic memory allocation: For reasons similar to those limiting the use of pointers, allocation of storage elements using malloc(), free(), new, and delete is not supported for SystemC synthesis. One notable exception is that allocation of sub-modules using new is supported. • Virtual functions: Virtual functions select the behavior of a particular object based upon run-time determination of its class identity. Since this cannot, in general, be determined statically, use of virtual functions is not supported for SystemC synthesis. 5.3 Synthesizable Module Structure Synthesizable SC MODULES can include multiple SC CTHREAD processes, and multiple SC METHOD processes. In addition they can include submodules along with signals and channels to provide internal interconnect. Because SC MODULES are C++ classes, they can also include data members of any synthesizable data type to provided internal state, and member functions that can be used by the processes to implement the required behavior. 5.4 Concurrent Processes Among the required hardware semantics provided by SystemC are process con- structs that allow a designer to directly express concurrent behaviors. Two of these process constructs, SC CTHREAD, and SC METHOD, are appropriate for syn- thesis to hardware and are supported by Cynthesizer. A module may contain any combination of these. Use of multiple SC CTHREADs and/or SC METHODs in a single module is fully supported. This allows SystemC synthesis using Cynthesizer to encompass the areas tra- ditionally considered separately as the behavioral level and the register-transfer 5 High-Level SystemC Synthesis with Forte’s Cynthesizer 79 level. Using Cynthesizer, an engineer can combine high-level behavioral design with low-level register-transfer level design. Ordinarily, an engineer who wants to do a pure RTL design will choose a con- ventional HDL such as SystemVerilog or VHDL. SystemC is more often used when high-level synthesis is needed for a substantial part of the design. Typically a com- plex algorithm or a complex control structure is defined using an SC CTHREAD, or multiple concurrently executing SC CTHREADs. These are combined with SC METHODS for implementation of small parts of the design that can be bet- ter specified at a low level. Examples of these low-level parts of the design might include the clock boundary crossing logic or an asynchronous bypass path. 5.4.1 SC CTHREAD Processes The SC CTHREAD construct implements a clocked process. The declaration of the process includes specification of a signal that will be used as the clock for the process. The semantics of SC CTHREAD guarantee that the behavior of the process will be synchronized to this clock. In addition the reset signal is() function specifies a signal that will be used to reset the process. Whenever the reset signal is asserted, the process is restarted in its initial state. This allows explicit initialization behaviors to be written that determine the state of the flip-flops of the design when it comes out of reset. During simulation, within the body of the subroutine that is the behavior of the SC CTHREAD process, execution proceeds sequentially until the process hits a wait() statement upon which the process is suspended until the next clock cycle. These characteristics make SC CTHREAD ideal for high-level synthesis of abs- tract, untimed behaviors combined with detailed cycle-accurate, pin-level interfaces. Synthesizer interprets all behavior in an SC CTHREAD process that occurs before the first wait() statement as reset conditions. Synthesis requires that this reset code be schedulable in a single clock cycle. void thread_func() { // reset behavior must be // executable in a single cycle reset_behavior(); wait(); // initialization may contain // any number of wait()s. // This part is only executed // once after a reset. initialization(); // infinite loop – concurrent hardware while (true) { rest_of_behavior(); } } SC_CTHREAD reset behavior while (1) { main loop } post-reset initialization module_name.cc 80 M. Meredith SC MODULE(sub) { // ports sc in clk clk; sc in<bool> rst; SC CTOR(sub) { SC CTHREAD( thread func, clk.pos() ); reset signal is( rst, 1 ); } void thread func() { // reset behavior wait(); while(1) { } } }; The “SC CTHREAD” statement associates the thread func() function with the positive edge of the signal clk. Cynthesizer implements such a thread as a circuit synchronous to that clock edge. The “reset signal is” statement makes the “1” level of the rst signal reset the thread. 5.4.2 SC METHOD Processes The SC METHOD process construct implements a triggered process associated with a sensitivity list. The SC METHOD declaration includes a set of signals and rising-edge/falling-edge information that define the sensitivity list of the SC METHOD. The subroutine associated with the SC METHOD process is exe- cuted whenever any of the signal transitions in its sensitivity list occurs. These characteristics make SC METHOD ideal for synthesis of register-transfer level behaviors. The SC METHOD construct is used to express design functionality at a low level equivalent to RTL for synthesis. SC METHOD provides a way to specify a sensitivity list that a specific clock signal with a thread, and has precise semantics for reset behavior. 5 High-Level SystemC Synthesis with Forte’s Cynthesizer 81 Cynthesizer can be used to synthesize synchronous SC METHODS using static sensitivity to a clock as follows. SC CTOR(sync) { CYN DEFAULT INPUT DELAY(.1,"delay"); SC METHOD( sync method ); sensitive pos( clk ); dont initialize(); } Asynchronous SC METHODS using static sensitivity to a number of inputs can also be synthesized as follows. SC CTOR(async) { CYN DEFAULT INPUT DELAY(.1,"delay"); SC METHOD( async method ); sensitive << input 1 << input 2; dont initialize(); } SystemC semantics for SC METHOD also provide for dynamic sensitivity using the next trigger() function. Dynamic sensitivity is not supported for synthesis. SystemC semantics for SC METHOD also provide that each SC METHOD will be executed once at the beginning of simulation. This is meaningless in the con- text of synthesis, so disabling this behavior using the dont initialize() function is recommended. 5.5 Modular Interfaces In addition to simple signals carrying single-bit or scalar values, designers using Cynthesizer can implement high-level channels for communication. By encapsulat- ing the low-level signals, ports, and protocol functions in modular interface socket classes, the designer is relieved of the tedious connection of individual signals, and can connect an entire interface, such as a connection to a memory, with a sin- gle binding function. In addition, the modular interface code can be thoroughly tested once, and then reused many times without modification, avoiding numerous common errors and reducing debug time. These modular interfaces consist of C++ classes containing synthesizable Sys- temC code using constructs such as signals, ports, and synthesizable protocol code. Common interfaces are provided by Forte. Interfaces conforming to specific cor- porate standards can be written in SystemC by a corporate CAD department, and project-specific interfaces can be written by any engineer. 82 M. Meredith The abstraction and modularity capabilities of C++ and SystemC offer a unique advantage for high-level hardware design when they are used in this way to encap- sulate interfaces for ease-of-use and for reuse. The key technique is to use the C++ class mechanism to encapsulate the signal- level connections (i.e., ports) along with the code that implements the signal-level protocol. In general, there are two complementary interfaces (e.g., input and output) that are implemented as two modular interface “socket” classes. These are connected by binding calls to a modular interface “channel” class. The processes in the modules containing the sockets call transaction-level interface functions defined in the socket classes to execute their interface behaviors. sc_in/out sc_signal Module 2 CTHREAD Module 1 CTHREAD Modular interface Channel Modular interface Output socket f1() Modular interface Input socket f2() g1() g2() 5.5.1 Modular Output Socket In its simplest form, an output socket for a ready/valid handshake interface might look like the following. // Output socket. template <class T> class RV out { public: sc in<bool> rdy; sc out<bool> vld; sc out<T> data; RV out( const char * name=0 ) {} // reset function called from SC CTHREAD // establishes initial conditions void reset() 5 High-Level SystemC Synthesis with Forte’s Cynthesizer 83 { vld = 0; data = 0; } // put function called from SC CTHREAD // executes output protocol void put( const T& val ) { do { wait(); } while ( !rdy.read() ); data.write( val ); vld = 1; wait(); vld = 0; } }; Note that the sc in/sc out ports are incorporated into the modular interface socket as data members. The two transactions that the port implements, reset() and put(), are also implemented as member functions. 5.5.2 Modular Input Socket The corresponding input socket implements the reciprocal protocol. Note that the direction of the ports is reversed from that of the output socket. // Output socket template <class T> class RV in { public: RV in( const char * name=0 ) {} sc out<bool> rdy; sc in<bool> vld; sc in<T> data; // // Protocol transaction functions // void reset() { rdy = 0; } 84 M. Meredith T get() { wait(); rdy = 1; do { wait(); } while ( !vld.read() ); rdy = 0; return data.read(); } }; 5.5.3 Use of Modular Interfaces The modular interface socket can be used in a design in a way that is similar to how a simple sc in or sc out port would be used. The instantiation and binding of the socket look just like an sc in or sc out port. To execute the protocol, the SC CTHREAD calls the transaction functions of the modular interface socket as follows. SC MODULE(sub) { sc in clk clk; sc in<bool> rst; RV in< sc uint<8> > din; RV out< sc uint<8> > dout; SC CTOR(sub) { SC CTHREAD( thread func, clk.pos() ); reset signal is( rst, 1 ); } void thread func() { // reset behavior din.reset(); dout.reset(); wait(); while (1) { sc uint<8> d = din.get(); dout.put( d + 1 ); } } }; 5 High-Level SystemC Synthesis with Forte’s Cynthesizer 85 5.5.4 Channel The signals that are needed to provide connectivity for this interface can also be encapsulated in a channel class as follows. // Channel class template <class T> class RV { public: sc signal<bool> rdy; sc signal<bool> vld; sc signal<T> data; }; 5.5.5 Binding The addition of a couple of binding functions to the modular interface socket allows the entire interface to be bound using a single function call. This reduces the number of lines of code needed to use an interface, allows interchange of different inter- faces with minimal code modification, and prevents trivial errors due to misspelling and misconnecting individual signals. For our example the binding functions in the output port are as follows. // Output socket. template <class T> class RV out { // // Binding functions. // template <class C> void bind( C& c ) { rdy(c.rdy); vld(c.vld); data(c.data); } template <class C> void operator() ( C& c ) . transaction -level func- tion call interface to the designer which allows them to be used without requiring the designer to be expert in the details of the interface protocol. • Construction of custom. Cynthesizer to encompass the areas tra- ditionally considered separately as the behavioral level and the register-transfer 5 High- Level SystemC Synthesis with Forte’s Cynthesizer 79 level. Using. Using Cynthesizer, an engineer can combine high- level behavioral design with low -level register-transfer level design. Ordinarily, an engineer who wants to do a pure RTL design will choose a con- ventional