1 Synthesis flow outline The synthesis route leads from the
RTL description of the circuit in SystemC, via translation to
Verilog performed by CoCentric, compilation of the Verilog source
with FPGA CompilerII to EDIF and placement and routing with Webpack.
In the first step, SystemC sources are translated to Verilog files.
The translation is performed by invoking compile_systemc
command of Synopsys CoCentric SystemC compiler. In the process of
translation, the general structure of the modules is preserved. Each
SystemC module (declared as SC_MODULE(module_name)) is
translated into a separate Verilog module and placed in the
module_name.v file. Each process within a module (declared
as SC_METHOD(process)) is translated to an always
block labeled with the process name. Also, the variables and ports
present in the SystemC code are preserved under the same name in the
Verilog source. In the second step, the Verilog files are compiled
by Synopsys FPGA CompilerII into the optimized network of Spartan2
primitives: look up tables, multiplexers, memories, etc. This
network can be exported in the EDIF format for interfacing with
place and route tools and in Verilog (or VHDL) for functional
simulation. Once the project is compiled, the user may invoke
analysis tools available in FPGA CompilerII to analyze and debug the
design. It is possible to view the hierarchical schematics of the
design and access detailed timing information. Finally, Webpack
suite is used to place and route the design exported from
FPGACompilerII in EDIF format, generate the FPGA programming file
and program the device.
2 Synthesis flow details
2.1 CoCentricTo translate SystemC sources to Verilog,
Synopsys CoCentric SystemC compiler is used. This compiler supports
only selected constructs of the standard language, a subset of
SystemC Version 1.0. Therefore, before the compilation is performed,
the SystemC sources need to be modified to comply with the
requirements of CoCentric. These requirements are discussed in
Section "Synthesizable SystemC" After the source have been modified,
Synopsys Design Compiler (DC) shell must be invoked with the
command: dc_shell
The DC after a number of information messages about version
and environment presents user with a command prompt. The SystemC
sources can be compiled by entering at the prompt the command: compile_systemc -rtl -rtl_format verilog -cpp_options "-DOPT1 -DOPT2" and.cpp
The options inform the compiler that the SystemC description
is an RTL description of the design and that the desired output
format is Verilog. Additionally, the C++ preprocessor, which is run
on the file source.cpp before compilation will be called
with options -DOPT1 -DOPT2. The compilation produces a
number of information messages, as well as warnings and errors.
Finally, a single number is printed corresponding to the exit status
of compilation: ``1'' on success and ``0'' on error. A sequence of
compile_systemc commands for a number SystemC files can be
for efficiency put together in a DC script file, which can be then
executed with the command: dc_shell -f script
The figures below show an example of translation of a file
and.cpp (with header and.h) containing two modules: AND2 and AND3
(2- and 3-input AND gate). As mentioned in Section "Synthesis flow
outline", translation produces one verilog file containing one
module for each SystemC module in the translated file. Note how the
names of the modules and variables are preserved in the translation
process. Also, the sensitivity list of the always block
corresponds exactly to the sensitivity list of the SystemC process
and the always block itself is labeled with the name of
SystemC process (and_process).
Figure: and.h
#include <systemc.h>
SC_MODULE(AND2)
{
sc_in< bool > a, b;
sc_out< bool > r;
void and_process();
SC_CTOR(AND2) {
SC_METHOD(and_process);
sensitive << a << b;
}
};
SC_MODULE(AND3)
{
sc_in< bool > a, b, c;
sc_out< bool > r;
void and_process();
SC_CTOR(AND3) {
SC_METHOD(and_process);
sensitive << a << b << c;
}
};
|
Figure: and.cpp
#include "and.h"
void AND2::and_process()
{
bool a_tmp, b_tmp, r_tmp;
a_tmp = a.read();
b_tmp = b.read();
r_tmp = a_tmp && b_tmp;
r.write( r_tmp );
}
void AND3::and_process()
{
bool a_tmp, b_tmp, c_tmp, r_tmp;
a_tmp = a.read();
b_tmp = b.read();
c_tmp = c.read();
r_tmp = a_tmp && b_tmp && c_tmp;
r.write( r_tmp );
}
|
Figure: AND2.v
module AND2( a, b, r );
input a;
wire a;
input b;
wire b;
output r;
reg r;
reg a_tmp, b_tmp, r_tmp;
always @( a or b )
begin : and_process
a_tmp = a;
b_tmp = b;
r_tmp = a && b;
r = r_tmp;
end
endmodule
|
Figure: AND3.v
module AND3( a, b, c, r );
input a;
wire a;
input b;
wire b;
output r;
reg r;
reg a_tmp, b_tmp, c_tmp, r_tmp;
always @( a or b or c )
begin : and_process
a_tmp = a;
b_tmp = b;
c_tmp = c;
r_tmp = a && b && c;
r = r_tmp;
end
endmodule
|
For the remote access to CoCentric compiler, a script is
prepared that will transfer the SystemC project files to the remote
host, execute an appropriate DesignCompiler script to translate them
to Verilog and transfer the Verilog files back.
2.2 FPGA CompilerIIThe Verilog files produced by CoCentric
form a project, which can be created and compiled by FPGA
CompilerII. FPGA Compiler's graphical Design Wizard can be used for
assisted project creation and compilation. In this scenario, the
user is asked in the series of message boxes to select the source
files, target architecture and determine basic optimization
objectives (target clock rate, area/time optimization, etc). Then,
the project is automatically created, compiled and the resulting
netlist is exported. The details of the DesignWizard synthesis flow,
as well as more advanced flows, both in graphical and command-line
environments can be found in [3]
in Chapters 2, 3 and 5.
The
final processing of the project is performed by the tools from
Xilinx Webpack suite. The shell script (MapEdif.bat) is
provided that calls appropriate tools. The script is called with a
single argument - the project name, e.g. MapEdif.bat
project. It assumes that the EDIF file project.edf is
present in the current directory, as well as the UCF file
project.ucf. The UCF file is needed to associate ports of
the top-level design with pads of the FPGA device. It is an ASCII
file familiar to the Webpack users (edited with "Edit UCF file"
command of Webpack). It can be edited with any text editor. The port
assignment constraints are specified by the lines in the following
format: NET "DATA<0>" LOC = "P99";
This constraint associates bit 0 of the input bus
DATA to the pin P99 of the FPGA device. The content of
MapEdif.bat script illustrates the steps of the mapping
process. First, the EDIF network is translated into Webpack internal
format with the command ngdbuild. Then, the network is
mapped to the target Spartan architecture. In this case the mapping
is trivial, since FPGA CompilerII already produced the network of
Spartan primitives. Then, the network is placed and routed, and
finally, the programming file for FPGA is produced. Additionally, a
timing report is generated in file <project>.twr.
Now, the ``Device Programming'' tool can be started to program the
FPGA with the programming file. It is also possible to use ModelSim
to simulate the design synthesized by FPGA CompilerII. For this
purpose, FCII can export the synthesized network in Verilog format.
The detailed description of the Webpack suite tools can be found
in [2].
3 Synthesizable SystemC
3.1 LanguageCoCentric SystemC compiler imposes certain
constraints on the SystemC code, which are not present in the
language. In particular, in the discussed synthesis flow, SystemC
sources need to comply with the requirements set by CoCentric for
RTL description. The detailed discussion of these requirements can
be found in the document [1].
Most of the combinatorial modules can be made synthesizable simply
by replacing the description of the functionality with
SC_THREAD, which suggests behavioral description, with
SC_METHOD, which corresponds with RTL description. The
necessary steps are the following:
- Replace in the constructor of the module
SC_THREAD(thread_name)
with
SC_METHOD(thread_name).
- Delete from the source of the thread (method, from now on)
while(true) statement, leaving the loop's body unchanged.
- Delete from the source of the thread wait()
statements.
Also, one of the most common requirements, not
explicitly mentioned in the Synopsys document, is the exact
equivalence of types and sizes of variables in assignment and
comparison. Some examples follow:
- It is not possible to compare a variable of
sc_bv<> type with integer. Statements of the type
sc_bv<1> bv;
if(bv == 1) ...
need to be replaced with sc_bv<1> bv;
sc_uint<1> ui = bv;
if(ui == 1) ...
or if(bv[0] == 1) ...
- Variables need to have the same bit-lengths for assignment
operator. E.g.
sc_bv<4> bv4;
sc_bv<8> bv8;
bv8 = bv4;
has to be replaced with sc_bv<4> bv4, zero4b(0) /* 4-bit long vector of zeros */;
sc_bv<8> bv8;
bv8 = (zero4b, bv4); /* concatenate bv4 with zeros */
For examples of synthesizable description of
sequential logic and other modules, see [1].
3.2 Project structureIn the regular SystemC flow, the
structure of the top-level design is determined in function
sc_main by declaration of appropriate modules and
connecting them with signals. Since CoCentric does not support
sc_main function, it is necessary to create an additional
SystemC module, which will reflect the structure of the top-level
design, i.e. instantiate the modules and create the connections. The
creation of such hierarchical module is discussed in [1]
on page 2-26. The fact that the top-level module replaces
sc_main function means that the the main project file
(containing sc_main) no longer needs to be translated.
Instead, the new top-level module file is translated to Verilog with
CoCentric. Naturally, it is still possible to simulate thus modified
design. Instead of describing the structure of the entire design in
sc_main, it is sufficient to instantiate only one module -
the newly created top-level module. An example of the modified
sc_main is shown below. The example assumes that the
top-level module named TOPLEVEL was defined in toplevel.h.
Figure: sc_main with
top-level module
#include "toplevel.h"
int sc_main(int argc, char *argv[]){
// instantiate top-level design
TOPLEVEL design("design");
// connect inputs and outputs of the top-level design
sc_signal< sc_uint<32> > in1, in2, out;
sc_signal< bool > clk;
design.in1(in1);
design.in2(in2);
design.out(out);
design.clk(clk);
// define traces
sc_trace_file *tf;
tf = sc_create_vcd_trace_file("design");
// trace in/out
sc_trace(tf, in1, "in1");
sc_trace(tf, in2, "in2");
sc_trace(tf, out, "out");
sc_trace(tf, clk, "clk");
// trace internal sc_signals of the design
sc_trace(tf, design.bus1, "design.bus1");
sc_trace(tf, design.bus2, "design.bus2");
...
}
|
3.3 TipsIncorrect system behavior can be caused by
unexpected appearence after synthesis of registers in the places,
where strictly combinational logic was designed. The most common
cause of such surprise is forgetting to specify a value for a
variable in some conditions. The compiler assumes, that in these
conditions the value of the variable remains unchanged (the same as
the current value), and therefore a register is necessary to store
the value. Consider the example of the switch statement: sc_uint<2> in = input.read();
switch( in ){
case 0 : output = 1; break;
case 1 : output = 1; break;
case 2 : output = 2; break;
}
or the equivalent if statements: if( in==0 || in==1 ) output = 1;
else if( in == 2 ) output = 2;
Since the value of the output for the input value 3 is not
specified, the value will have to remain the same as it was
previously. To remember that previous value, the output variable
will have to be registered. To avoid this pitfall, it is necessary
to always specify the value of a combinational variable for all
input conditions, or supply the default value. The default value in
the above example can be supplied by adding to the case statement
the line default : output = 0; break;
It can also be supplied by setting the output to the default
value before any other assignment, e.g. sc_uint<2> in = input.read();
output = 0; /* default value */
switch( in ){
case 0 : output = 1; break;
case 1 : output = 1; break;
case 2 : output = 2; break;
}
An unexpected registered variable can often be identified by
an unexpected clock signal appearing in the timing report of FPGA
CompilerII.
Bibliography1 ``Describing
Synthesizable RTL in SystemC''
2 Webpack
documentation Chapter "Development System Reference Guide".
3 ``FPGA
CompilerII User Guide''
|