Aleksandar Milenkovic
E-mail: milenka@ece.uah.edu
Web: http://www.ece.uah.edu/~milenka
Motivation for SystemC
What is SystemC?
Modules
Processes
A. Milenkovic 2
System-level designers write a
C or C++ model
Written in a stylized, hardware-like form
Sometimes refined to be more hardware-like
C/C++ model simulated to verify functionality
Parts of the model to be implemented in hardware are given to Verilog/VHDL coders
Verilog or VHDL specification written
Models simulated together to test equivalence
Verilog/VHDL model synthesized
A. Milenkovic 3
Current conditions
Every system company was doing this differently
Every system company used its own simulation library
System designers don’t know Verilog or VHDL
Verilog or VHDL coders don’t understand system design
Problems with standard methodology:
Manual conversion from C to HDL o Error prone and time consuming
Disconnect between system model and HDL model o C model becomes out of date as changes are made only to the HDL model
System testing o Tests used for C model cannot be run against HDL model => they have to be converted to the HDL environment
A. Milenkovic 4
C and C++ are being used as ad-hoc modeling languages
Why not formalize their use?
Why not interpret them as hardware specification languages just as Verilog and VHDL were?
SystemC developed at Synopsys to do just this
A. Milenkovic 5
Refinement methodology
Design is slowly refined in small sections to add necessary hardware and timing constructs
Written in a single language
higher productivity due to modeling at a higher level
testbenches can be reused saving time
Future releases will have constructs to model RTOS
A. Milenkovic 6
A subset of C++ that models/specifies synchronous digital hardware
A collection of simulation libraries that can be used to run a SystemC program
A compiler that translates the “synthesis subset” of SystemC into a netlist
A. Milenkovic 7
Language definition is publicly available
Libraries are freely distributed
Compiler is an expensive commercial product
See www.systemc.org for more information
A. Milenkovic 8
A SystemC program consists of module definitions plus a top-level function that starts the simulation
Modules contain processes (C++ methods) and instances of other modules
Ports on modules define their interface
Rich set of port data types (hardware modeling, etc.)
Signals in modules convey information between instances
Clocks are special signals that run periodically and can trigger clocked processes
Rich set of numeric types
(fixed and arbitrary precision numbers)
A. Milenkovic 9
Hierarchical entity
Similar to Verilog’s module
Actually a C++ class definition
Simulation involves
Creating objects of this class
They connect themselves together
Processes in these objects (methods) are called by the scheduler to perform the simulation
A. Milenkovic 10
SC_MODULE(mymod) { // struct mymod : sc_module {
/* port definitions */
/* signal definitions */
/* clock definitions */
/* storage and state variables */
/* process definitions */
};
}
SC_CTOR(mymod) {
/* Instances of processes and modules */
A. Milenkovic 11
Define the interface to each module
Channels through which data is communicated
Port consists of a direction
input sc_in
output sc_out
bidirectional sc_inout
and any C++ or
SystemC type
SC_MODULE(mymod) { sc_in<bool> load, read; sc_inout<int> data; sc_out<bool> full;
};
/* rest of the module */
A. Milenkovic 12
Convey information between modules within a module
Directionless: module ports define direction of data transfer
Type may be any C++ or built-in type
SC_MODULE(mymod) {
/* port definitions */ sc_signal<sc_uint<32> > s1, s2; sc_signal<bool> reset;
*/
}
/* … */
SC_CTOR(mymod) {
/* Instances of modules that connect to the signals
};
A. Milenkovic 13
Each instance is a pointer to an object in the module
Connect instance’s ports to signals
SC_MODULE(mod1) { … };
SC_MODULE(mod2) { … };
SC_MODULE(foo) { mod1* m1; mod2* m2; sc_signal<int> a, b, c;
};
}
SC_CTOR(foo) { m1 = new mod1(“i1”); (*m1)(a, b, c); m2 = new mod2(“i2”); (*m2)(c, b);
A. Milenkovic 14
// filter.h
#include "systemc.h“ // systemC classes
#include "mult.h“ // decl. of mult
#include "coeff.h“ // decl. of coeff
#include "sample.h“ // decl. of sample
SC_MODULE(filter) { sample *s1; // pointer declarations coeff *c1; mult *m1;
// signal declarations sc_signal<sc_uint<32> > q, s, c;
SC_CTOR(filter) { // constructor s1 = new sample ("s1"); // create obj.
(*s1)(q,s); // signal mapping c1 = new coeff ("c1");
(*c1)(c); m1 = new mult ("m1");
(*m1)(s,c,q);
}
}
A. Milenkovic 15
#include "systemc.h“ // systemC classes
#include "mult.h“ // decl. of mult
#include "coeff.h“ // decl. of coeff
#include "sample.h“ // decl. of sample
SC_MODULE(filter) { sample *s1; coeff *c1; mult *m1; sc_signal<sc_uint<32> > q, s, c;
SC_CTOR(filter) { s1 = new sample ("s1"); s1->din(q); // din of s1 to signal q s1->dout(s); // dout to signal s c1 = new coeff ("c1"); c1->out(c); // out of c1 to signal c m1 = new mult ("m1"); m1->a(s); // a of m1 to signal s m1->b(c); // b of m1 to signal c m1->q(q); // q of m1 to signal c
}
}
A. Milenkovic 16
Local variables to store data within a module
May be of any legal C++, SystemC, or user-defined type
Not visible outside the module unless it is made explicitly
// count.h
#include "systemc.h"
SC_MODULE(count) { sc_in<bool> load; sc_in<int> din; // input port sc_in<bool> clock; // input port sc_out<int> dout; // output port int count_val; // internal data storage void count_up();
SC_CTOR(count) {
SC_METHOD(count_up); //Method process sensitive_pos << clock;
}
};
// count.cc
#include "count.h" void count::count_up() { if (load) { count_val = din;
} else {
}
// could be count_val++ count_val = count_val + 1;
} dout = count_val;
A. Milenkovic 17
Only thing in SystemC that actually does anything
Functions identified to the SystemC kernel and called whenever signals these processes are sensitive to change value
Statements are executed sequentially until the end of the process occurs, or the process is suspended using wait function
Very much like normal C++ methods
+ Registered with the SystemC kernel
Like Verilog’s initial blocks
A. Milenkovic 18
Processes are not hierarchical
no process can call another process directly
can call other methods and functions that are not processes
Have sensitivity lists
signals that cause the process to be invoked, whenever the value of a signal in this list changes
Processes cause other processes to execute by assigning new values to signals in the sensitivity lists of the processes
To trigger a process, a signal in the sensitivity list of the process must have an event occur
A. Milenkovic 19
Determines how the process is called and executed
METHOD
Models combinational logic
THREAD
Models testbenches
CTHREAD
Models synchronous FSMs
A. Milenkovic 20
Triggered in response to changes on inputs
Cannot store control state between invocations
Designed to model blocks of combinational logic
A. Milenkovic 21
SC_MODULE(onemethod) { sc_in<bool> in; sc_out<bool> out; void inverter();
SC_CTOR(onemethod) {
SC_METHOD(inverter); sensitive(in);
};
}
A. Milenkovic
Process is simply a method of this class
Instance of this process created and made sensitive to an input
22
Invoked once every time input “in” changes
Should not save state between invocations
Runs to completion: should not contain infinite loops
Not preempted void onemethod::inverter() { bool internal; internal = in; out = ~internal;
}
Read a value from the port
Write a value to an output port
A. Milenkovic 23
Triggered in response to changes on inputs
Can suspend itself and be reactivated
Method calls wait() function that suspends process execution
Scheduler runs it again when an event occurs on one of the signals the process is sensitive to
Designed to model just about anything
A. Milenkovic 24
SC_MODULE(onemethod) { sc_in<bool> in; sc_out<bool> out; void toggler();
SC_CTOR(onemethod) {
};
}
SC_THREAD(toggler); sensitive << in;
A. Milenkovic
Process is simply a method of this class
Instance of this process created alternate sensitivity list notation
25
Reawakened whenever an input changes
State saved between invocations
Infinite loops should contain a wait() void onemethod::toggler() { bool last = false; for (;;) { last = in; out = last; wait(); last = ~in; out = last; wait();
}
}
Relinquish control until the next change of a signal on the sensitivity list for this process
A. Milenkovic 26
Triggered in response to a single clock edge
Can suspend itself and be reactivated
Method calls wait to relinquish control
Scheduler runs it again later
Designed to model clocked digital hardware
A. Milenkovic 27
SC_MODULE(onemethod) { sc_in_clk clock; sc_in<bool> trigger, in; sc_out<bool> out; void toggler();
SC_CTOR(onemethod) {
};
}
SC_CTHREAD(toggler, clock.pos());
Instance of this process created and relevant clock edge assigned
A. Milenkovic 28
Reawakened at the edge of the clock
State saved between invocations
Infinite loops should contain a wait()
Relinquish control until the next clock cycle in which the trigger input is 1 void onemethod::toggler() { bool last = false; for (;;) { wait_until(trigger.delayed() == true); last = in; out = last; wait(); last = ~in; out = last; wait();
}
}
Relinquish control until the next clock cycle
A. Milenkovic 29
struct complex_mult : sc_module { sc_in<int> a, b, c, d; sc_out<int> x, y; sc_in_clk clock; void do_mult() { for (;;) { x = a * c - b * d; wait(); y = a * d + b * c; wait();
}
}
};
SC_CTOR(complex_mult) {
SC_CTHREAD(do_mult, clock.pos());
}
A. Milenkovic 30
Process Type
Exec. Trigger
Exec.
Suspend
Infinite Loop
Suspend /
Resume by
Construct &
Sensitize
Method
SC_METHOD
Signal Events
NO
NO
N.A.
SC_METHOD(call_back); sensitive(signals); sensitive_pos(signals); sensitive_neg(signals);
SC_THREAD
Signal Events
YES
SC_CTHREAD
Clock Edge
YES
YES wait()
SC_THREAD(call_back); sensitive(signals); sensitive_pos(signals); sensitive_neg(signals);
YES wait() wait_until()
SC_CTHREAD(
call_back,
clock.pos());
SC_CTHREAD(
call_back,
clock.neg());
A. Milenkovic 31
SC_THREAD and SC_CTHREAD processes typically have infinite loops that will continuously execute
Use watching construct to jump out of the loop
Watching construct will monitor a specified condition
When condition occurs control is transferred from the current execution point to the beginning of the process
A. Milenkovic 32
// datagen.h
#include "systemc.h"
SC_MODULE(data_gen) { sc_in_clk clk; sc_inout<int> data; sc_in<bool> reset; void gen_data();
SC_CTOR(data_gen){
}
};
SC_CTHREAD(gen_data, clk.pos()); watching(reset.delayed() == true);
// datagen.cc
#include "datagen.h" void gen_data() { if (reset == true) { data = 0;
}
}
} while (true) { data = data + 1; wait(); data = data + 2; wait(); data = data + 4; wait();
Watching expressions are tested at the wait() or wait_until() calls.
All variables defined locally will lose their value on the control exiting.
A. Milenkovic 33
Allows us to specify exactly which section of the process is watching which signal, and where event handlers are located
W_BEGIN
// put the watching declarations here watching(...); watching(...);
W_DO
// This is where the process functionality goes
...
W_ESCAPE
// This is where the handlers
// for the watched events go if (..) {
...
}
W_END
W_BEGIN macro marks the beginning of the local watching block.
W_BEGIN – W_DO: watching declarations
W_DO – W_ESCAPE: process functionality
W_ESCAPE – W_END: event handlers
W_END macro ends local watching block
A. Milenkovic 34
All of the events in the declaration block have the same priority. If a different priority is needed then local watching blocks will need to be nested
Local watching only works in SC_CTHREAD processes
The signals in the watching expressions are sampled only on the active edges of the process. In an SC_CTHREAD process this means only when the clock that the process is sensitive to changes
Globally watched events have higher priority than locally watched events
A. Milenkovic 35
// dff.h
#include "systemc.h"
SC_MODULE(dff) { sc_in<bool> din; sc_in<bool> clock; sc_out<bool> dout;
// data input
// clock input
// data output void doit(); // method in the module
}
};
SC_CTOR(dff) { // constructor
SC_METHOD(doit); // doit type is SC_METHOD
// method will be called whenever a
// positive edge occurs on port clock sensitive_pos << clock;
// dff.cc
#include "dff.h" void dff::doit() { dout = din;
}
A. Milenkovic 36
Assumptions
32-bit internal data path; 8-bit external data path
every address and data transaction will have to be multiplexed over the 8-bit bus
Protocol
32-bit address is sent to the bus controller (BC) process
address will be multiplexed byte by byte and sent to MC
bus controller will wait for ready signal from memory
receive the data
send the data to microcontroller
A. Milenkovic 37
// bus.h
#include "systemc.h"
SC_MODULE(bus) { sc_in_clk clock; sc_in<bool> newaddr; sc_in<sc_uint<32> > addr; sc_in<bool> ready; sc_out<sc_uint<32> > data;
}
}; sc_out<bool> start; sc_out<bool> datardy; sc_inout<sc_uint<8> > data8; sc_uint<32> tdata; sc_uint<32> taddr; void xfer();
SC_CTOR(bus) {
SC_CTHREAD(xfer, clock.pos());
// ready to accept new address datardy = true;
A. Milenkovic 38
// bus.cc
#include "bus.h" void bus::xfer() { while (true) {
// wait for a new address to appear wait_until( newaddr.delayed() == true);
// got a new address so process it taddr = addr.read(); datardy = false; // cannot accept new address now data8 = taddr.range(7,0); start = true; // new addr for memory controller wait();
// wait 1 clock between data transfers data8 = taddr.range(15,8); start = false; wait(); data8 = taddr.range(23,16); wait(); data8 = taddr.range(31,24); wait();
A. Milenkovic 39
}
}
// now wait for ready signal from memory controller wait_until(ready.delayed() == true);
// now transfer memory data to databus tdata.range(7,0) = data8.read(); wait(); tdata.range(15,8) = data8.read(); wait(); tdata.range(23,16) = data8.read(); wait(); tdata.range(31,24) = data8.read(); data = tdata; datardy = true; // data is ready, new addresses ok
A. Milenkovic 40
Modify bus controller by allowing the bus controller to be interrupted during the transfer from the memory but not to the memory
...
// now wait for ready signal from memory controller wait_until(ready.delayed() == true);
A. Milenkovic 41
}
}
W_BEGIN watching(reset.delayed()); // Active value of reset will trigger watching
W_DO
// the rest of this block is as before tdata.range(7,0) = data8.read(); // now transfer memory data to databus wait(); tdata.range(15,8) = data8.read(); wait(); tdata.range(23,16) = data8.read(); wait(); tdata.range(31,24) = data8.read(); data = tdata; datardy = true; // data is ready, new addresses ok
W_ESCAPE if (reset) {
} datardy = false;
W_END
A. Milenkovic 42