|
|
4. Hierarchy
Hierarchy in SystemC is provided in a similar way to VHDL. You add hierarchy by instantiating other modules (components) in your sc_module (architecture/entity pair). Figure one shows a system in VHDL and SystemC.
As one can see from the above image there is a lot of similarity between a VHDL and a SystemC system. Both consist of I/O ports, processes, process communications (signals) and other modules (components). What should also be clear from the figure is that SystemC provides additional modeling capabilities, such as channels, interfaces and event.
The code fragment below shows a simple 4 bits shiftregister build up out of 4 FlipFlops (dff1..4).
|
VHDL |
SystemC |
|
entity shiftreg is |
#include "dff.h" |
Starting from the top of the VHDL program, the component dff instantiation tells the VHDL compiler about the dff port mapping, this is somewhat similar to the #define "dff.h" statement in the SystemC program.
In VHDL you name the instance and bind the ports in a single statement dff1:dff port map(), in SystemC you instantiate and name the module dff dff1; first and then you bind the ports in the constructor (SC_CTOR).
You need to do one additional step for which there is no VHDL equivalent and that is to call the constructor of each instantiated component. This is done on the so called initializer list,
SC_CTOR(shiftreg) : dff1("dff1"),dff2("dff2"),dff3("dff3"),dff4("dff4") {
The initalizer list consist of listing the module instance names e.g. dff1 and pass on a label e.g. "dff1" to its constructor. Note that you need to list each component on the initializer list in the same order as you instantiated them example:
dff dff2;
dff dff1;
dff dff3;
SC_CTOR(shiftreg) : dff2("dff2"), dff1("dff1"), dff3("dff3),...
Port binding is done in the constructor. Similar to VHDL you can use positional or named association.
|
VHDL |
SystemC |
Note |
|
dff1: dff port map (clk => clk, |
dff1.clk(clk); |
Name Associated, recommended style |
|
dff1: dff port map (clk,reset,din ,q1_s); |
dff1 << clk << din << dout; |
Positional Association, deprecated in SystemC 2.2 |
|
dff1: dff port map (clk,reset,din ,q1_s); |
dff1(clk); |
Alternative form of Positional Association, also deprecated in SystemC 2.2 |
As with VHDL the recommended style is to use the named association.
4.1 SC_MAIN
If you have looked at the sc_main.cp file in some of the previous examples you have noticed a different style for component instantiation, the reason for this is that sc_main is not a module but a function. You can think of sc_main as a VHDL testbench, it looks like a normal entity-architecture pair but the entity is empty.
Because sc_main is a function, there is no constructor and hence you can not use any processes. The function of sc_main is to instantiate the top level (e.g. VHDL DUT) and start and stop the simulation. You can also pass on command line arguments to your simulation and setup a VCD trace file so that you can log any signals for later viewing.
The code fragment below shows a simple testbench for the above 4 bits shift register.
#include "shiftreg.h"
int sc_main(int argc, char* argv[]) {
sc_signal<bool> reset, din, dout; // Local signals
sc_clock clk("clk",10,SC_NS); // Create a 10ns period clock signal
shiftreg DUT("shiftreg"); // Instantiate Device Under Test
DUT.clk(clk); // Connect ports
DUT.reset(reset);
DUT.din(din);
DUT.dout(dout);
sc_trace_file *fp; // VCD filepointer
fp=sc_create_vcd_trace_file("wave");// Create wave.vcd file
sc_trace(fp,clk,"clk"); // Add signals to trace file
sc_trace(fp,reset,"reset");
sc_trace(fp,din,"din");
sc_trace(fp,dout,"dout");
din=true;
sc_start(100, SC_NS); // Run simulation for 100 ns
din=false;
sc_start(100, SC_NS); // Run another 100 ns
sc_close_vcd_trace_file(fp); // close wave.vcd
return 0; // Return OK, no errors.
} // no ;
The top level component is specified in the header file #include "shiftreg.h". Inside the sc_main() function the shiftreg component is instantiated and the ports are connected to some local signals. The latter is required for the VCD trace function which will be described later. Notice the component instantiation shiftreg DUT contains the initializer list label "shiftreg" and the port binding is not done inside a constructor.
You can start and stop the simulator using the methods sc_start() and sc_stop() respectively.
sc_start(33.4, SC_PS); // run for 33.4 picosecond. SC_FS, SC_PS, SC_NS, SC_US, SC_MS, SC_SEC
sc_start(); // run until sc_stop()
sc_start(-1); // run until sc_stop()
sc_start(0); // run for 1 delta cycle!
To use sc_start() you need to specify a clock using the sc_clock module. The sc_clock arguments are passed on to its constructor,
// sc_clock signal( label, period(,) dutycycle, start_time(,) posedge_first);
sc_clock clk1("clk1", 10,SC_NS); // Create a 100MHz clock signal
sc_clock clk2("clk2", 10,SC_NS, 0.5, 0,SC_NS, true); // Same as clk1, default values
sc_clock clk3("clk3", 5,SC_NS, 0.25); // Create a 200MHz clock signal with a 25% high dutycycle
sc_clock clk4("clk4", 5,SC_NS, 0.25, 3,SC_NS, false);// first edge at 3ns, first edge is falling
sc_clock clk5("clk5", 5,SC_NS, 0.25, 3,SC_NS, true); // first edge at 3ns, first edge is rising

See the clock_test subdirectory for a simple sc_clock test that created the above waveform.
4.2 Simple Example
Figure 4.2 shows a simple adder test environment. The system consist of 4-bits ripple-carry adder, a stimulus generator and a checker module. The full source can be found in the adder subdirectory.

Some code fragments are discussed below.
sc_out<sc_uint<4> > sum; // output port
sc_uint<4> sum_s; // Variable
bool fulladder(bool a, bool b, bool cif, bool& cof) {
bool sumr;
sumr =(a ^ b) ^ cif;
cof=(a & b) | ((a ^ b) & cif);
return sumr;
}
void p1() {
sum_s[0]=fulladder(ain.read()[0],bin.read()[0],ci.read(), co0);
sum_s[1]=fulladder(ain.read()[1],bin.read()[1],co0,co1);
sum_s[2]=fulladder(ain.read()[2],bin.read()[2],co1,co2);
sum_s[3]=fulladder(ain.read()[3],bin.read()[3],co2,co3);
sum.write(sum_s);
co.write(co3);
}
Code Fragment from adder.h
The fulladder is implemented using a simple C++ function. The point to note is the & character after the bool type (bool& cof). The & character is used to indicate that the cof argument expects a reference, that is, a pointer to an object rather than the object value itself. References allow you to modify the orginal argument which in this case is co0..co3. In VHDL terms this is like a function were the parameter list can not only be of type IN but also OUT and INOUT. Note that you can not pass a reference to an I/O port. On a similar note, you can not write to a single bit [], bit slice (,) or an I/O port (sum), hence you need to modify a variable (sum_s) and write the value back to the I/O port (sum.write(sum_s)).
void ps1() {
......
ain.write("0b1110");
bin.write("0b0010");
ci.write(true);
wait();
sc_stop(); // End simulation
}
SC_CTOR(stim) {
SC_THREAD(ps1); // Run ps1 only ones
sensitive << clk.pos();
}
Code Fragment from stim.h
The stimulus module stim.h provide a sequence of stimuli before stopping the simulator using sc_stop(). Notice the method of writing a binary number, 0b is the C/C++ prefix of a binary value, similar to 0x for hexadecimal and 0d for decimal.
#include <iostream>
using namespace std;
SC_MODULE(check) {
sc_in<bool> clk;
sc_in<sc_uint<4> > ain, bin;
sc_in<bool> ci;
sc_in<sc_uint<4> > sum;
sc_in<bool> co;
sc_uint<5> sumc;
void pc1() {
sumc=ain.read() + bin.read() + ci.read(); // Model of an adder
cout << "fulladder " << ain.read() << " + " << bin.read() << " + " << ci.read() << " =" << sum.read()+co.read()*16;
if (sumc(3,0)==sum.read() && co==sumc[4]) {
cout << " Passed" << endl;
} else {
cout << " Failed, expected sum=" << sumc(3,0) << " co=" << sumc[4] << endl;
}
}
SC_CTOR(check) {
SC_METHOD(pc1);
sensitive << clk.pos();
dont_initialize();
}
};
Code fragment from check.h
The final module is the checker check.h, this module takes the stimuli input and the adder's output and compares this to a local model of an adder. The cout statements are similar to VHDL's write/writeline statements and will be discussed in the next section.