Register-Transfer Level Hardware Description with SystemC:SystemC Constructs for RTL Design
SystemC Constructs for RTL Design
In this section we will introduce the basic components in SystemC for describing hardware at RT level. This section uses a series of examples starting from a very primitive one to a more complex one.
Each example incrementally discusses SystemC constructs at the RT level. After discussing basic parts of the SystemC language, we start with combinational circuits and then cover several RT-level sequential circuits. Combinational circuits cover basic functions, adders and ALU functions, and the sequential examples cover basic flip-flops, functional registers, and finally, several ways of describing state machines.
The last part of this section shows SystemC description of testbenches. This part is also example oriented in which we will be using the examples of the earlier parts of this section.
Modules
The entity used in SystemC for description of hardware components is a module defined by the SC_MODULE keyword. A module can describe a hardware component as simple as a multiplexer, or a network of complex digital systems. As shown in Figure 89.1, the definition of modules begins with the SC_MODULE keyword. This is usually done in a header file (.h file). In the definition of a module, ports, member variables, and member functions are defined. The implementation of member functions is usually placed in a separate file (a .cpp file) as shown in the figure. In this section, most of the example
codes are partitioned into two parts. The first part shows the header file (.h file) of the example code and the second part is the implementation of the member functions of the first part (.cpp file).
A design may be described in a hierarchy of other modules. The top-level module is the complete design, and modules lower in the hierarchy are the design’s components. Module instantiation is the construct used for bringing a lower level module into a higher level one. Figure 89.2 shows a hierarchy of several nested modules. As shown in this figure, module instantiation is performed by defining a member variable of a lower level module inside a higher level module in hierarchy.
As shown in Figure 89.3, a module declaration includes several parts. These parts can be put in any order. They include module ports, local signal declarations, definition of concurrent processes of a module, member data declarations, and member function declarations.
Module Ports
A part of module declaration is a list of port declarations. This list includes inputs, outputs, and bidirectional input/output lines. Ports may be listed in any order. This ordering can only become significant when a module is instantiated and does not affect the way its operation is described. Usually, top-level modules used for testbenches have no ports.
In this part, the direction, type, and size of each port listed in the module header are specified. A port may be sc_in, sc_out or sc_inout. The latter kind is used for bidirectional input/output lines. A general declaration of ports is sc_port that is more useful in higher level designs than the RTL. The type and size of each port is defined after its direction. For example in Figure 89.4, the type of a and b input ports are bool and the type w output port is int. Size of vectored ports of a module is also declared in the module port declaration part. The size and indexing of a port is specified after its type declaration between < and > signs. Figure 89.4 shows an example circuit with scalar and vectored input, output and input/ output ports. Ports named a and b are one-bit inputs. Ports av and bv are 8-bit logic vector inputs of acircuit. Port w of acircuit is declared as a 1-bit output, and wv is a logic vector bidirectional port of size 8.
Logic Value System
SystemC uses C and C++ data types as well as its own predefined data types. Types that are used most frequently in RTL designs include int, bool, sc_int<n>, sc_uint<n>, sc_logic, and sc_lv<n>. sc_logic and sc_lv types are generally used in bussing and bool is usually used for a single-bit port or signal definition. Values in sc_logic and sc_lv types include “0” (or SC_LOGIC_0), “1” (or SC_LOGIC_1), “Z” (or SC_LOGIC_Z), and “X” (or SC_LOGIC_X). Value “0” is for logical 0 which in most cases represents a path to ground (Gnd). Value “1” is logical 1 and it represents a path to supply (Vdd). Value “Z” is for float, and “X” is used for uninitialized, undefined, undriven, unknown, and value conflicts. Values Z and X are used for busses, initialization values, and tri-state structures. Values of bool type include true and false. In the text of this section we will use 1 and true, or 0 and false interchangeably.
Module Signals
A portion of module declaration is a list of internal signals that are used inside the module (e.g., in its concurrent parts). Internal signals in SystemC are defined by sc_signal keyword.
Similar to ports, an internal signal has a type and it can be a single or a vectored signal. The acceptable data types for a signal are similar to those for a port. Figure 89.5 shows internal signals named d, e, and dv for the bcircuit module. As shown in this figure, signals d and e are defined as single signals, and signal dv is a logic vector of size 8.
Module Members
Similar to a class in C++, a module in SystemC has its own data and functions. These are called members of a module. Member definitions are similar to the definition of member data and member functions in C++.
As shown in Figure 89.6, md is a member data of type int, mf1 is a member function that returns void and accepts two arguments of type bool, and mf2 is a member function with no arguments. Similar to C++, all members in a module must be public, private, or protected. Public members of a module can be accessed by other modules via the instance of that module. For example, mf1 member function can be accessed in other modules, if they have an instance (a defined variable) of type ccircuit. Private members of a module can be accessed only by other members of that module. For example, md member data can only be accessed in other functions in ccircuit. Protected members of a module act as public members for the module’s children and act as private members otherwise.
Module Concurrent Parts
A module in SystemC consists of one or more concurrent parts. These concurrent parts are called processes and they can be methods (defined by SC_METHOD keyword), threads (defined by SC_THREAD keyword), or clocked threads (defined by SC_CTHREAD keyword). These three kinds of processes are discussed in the examples of this section.
A method in SystemC is a light-weight process with a sensitivity list. Every time an event happens on one of the signals in its sensitivity list it is executed. A thread is like a method in that it also has a sensitivity list. But it can be suspended and resumed by the use of wait functions inside its body. A clocked thread is similar to a thread in that it can be suspended and resumed by wait functions. But a specified edge of a signal is defined as its clock in its registration, and it is sensitive to the defined edge of this signal.
A member function defined in a module can be defined as a process. These process definitions (also named as process registration) are performed inside the module constructor.
A mandatory part of a module is its constructor. Necessary variable initializations, memory allocations, and process registrations are performed in a constructor. Figure 89.7 shows two ways of constructor definition in SystemC.
A constructor of a module is a member function with the same name as the module name. In SystemC, the implementation of a constructor can be in a .cpp file (like other member functions) or it can be inside the module definition. In the former case, the SC_HAS_PROCESS keyword defines the member function that is a constructor, and in the latter case, the implementation of a module constructor begins with SC_CTOR followed by the module name and the body of the constructor.
In addition to member data initializations and memory allocations, process registration is done inside a constructor. Process registration specifies which member functions execute concurrently with other parts of the system and to which signals or ports they are sensitive. In Figure 89.8, the member function mf2 defined in Figure 89.6 is registered as a method that is sensitive to port a and signal ib. As shown in Figure 89.8, a sensitivity list is defined by sensitive keyword and the separation between signals in a sensitivity list is done by the << operator.
Using the basic concepts presented above, the sections that follow show how SystemC can be used for describing combinational and sequential components. Utilization of SystemC operators and constructs for specifying functions will be described in the examples that are discussed next.
Combinational Circuits Using Equations
A combinational circuit can be represented by its gate-level structure, its Boolean functionality, or description of its behavior. At the gate level, interconnection of its gates are shown; at the functional level, Boolean expressions representing its outputs are written; and at the behavioral level, a software- like procedural description represents its functionality. This section shows examples of combinational circuits using expressions of procedural descriptions with SystemC constructs.
At a higher level than gates and transistors, a combinational circuit may be described by use of Boolean, logical, and arithmetic expressions. These operations are performed on variables of C++ or SystemC data types and they are predefined or overloaded C and C++ operations. In the following, there are several combinational circuits implemented with equations in SystemC. Table 89.1 shows the predefined operations that can be used or overloaded for several data types in SystemC.
Majority Example
We use the majority circuit of Figure 89.9 to illustrate how expressions are used in a design. The description shown in Figure 89.10 corresponds to this circuit. The module description has inputs and outputs according to the schematic of Figure 89.9.
This module has three inputs and one output. It has a member function named maj_func that is defined as a method in the maj3 constructor. This method is sensitive to all input signals. Figure 89.10 also includes the implementation of maj_func. The expression in this figure, at line 8, implements the functionality of the majority circuit shown in Figure 89.9.
XOR Example
As our second example for using expressions consider the description of an XOR gate as shown in Figure 89.11. Similar to the majority circuit in Figure 89.10, the module developed for XOR gate has a process that handles the functionality of a three-input XOR gate. The xor3 module in Figure 89.11 uses a thread (SC_THREAD) process for its functional part. This thread is sensitive to the module’s three inputs a, b, and c. Inside the thread, it waits for a new event on its sensitivity list and after each event it recalculates the output y and waits for the next event. The majority circuit in Figure 89.10 was developed by a method (SC_METHOD) process.
Full-Adder Example
After developing modules for a majority circuit and an XOR gate, we can develop our full-adder circuit using these two modules by instantiating them into the full adder. In SystemC, instantiating a module inside another module is performed by defining a member data of the instantiated module inside the module definition of the instantiating module. As shown in Figure 89.12, there are several options for instantiating a module inside another one. For example, in this figure, xor_inst and maj_inst are instances of xor3 and maj3 modules, respectively. xor_inst is defined as a reference to xor3 module of Figure 89.11, while maj_inst is defined as a pointer to maj3 module of Figure 89.10. The initialization of these two instances must be inside the add_1bit constructor, as shown in Figure 89.12.
After initializing the instances discussed above, their ports must be bound to proper ports or signals in the add_1bit module. The port binding of instantiated modules are done inside the add_1bit constructor. There are two ways for port binding in SystemC that are instantiation by name and instantiation by position. In Figure 89.12, ports a, b, c, and y of xor_inst are bound to a, b, cin, and s of add_1bit, respectively. These ports are bound in the same order they are defined in the xor3 module. Also in this figure, ports a, b, c, and y of maj_inst are bound to a, b, cin, and co of add_1bit, respectively. These ports are bound by name.
As shown in Figure 89.12, by instantiating modules xor3 and maj3 inside the add_1bit constructor, a full adder is developed. Two processes inside xor3 and maj3 perform the function of full adder.
We can also develop a full adder by directly describing it with two expressions. One expression for the XOR function and the other for majority function for the sum and carry-out outputs, respectively. This code is shown in Figure 89.13.
Adder Example
As another example for developing modules using expressions, we discuss a 4-bit adder. In this adder, shown in Figure 89.14, we use sc_lv data type and its methods for adding two 4-bit vectors. This adder
has one bit carry-in (cin in Figure 89.14) and one bit carry-out (co in Figure 89.14). In this figure, add_func is a method that is sensitive to all of add_4bit’s input signals. In line 6 of this function, the vectored inputs are converted to unsigned integers (using to_uint method) and added by the standard + operation in C++. Also in this line ci, which is an sc_logic type, is converted to sc_logic_value_t enumeration type that can be between 0 and 3 using value method and added to a and b vectors. ci.value() is converted to 0 when ci is SC_LOGIC_0, 1 when ci is SC_LOGIC_1, 2 when ci is SC_LOGIC_Z, and 3 when ci is SC_LOGIC_X. The total result is stored in an integer (tmp_result_i) that is converted to a 5-bit sc_lv type variable (tmp_result) using type casting. The leftmost bit of tmp_result is assigned to co and its first four bits are assigned to s using range function.
Multiplexer Example
Another example of a combinational circuit is a simple multiplexer that is shown in Figure 89.15. The mux2_1 multiplexer uses a C style if-statement to implement a multiplexer functionality.
ALU Example
The if-statement, used in the previous example, is easy to use, descriptive, and expandable. However, when many choices exist, a case-statement that is more structured may be a better choice. The ALU description of Figure 89.16 uses a case-statement to describe an ALU with add, subtract, AND, and XOR functions.
The ALU has a and b data inputs and a 2-bit f input that selects its function. The SystemC code shown in Figure 89.16 uses a, b, and f on its sensitivity list for the alu_func method. The case-statement shown in the alu_func method uses f to select one of the case alternatives. The last alternative is the default alternative that is taken when f does not match any of the alternatives that appear before it. This is necessary to make sure that unspecified input values (here, those that contain X and/or Z) cause the assignment of the default value to the output and not leave it unspecified.
Sequential Circuits
As any digital circuit, a sequential circuit can be described in SystemC by use of gates, Boolean expressions, or behavioral constructs. While gate-level descriptions enable a more detailed description of timing and
delays, because of complexity of clocking and register and flip-flop controls, these circuits are usually described by the use of expressions inside the module processes. This section shows behavioral way of describing sequential circuits in SystemC. The following discusses primitive structures like latch and flip-flops, and then generalizes coding styles used for representing these structures to more complex sequential circuits including counters and state machines.
Basic Memory Elements
A clocked D-latch latches its input data during an active clock cycle. The latch structure retains the latched value until the next active clock cycle. This element is the basis of all static memory elements.
Although latches and flip-flops can be described by primitive gates and expressions, such descriptions are hard to generalize, and describing more complex register structures cannot be done this way. This section uses behavioral ways to describe latches and flip-flops. We will show that the same coding styles used for these simple memory elements can be generalized to describe memories with complex control as well as functional register structures like counters and shift-registers.
Latches. Figure 89.17 shows a D-latch described by a thread in SystemC. Latch clock and data inputs (c and d) appear in the sensitivity list of the latch_func thread, making this process sensitive to c and d. The reason for using a thread instead of a method in this latch is the delays used in the body of latch_func. Since we are modeling the delays between circuit inputs and its output, we have to use wait functions inside the process. As methods do not support wait functions, the use of threads is mandatory here. If this module had no delays, we could use methods as well as threads.
The if-statement enclosed in latch_func puts d into q when c is active. This means that if c is 1 and d changes, the change on d propagates to the q output after 4 ns. This behavior is referred to as transparency, which is how latches work. While clock is active, a latch structure is transparent, and input changes affect its output.
Any time latch_func is active, if c is 1, it waits 4 ns and then puts d into q. It then waits another 3 ns and then puts the complement of d into q_b. This makes the delay of the q_b output 7 ns.
D Flip-Flop. While a latch is transparent, a change on the D-input of a D flip-flop does not directly pass on to its output. The SystemC code of Figure 89.18 describes a positive-edge trigger D-type flip-flop. The sensitivity list of the dff_func thread, shown in Figure 89.18, includes only the positive edge of clk (using pos function). This process is activated when clk makes a 0 to 1 transition. When this process is active, the value of d is put into q after 4 ns and the inverted value of d into q_b after 7 ns.
Instead of pos function used in Figure 89.18, use of neg function would implement a falling-edge D flip- flop. After the specified edge, the flow into the dff_func thread begins. In our description, this flow is halted in 4 ns by the wait (4, SC_NS) statement. After this delay, the value of d is read and put into q. Following this transaction, the flow into the dff_func thread is again halted by 3 ns, after which !d is put into q_b. This makes the delay of q after the edge of the clock equal to 4 ns. The delay for q_b becomes the accumulation of the delay values shown, and it is 7 ns. Delay values are ignored in synthesis.
Synchronous control. The coding style presented for the above simple D flip-flop is a general one and can be expanded to cover many features found in flip-flops and even memory structures. The description shown in Figure 89.19 is a D-type flip-flop with synchronous set and reset (s and r) inputs.
The description uses a SC_THREAD that is sensitive to the positive-edge of clk. When clk makes a 0 to 1 transition, the flow into the dff_func thread begins. Immediately after the positive-edge, s is inspected and if it is active (1 or true), after 4 ns q is set to 1 and 3 ns after that q_b is set to 0. Following the positive edge of clk, if s is not 1, r is inspected and if it is active, q is set to 0. If neither s nor r is 1, the flow of the program reaches the last else part of the if-statement and assigns d to q.
The behavior discussed here only looks at s and r on the positive-edge of clk, which corresponds to a rising-edge trigger D-type flip-flop with synchronous active high set and reset inputs. Furthermore, the set input is given a higher priority over the reset input. The flip-flop structure that corresponds to this description is shown in Figure 89.20.
Other synchronous control inputs can be added to this flip-flop in a similar fashion. A clock enable (en) input would only require inclusion of an if-statement in the last else part of the if-statement in the code of Figure 89.19.
Asynchronous control. The control inputs of the flip-flop of Figure 89.19 are synchronous because the flow into the thread is only allowed to start when the positive edge of clk is observed. To change this to
a flip-flop with asynchronous control, it is only required to include asynchronous control inputs in the sensitivity list of its process registration.
Figure 89.21 shows a D flip-flop with active high asynchronous set and reset control inputs. Note that the only difference between this description and the code of Figure 89.18 (synchronous control) is the inclusion of s.pos() and r.pos() in the sensitivity list of the dff_func thread. This inclusion allows the flow into the thread to begin when clk becomes 1 or s becomes 1 or r becomes 1. The if-statement in this block checks for s and r being 1, and if none is active (activity levels are high), then clocking d into q occurs. The graphic symbol corresponding to the flip-flop of Figure 89.21 is shown in Figure 89.22.
Registers, Shifters, and Counters
Registers, shifter-registers, counters, and even sequential circuits with more complex functionalities can be described by simple extensions of the coding styles presented for the flip-flops. In most cases, the functionality of the circuit only affects the last else of the if-statement in processes of codes shown for the flip-flops.
Registers. Figure 89.23 shows an 8-bit register with synchronous set and reset inputs. The set input puts all 1s in the register and the reset input resets it to all 0s. The main difference between this and the flip-flop with synchronous control is the vector declaration of inputs and outputs.
Shift-registers. A 4-bit shift-register with right- and left-shift capabilities, a serial-input, synchronous reset input, and parallel loading capability is shown in Figure 89.24. As shown, only the positive-edge of clk is included in the sensitivity list of shiftreg_func thread of this code, which makes all activities of the shift-register synchronous with the clock input. If rst is 1, the register is reset, if ld is 1, parallel d inputs are loaded into the register, and if none is 1, shifting left or right takes place depending on the value of the l_r input (1 for left, 0 for right). Shifting in this code is done by operations << and >> overloaded for sc_lv. For left-shift, s_in is concatenated to the right of q[2 :0] to form a 4-bit vector that is put into q (lines 14 and 15). For right- shift, s_in is concatenated to the left of q[3 :1] to form a 4-bit vector that is clocked into q[3:0] (lines 18 and 19). The style used for coding this register is the same as that used for flip-flops and registers presented earlier. In all these examples, a single thread handles function selection (e.g., zeroing, shifting, or parallel loading) as well as clocking data into the register output.
Counters. The style described for the shift-register in the previous discussion can be used for describing counters. A counter counts up or down, while a shift-register shifts right or left. We use arithmetic operations in counting as opposed to shift operation in shift-registers.
Figure 89.25 shows a 4-bit up-down counter with a synchronous rst reset input. The counter has an ld input for doing the parallel loading of d_in into the counter. The counter output is q. We use counter_ func process, which is a clocked thread (SC_CTHREAD), to implement the functionality of the counter. This clocked thread is sensitive to the positive edge of clk.
Discussions about synchronous and asynchronous control of flip-flops and registers also apply to counters. For example, if counter_func process is an SC_THREAD instead of SC_CTHREAD and rst.pos() is added to the sensitivity list of the counter_func of Figure 89.25, the counter resetting would be asynchronous.
State Machine Coding
Coding styles presented thus far can be further generalized to cover finite-state machines of any type. This section shows coding for Moore state machines. The example we will use is a simple sequence detector. This circuit represents the controller part of a digital system that has been partitioned into a data path and a controller. The coding styles used here apply to such controllers.
Moore detector. State diagram for a Moore sequence detector detecting 101 on its x input is shown in Figure 89.26. The machine has four states that are labeled, reset, got1, got10, and got101. Starting in reset, if the 101 sequence is detected, the machine goes into the got101 state in which the output becomes 1. In addition to the x input, the machine has an input named rst that forces the machine into its reset state. The resetting of the machine is synchronized with a clock.
The SystemC code of the Moore machine of Figure 89.26 is shown in Figure 89.27. After the declaration of inputs and outputs of this module, enum declaration for states declares four states of the machine as an enumeration type. Following enum declarations in the code of Figure 89.27, the current variable of type states is declared. This variable holds the current state of the state machine.
The moore_func thread used in the module of Figure 89.27 describes state transitions and output assignments of the state diagram of Figure 89.26. The main task of this thread is to inspect input
conditions (values on rst and x) during the present state of the machine defined by current, and set values into current for the next state of the machine.
The flow into the moore_func thread begins with the positive edge of clk. Since all activities in this machine are synchronized with the clock, only clk appears on the sensitivity list of the moore_func thread.
Upon entry into this block, the rst input is checked and if it is active, current is set to reset (reset is declared in the states enumeration and its value is 0). The value put into current in this pass gets checked in the next pass with the next edge of the clock. Therefore, this assignment is regarded as the next-state assignment. When this assignment is made, the if-else statements skip the rest of the code of the moore_func thread, and this thread will next be entered with the next positive edge of clk.
Upon entry into the moore_func thread, if rst is not 1, program flow reaches the case-statement that checks the value of current against the four states of the machine.
In this coding style, for every state of the machine there is a case-alternative that specifies the next state values. For larger machines, there will be more case-alternatives, and more conditions within an alternative. Otherwise, this style can be applied to state machines of any size and complexity. This same machine can be described in SystemC in many other ways. For example, we can use nested if-else statements instead of using case-statement.
Huffman coding style. The Huffman model for a digital system characterizes it as a combinational block with feedbacks through an array of flip-flops or a register. According to the Huffman model, SystemC coding of digital systems uses two concurrent processes; one for describing the register part and another for describing the combinational part.
We will describe the state machine of Figure 89.26 to illustrate this style of coding. Figure 89.28 shows the combinational and register partitioning that we will use for describing this machine. The Combinational
Part block uses x and Present state as input and generates z and Next state. The Register block clocks Next state into Present state, and resets Present state when reset is active.
Figure 89.29 shows the SystemC code of Figure 89.26 according to the partitioning of Figure 89.28. As shown, n_state and p_state variables are declared as variables of enum type states. These variables hold values corresponding to the states of the 101 Moore detector. The comb_func method is sensitive to x and p_state. In this method, n_state and z are set to their inactive or reset values. This is done so that these variables are always reset with the clock to make sure that they do not retain their old values. As discussed before, retaining old values implies latches, which is not what we want in our combinational block.
The body of the combinational method of Figure 89.29 contains a case-statement that uses the p_state input for its case-expression. This expression is checked against the states of the Moore machine. As in the other styles discussed before, this case-statement has case-alternatives for reset, got1, got10, and got101 states.
In a block corresponding to a case-alternative, based on input values, n_state and z output are assigned values. Unlike the other styles where current is used both for the present and next states, here we use two different variables, p_state and n_state.
The next concurrent process (seq_func thread), shown in Figure 89.29, handles the Register part of the Huffman model of Figure 89.28. In this part, n_state is treated as the register input and p_state as its output. On the positive edge of the clock, p_state is either set to the reset state or is loaded with contents of n_state. Together, comb_func and seq_func processes describe our state machine in a modular fashion.
The advantage of this style of coding is in its modularity and well-defined tasks of each block. State transitions are handled by the comb_func method and clocking is done by the seq_func thread. Changes in clocking, resetting, enabling, or presetting the machine only affect the coding of the seq_func process. If we were to change the synchronous resetting to asynchronous, the only change we had to make was adding rst.pos() to the sensitivity list of the seq_func thread registration.
A more modular style. For a design with more input and output lines and more complex output logic, the Combinational Part may further be partitioned into a process for handling transitions and another for assigning values to the outputs of the circuit.
Figure 89.30 shows the coding of the 101 mealy detector using two separate processes for assigning values to n_state and the z output. In a situation like what we have in which the output logic is fairly simple, a simple assignment statement, like what we had in Figure 89.29, could replace the outp_func method.
The examples discussed above, and in particular the last two styles, show how combinational and sequential coding styles can be combined to describe very complex digital systems.
Memories
SystemC allows description and use of memories. Memories are variables that are declared as a two- dimensional array of a valid type in C or SystemC. These data types are usually bool and sc_logic types.
Also one-dimensional arrays of sc_lv type can be interpreted as memory. Figure 89.31 shows two different ways of memory declaration. This figure also shows several valid memory operations.
Figure 89.32 shows a memory block with separate input and output busses. Writing into the memory is clocked, while reading from it only requires rw to be 1. The read_func method handles reading and the write_func thread performs writing into this memory.
Writing Testbenches
SystemC coding styles discussed so far were for coding hardware structures, and in all cases synthesizability and direct correspondence to hardware were our main concerns. In contrast, testbenches do not have to have hardware correspondence and they usually do not follow any synthesizability rules. We will see that delay specifications and initialization statements that do not have a one-to-one hardware correspondence are generously used in testbenches.
For demonstration of testbench coding styles, we use the SystemC code of Figure 89.33 that is a 101 Moore detector and generate a testbench for this circuit.
This description is functionally equivalent to that of Figure 89.27. The difference is in the use of condition expressions (?:) instead of if-else statements. This code will be instantiated in the testbenches that follow.
Generating Periodic Data
Figure 89.34 shows a testbench module that instantiates moore_detector of Figure 89.33 and applies test data to its inputs. In SystemC, sc_main plays the role of the testbench for a design. There are several functions in SystemC that are useful for developing testbenches. The examples of these functions include sc_time, sc_clock, sc_trace, and sc_simulation_time, some of which we will use in our testbench examples.
One of the utility functions in SystemC is sc_clock that is used for generating periodic data on a specified signal. In Figure 89.34, signals x, reset, and clock are defined as periodic data. x is a periodic signal with 14 ns period and duty cycle of 0.5, which is the default value for duty cycle in sc_clock. The period of this signal and other periodic signals are defined by variables of kind sc_time (line 5–7 in Figure 89.34). The period of the clock signal is 10 ns and its first transition is a false to true transition, which is the default value for transitions in sc_clock. In the testbench of Figure 89.34, reset is also defined as a periodic data. The period of this signal is 1000 ns, its duty cycle is 0.024, it starts from the beginning of the simulation (at SC_ZERO_TIME), and its first transition is the negative edge transition. These arguments cause the reset signal to change from true to false in the 24th ns of the simulation time and remains false until the simulation time reaches 1000 ns.
The reason that we define such a long period for reset signal is that we want to reset the cut only once in this example. As shown in Figure 89.34, the simulation time, defined as the argument of sc_start, is specified as 1000 ns. This simulation time causes the reset signal to change only once during the simulation time.
All data inputs to the cut are locally generated in this testbench. We are using different periods for clock and x, so that a combination of patterns on these circuit inputs is seen. A more deterministic set of values could be set by specifying exact values at specific times.
Timed Data
A simple testbench for our sequence detector can be done by applying test data to x and timing them appropriately to generate the sequence similar to values we wanted to observe on the reset signal in the previous example. Figure 89.35 shows this testbench.
Techniques discussed in the above example are just some of what one can do for test data generation. These techniques can be combined for more complex examples. After using SystemC for some time, users form their own test generation techniques. For small designs, simulation environments generally provide waveform editors and other tool-dependent test generation schemes. Some tools come with code fragments that can be used as templates for testbenches.
The testbench shown in Figure 89.35 uses a helper module (apply_data) to apply input data to cut at specified times. The apply_data module accepts a file name as one of its constructor arguments. This filename corresponds to a text file that contains values a signal should receive in specified times. An example of this file is shown in Figure 89.36. The first element in each line specifies the value of a signal and the second element specifies the relative timing of that value (relative to the previous time) that is to be applied to the signal. The apply_data module has an output port named sig that is bound to the signal to which the values in the text file should be applied. The apply_data module is shown in Figure 89.37. This module has a thread named apply_func and a helper function, which reads and analyzes each line of the specified data file, named read_line. The apply_func thread reads each line of the data file, waits for a proper time, and applies the proper value to its output sig.
With this helper module, the testbench in Figure 89.35 can easily be developed. The first part of this testbench (lines 5 and 6) makes a clock signal with 10 ns period. The next part (lines 8–12) uses the apply_data module to read reset.txt and x.txt files and apply appropriate waveforms to reset and x signals, respectively.
Lines 14 and 15 of this testbench instantiates the circuit under test (moore_detector in this example). Lines 18–20 of this testbench uses the predefined SystemC functions and data types to generate an output file for the result of cut instance (z in this example). To generate this output file sc_trace_file, sc_create_vcd_trace_file, sc_trace, and sc_close_vcd_trace_file data type and functions are utilized. The zfile.vcd output file is a file in VCD format that includes the values of the z signal for the first 100 ns of simulation.
Comments
Post a Comment