12.5
Verilog and Logic Synthesis
A top-down design approach using Verilog begins with a single
module
at the top of the hierarchy to model the input and output response of the ASIC:
module
MyChip_ASIC(); ... (code to model ASIC I/O) ...
endmodule
;
This top-level Verilog module is used to simulate the ASIC I/O connections and any bus I/O during the earliest stages of design. Often the reason that designs fail is lack of attention to the connection between the ASIC and the rest of the system.
As a designer, you proceed down through the hierarchy as you add lower-level modules to the top-level Verilog module. Initially the lower-level modules are just empty placeholders, or
stubs
, containing a minimum of code. For example, you might start by using inverters just to connect inputs directly to the outputs. You expand these stubs before moving down to the next level of modules.
module
MyChip_ASIC()
// behavioral "always", etc. ...
SecondLevelStub1 port mapping
SecondLevelStub2 port mapping
...
endmodule
module
SecondLevelStub1() ...
assign
Output1 = ~Input1;
endmodule
module
SecondLevelStub2() ...
assign
Output2 = ~Input2;
endmodule
Eventually the Verilog modules will correspond to the various component pieces of the ASIC.
12.5.1 Verilog Modeling
Before we could start synthesis of the Viterbi decoder we had to alter the model for the D flip-flop. This was because the original flip-flop model contained syntax (multiple
wait
statements in an
always
statement) that was acceptable to the simulation tool but not by the synthesis tool. This example was artificial because we had already prepared and tested the Verilog code so that it was acceptable to the synthesis software (we say we created
synthesizable
code). However, finding ourselves with nonsynthesizable code arises frequently in logic synthesis. The original OVI LRM included a
synthesis policy
, a set of guidelines that outline which parts of the Verilog language a synthesis tool should support and which parts are optional. Some EDA vendors call their synthesis policy a
modeling style
. There is no current standard on which parts of an HDL (either Verilog or VHDL) a synthesis tool should support.
It is essential that the structural model created by a synthesis tool is
functionally identical
, or
functionally equivalent
, to your behavioral model. Hopefully, we know this is true if the synthesis tool is working properly. In this case the logic is “correct by construction.” If you use different HDL code for simulation and for synthesis, you have a problem. The process of
formal verification
can prove that two logic descriptions (perhaps structural and behavioral HDL descriptions) are identical in their behavior. We shall return to this issue in Chapter 13.
Next we shall examine Verilog and VHDL from the following viewpoint: “How do I write synthesizable code?”
12.5.2
Delays in Verilog
Synthesis tools ignore delay values. They must—how can a synthesis tool guarantee that logic will have a certain delay? For example, a synthesizer cannot generate hardware to implement the following Verilog code:
module
Step_Time(clk, phase);
input
clk;
output
[2:0] phase;
reg
[2:0] phase;
always
@(
posedge
clk)
begin
phase <= 4'b0000;
phase <= #1 4'b0001; phase <= #2 4'b0010;
phase <= #3 4'b0011; phase <= #4 4'b0100;
end
endmodule
We can avoid this type of timing problem by dividing a clock as follows:
module
Step_Count (clk_5x, phase);
input
clk_5x;
output
[2:0] phase;
reg
[2:0] phase;
always
@(
posedge
clk_5x)
case
(phase)
0:phase = #1 1; 1:phase = #1 2; 2:phase = #1 3; 3:phase = #1 4;
default
: phase = #1 0;
endcase
endmodule
12.5.3
Blocking and Nonblocking Assignments
There are some synthesis limitations that arise from the different types of Verilog assignment statements. Consider the following shift-register model:
module
race(clk, q0);
input
clk, q0;
reg
q1, q2;
always
@(
posedge
clk) q1 = #1 q0;
always
@(
posedge
clk) q2 = #1 q1;
endmodule
This example has a
race condition
(or a
race
) that occurs as follows. The synthesizer ignores delays and the two
always
statements are procedures that execute concurrently. So, do we update
q1
first and then assign the new value of
q1
to
q2
? or do we update
q2
first (with the old value of
q1
), and then update
q1
? In real hardware two signals would be racing each other—and the winner is unclear. We must think like the hardware to guide the synthesis tool. Combining the assignment statements into a single
always
statement, as follows, is one way to solve this problem:
module
no_race_1(clk, q0, q2);
input
clk, q0;
output
q2;
reg
q1, q2;
always
@(
posedge
clk)
begin
q2 = q1; q1 = q0;
end
endmodule
Evaluation is sequential within an
always
statement, and the order of the assignment statements now ensures
q2
gets the old value of
q1
—before we update
q1
.
We can also avoid the problem if we use nonblocking assignment statements,
module
no_race_2(clk, q0, q2);
input
clk, q0;
output
q2;
reg
q1, q2;
always
@(
posedge
clk) q1 <= #1 q0;
always
@(
posedge
clk) q2 <= #1 q1;
endmodule
This code updates all the registers together, at the end of a time step, so
q2
always gets the old value of
q1
.
12.5.4
Combinational Logic in Verilog
To model combinational logic, the sensitivity list of a Verilog always statement must contain only signals with no edges (no reference to keywords
posedge
or
negedge
). This is a
level-sensitive
sensitivity list—as in the following example that implies a two-input AND gate:
module
And_Always(x, y, z);
input
x,y;
output
z;
reg
z;
always
@(x
or
y) z <= x & y; // combinational logic method 1
endmodule
Continuous assignment statements also imply combinational logic (notice that
z
is now a
wire
rather than a
reg
),
module
And_Assign(x, y, z);
input
x,y;
output
z;
wire
z;
assign
z <= x & y; // combinational logic method 2 = method 1
endmodule
We may also use concatenation or bit reduction to synthesize combinational logic functions,
module
And_Or (a,b,c,z);
input
a,b,c;
output
z;
reg
[1:0]z;
always
@(a
or
b
or
c)
begin
z[1]<= &{a,b,c}; z[2]<= |{a,b,c};
end
endmodule
module
Parity (BusIn, outp);
input
[7:0] BusIn;
output
outp;
reg
outp;
always
@(BusIn)
if
(^Busin == 0) outp = 1;
else
outp = 0;
endmodule
The number of inputs, the types, and the drive strengths of the synthesized combinational logic cells will depend on the speed, area, and load requirements that you set as constraints.
You must be careful if you reference a signal (
reg
or
wire
) in a level-sensitive
always
statement and do not include that signal in the sensitivity list. In the following example, signal
b
is missing from the sensitivity list, and so this code should be flagged with a warning or an error by the synthesis tool—even though the code is perfectly legal and acceptable to the Verilog simulator:
module
And_Bad(a, b, c);
input
a, b;
output
c;
reg
c;
always
@(a) c <= a & b; // b is missing from this sensitivity list
endmodule
It is easy to write Verilog code that will simulate, but that does not make sense to the synthesis software. You must think like the hardware. To avoid this type of problem with combinational logic inside an
always
statement you should either:
-
include all variables in the event expression or
-
assign to the variables before you use them
For example, consider the following two models:
module
CL_good(a, b, c);
input
a, b;
output
c;
reg
c;
always
@(a or b)
begin
c = a + b; d = a & b; e = c + d;
end
// c, d: LHS before RHS
endmodule
module
CL_bad(a, b, c);
input
a, b;
output
c;
reg
c;
always
@(a or b)
begin
e = c + d; c = a + b; d = a & b;
end
// c, d: RHS before LHS
endmodule
In
CL_bad
, the signals
c
and
d
are used on the right-hand side (RHS) of an assignment statement before they are defined on the left-hand side (LHS) of an assignment statement. If the logic synthesizer produces combinational logic for
CL_bad
, it should warn us that the synthesized logic may not match the simulation results.
When you are describing combinational logic you should be aware of the complexity of logic optimization. Some combinational logic functions are too difficult for the optimization algorithms to handle. The following module,
Achilles
, and large parity functions are examples of hard-to-synthesize functions. This is because most logic-optimization algorithms calculate the complement of the functions at some point. The complements of certain functions grow exponentially in the number of their product terms.
// The complement of this function is too big for synthesis.
module
Achilles (out, in);
output
out;
input
[30:1] in;
assign
out = in[30]&in[29]&in[28] | in[27]&in[26]&in[25]
| in[24]&in[23]&in[22] | in[21]&in[20]&in[19]
| in[18]&in[17]&in[16] | in[15]&in[14]&in[13]
| in[12]&in[11]&in[10] | in[9] & in[8]&in[7]
| in[6] & in[5]&in[4] | in[3] & in[2]&in[1];
endmodule
In a case like this you can isolate the problem function in a separate module. Then, after synthesis, you can use directives to tell the synthesizer not to try and optimize the problem function.
12.5.5 Multiplexers In Verilog
We imply a MUX using a
case
statement, as in the following example:
module
Mux_21a(sel, a, b, z);
input
sel, a , b;
output
z;
reg
z;
always
@(a
or
b
or
sel)
begin case
(sel) 1'b0: z <= a; 1'b1: z <= b;
end
endmodule
Be careful using
'x'
in a
case
statement.
Metalogical values (such as
'x'
) are not “real” and are only valid in simulation (and they are sometimes known as
simbits
for that reason). For example, a synthesizer cannot make logic to model the following and will usually issue a warning to that effect:
module
Mux_x(sel, a, b, z);
input
sel, a, b;
output
z;
reg
z;
always
@(a
or
b
or
sel)
begin case
(sel) 1'b0: z <= 0; 1'b1: z <= 1; 1'bx: z <= 'x';
end
endmodule
For the same reason you should avoid using
casex
and
casez
statements.
An
if
statement can also be used to imply a MUX as follows:
module
Mux_21b(sel, a, b, z);
input
sel, a, b;
output
z;
reg
z;
always
@(a
or
b
or
sel)
begin if
(sel) z <= a
else
z <= b;
end
endmodule
However, if you do not always assign to an output, as in the following code, you will get a latch:
module
Mux_Latch(sel, a, b, z);
input
sel, a, b;
output
z;
reg
z;
always
@(a
or
sel)
begin if
(sel) z <= a;
end
endmodule
It is important to understand why this code implies a sequential latch and not a combinational MUX. Think like the hardware and you will see the problem. When
sel
is zero, you can pass through the
always
statement whenever a change occurs on the input
a
without updating the value of the output
z
. In this situation you need to “remember” the value of
z
when
a
changes. This implies sequential logic using
a
as the latch input,
sel
as the active-high latch enable, and
z
as the latch output.
The following code implies an 8:1 MUX with a three-state output:
module
Mux_81(InBus, sel, OE, OutBit);
input
[7:0] InBus;
input
[2:0] Sel;
input
OE;
output
OutBit;
reg
OutBit;
always
@(OE
or
sel
or
InBus)
begin
if
(OE == 1) OutBit = InBus[sel];
else
OutBit = 1'bz;
end
endmodule
When you synthesize a large MUX the required speed and area, the output load, as well as the cells that are available in the cell library will determine whether the synthesizer uses a large MUX cell, several smaller MUX cells, or equivalent random logic cells. The synthesized logic may also use different logic cells depending on whether you want the fastest path from the select input to the MUX output or from the data inputs to the MUX output.
12.5.6 The Verilog Case Statement
Consider the following model:
module
case8_oneHot(oneHot, a, b, c, z);
input
a, b, c;
input
[2:0] oneHot;
output
z;
reg
z;
always
@(oneHot
or
a
or
b
or
c)
begin case
(oneHot) //synopsys full_case
3'b001: z <= a; 3'b010: z <= b; 3'b100: z <= c;
default: z <= 1'bx;
endcase
end
endmodule
By including the
default
choice, the
case
statement is
exhaustive
. This means that every possible value of the select variable (
oneHot
) is accounted for in the arms of the
case
statement. In some synthesizers (Synopsys, for example) you may indicate the arms are exhaustive and imply a MUX by using a
compiler directive
or
synthesis directive
. A compiler directive is also called a
pseudocomment
if it uses the comment format (such as
//synopsys full_case
). The format of pseudocomments is very specific. Thus, for example,
//synopys
may be recognized but
// synopys
(with an extra space) or
//SynopSys
(uppercase) may not. The use of pseudocomments shows the problems of using an HDL for a purpose for which it was not intended. When we start “extending” the language we lose the advantages of a standard and sacrifice portability. A compiler directive in module
case8_oneHot
is unnecessary if the
default
choice is included. If you omit the
default
choice and you do not have the ability to use the
full_case
directive (or you use a different tool), the synthesizer will infer latches for the output
z
.
If the default in a
case
statement is
'x'
(signifying a
synthesis don’t care value
), this gives the synthesizer flexibility in optimizing the logic. It does not mean that the synthesized logic output will be unknown when the default applies. The combinational logic that results from a
case
statement when a don’t care (
'x'
) is included as a default may or may not include a MUX, depending on how the logic is optimized.
In
case8_oneHot
the choices in the arms of the
case
statement are exhaustive and also
mutually exclusive
. Consider the following alternative model:
module
case8_priority(oneHot, a, b, c, z);
input
a, b, c;
input
[2:0] oneHot;
output
z;
reg
z;
always
@(oneHot
or
a
or
b
or
c)
begin
case
(1'b1) //synopsys parallel_case
oneHot[0]: z <= a;
oneHot[1]: z <= b;
oneHot[2]: z <= c;
default: z <= 1'bx;
endcase
end
endmodule
In this version of the
case
statement the choices are not necessarily mutually exclusive (
oneHot[0]
and
oneHot[2]
may both be equal to
1'b1
, for example). Thus the code implies a priority encoder. This may not be what you intended. Some logic synthesizers allow you to indicate mutually exclusive choices by using a directive (
//synopsys parallel_case
, for example). It is probably wiser not to use these “outside-the-language” directives if they can be avoided.
12.5.7
Decoders In Verilog
The following code models a 4:16 decoder with enable and three-state output:
module
Decoder_4To16(enable, In_4, Out_16); // 4-to-16 decoder
input
enable;
input
[3:0] In_4;
output
[15:0] Out_16;
reg
[15:0] Out_16;
always
@(enable
or
In_4)
begin
Out_16 = 16'hzzzz;
if
(enable == 1)
begin
Out_16 = 16'h0000; Out_16[In_4] = 1;
end
end
endmodule
In line
7
the binary-encoded 4-bit input sets the corresponding bit of the 16-bit output to
'1'
.
The synthesizer infers a three-state buffer from the assignment in line
5
. Using the equality operator,
'=='
, rather than the case equality operator,
'==='
, makes sense in line
6
, because the synthesizer cannot generate logic that will check for
enable
being
'x'
or
'z'
. So, for example, do not write the following (though some synthesis tools will still accept it):
if
(enable === 1) // can't make logic to check for enable = x or z
12.5.8 Priority Encoder in Verilog
The following Verilog code models a priority encoder with three-state output:
module
Pri_Encoder32 (InBus, Clk, OE, OutBus);
input
[31:0]InBus;
input
OE, Clk;
output
[4:0]OutBus;
reg
j;
reg
[4:0]OutBus;
always
@(
posedge
Clk)
begin
if
(OE == 0) OutBus = 5'bz ;
else
begin
OutBus = 0;
for
(j = 31; j >= 0; j = j - 1)
begin if
(InBus[j] == 1) OutBus = j;
end
end
end
endmodule
In lines
9
–
11
the binary-encoded output is set to the position of the lowest-indexed
'1'
in the input bus. The logic synthesizer must be able to unroll the loop in a
for
statement. Normally the synthesizer will check for fixed (or
static) bounds on the loop limits, as in line
9
above.
12.5.9
Arithmetic in Verilog
You need to make room for the carry bit when you add two numbers in Verilog. You may do this using concatenation on the LHS of an assignment as follows:
module
Adder_8 (A, B, Z, Cin, Cout);
input
[7:0] A, B;
input
Cin;
output
[7:0] Z;
output
Cout;
assign
{Cout, Z} = A + B + Cin;
endmodule
In the following example, the synthesizer should recognize
'1'
as a carry-in bit of an adder and should synthesize one adder and not two:
module
Adder_16 (A, B, Sum, Cout);
input
[15:0] A, B;
output
[15:0] Sum;
output
Cout;
reg
[15:0] Sum;
reg
Cout;
always
@(A
or
B) {Cout, Sum} = A + B + 1;
endmodule
It is always possible to synthesize adders (and other arithmetic functions) using random logic, but they may not be as efficient as using datapath synthesis (see
Section 12.5.12
).
A logic sythesizer may infer two adders from the following description rather than shaping a single adder.
module
Add_A (sel, a, b, c, d, y);
input
a, b, c, d, sel;
output
y;
reg
y;
always
@(sel
or
a
or
b
or
c
or
d)
begin
if
(sel == 0) y <= a + b;
else
y <= c + d;
end
endmodule
To imply the presence of a MUX before a single adder we can use temporary variables. For example, the synthesizer should use only one adder for the following code:
module
Add_B (sel, a, b, c, d, y);
input
a, b, c, d, sel;
output
y;
reg
t1, t2, y;
always
@(sel
or
a
or
b
or
c
or
d)
begin
if
(sel == 0)
begin
t1 = a; t2 = b;
end
// Temporary
else
begin
t1 = c; t2 = d;
end
// variables.
y = t1 + t2;
end
endmodule
If a synthesis tool is capable of performing
resource allocation
and
resource sharing
in these situations, the coding style may not matter. However we may want to use a different tool, which may not be as advanced, at a later date—so it is better to use
Add_B
rather than
Add_A
if we wish to conserve area. This example shows that the simplest code (
Add_A
) does not always result in the simplest logic (
Add_B
).
Multiplication in Verilog assumes nets are unsigned numbers:
module
Multiply_unsigned (A, B, Z);
input
[1:0] A, B;
output
[3:0] Z;
assign
Z <= A * B;
endmodule
To multiply signed numbers we need to extend the multiplicands with their sign bits as follows (some simulators have trouble with the concatenation
'{}'
structures, in which case we have to write them out “long hand”):
module
Multiply_signed (A, B, Z);
input
[1:0] A, B;
output
[3:0] Z;
// 00 -> 00_00 01 -> 00_01 10 -> 11_10 11 -> 11_11
assign
Z = { { 2{A[1]} }, A} * { { 2{B[1]} }, B};
endmodule
How the logic synthesizer implements the multiplication depends on the software.
12.5.10
Sequential Logic in Verilog
The following statement implies a positive-edge–triggered D flip-flop:
always
@(
posedge
clock) Q_flipflop = D; // A flip-flop.
When you use edges (
posedge
or
negedge
) in the sensitivity list of an
always
statement, you imply a clocked storage element. However, an
always
statement does not have to be edge-sensitive to imply sequential logic. As another example of sequential logic, the following statement implies a level-sensitive transparent latch:
always
@(clock
or
D)
if
(clock) Q_latch = D; // A latch.
On the negative edge of the clock the
always
statement is executed, but no assignment is made to
Q_latch
. These last two code examples concisely illustrate the difference between a flip-flop and a latch.
Any sequential logic cell or memory element must be initialized. Although you could use an
initial
statement to simulate power-up, generating logic to mimic an
initial
statement is hard. Instead use a reset as follows:
always
@(
posedge
clock
or
negedge
reset)
A problem now arises. When we use two edges, the synthesizer must infer which edge is the clock, and which is the reset. Synthesis tools cannot read any significance into the names we have chosen. For example, we could have written
always
@(
posedge
day
or
negedge
year)
—but which is the clock and which is the reset in this case?
For most synthesis tools you must solve this problem by writing HDL code in a certain format or pattern so that the logic synthesizer may correctly infer the clock and reset signals. The following examples show one possible pattern or
template
. These templates and their use are usually described in a
synthesis style guide
that is part of the synthesis software documentation.
always
@(
posedge
clk
or
negedge
reset)
begin
// template for reset:
if
(reset == 0) Q = 0; // initialize,
else
Q = D; // normal clocking
end
module
Counter_With_Reset (count, clock, reset);
input
clock, reset;
output
count;
reg
[7:0] count;
always
@ (
posedge
clock
or
negedge
reset)
if
(reset == 0) count = 0;
else
count = count + 1;
endmodule
module
DFF_MasterSlave (D, clock, reset, Q); // D type flip-flop
input
D, clock, reset;
output
Q;
reg
Q, latch;
always
@(
posedge
clock
or
posedge
reset)
if
(reset == 1) latch = 0;
else
latch = D; // the master.
always
@(latch) Q = latch; // the slave.
endmodule
The synthesis tool can now infer that, in these templates, the signal that is tested in the
if
statement is the reset, and that the other signal must therefore be the clock.
12.5.11 Component Instantiation in Verilog
When we give an HDL description to a synthesis tool, it will synthesize a netlist that contains generic logic gates. By generic we mean the logic is
technology-independent (it could be CMOS standard cell, FPGA, TTL, GaAs, or something else—we have not decided yet). Only after logic optimization and mapping to a specific ASIC cell library do the speed or area constraints determine the cell choices from a cell library: NAND gates, OAI gates, and so on.
The only way to ensure that the synthesizer uses a particular cell,
'special'
for example, from a specific library is to write structural Verilog and instantiate the cell,
'special'
, in the Verilog. We call this
hand instantiation
. We must then decide whether to allow logic optimization to replace or change
'special'
. If we insist on using logic cell
'special'
and do not want it changed, we flag the cell with a synthesizer command. Most logic synthesizers currently use a pseudocomment statement or set an attribute to do this.
For example, we might include the following statement to tell the Compass synthesizer—“Do not change cell instance
my_inv_8x
.” This is not a standard construct, and it is not portable from tool to tool either.
//Compass dontTouch my_inv_8x or // synopsys dont_touch
INVD8 my_inv_8x(.I(a), .ZN(b) );
(
some compiler directives are trademarks). Notice, in this example, instantiation involves declaring the instance name and defining a
structural port mapping.
There is no standard name for technology-independent models or components—we shall call them
soft models
or
standard components
. We can use the standard components for synthesis or for behavioral Verilog simulation. Here is an example of using standard components for flip-flops (remember there are no primitive Verilog flip-flop models—only primitives for the elementary logic cells):
module
Count4(clk, reset, Q0, Q1, Q2, Q3);
input
clk, reset;
output
Q0, Q1, Q2, Q3;
wire
Q0, Q1, Q2, Q3;
// Q , D , clk, reset
asDff dff0( Q0, ~Q0, clk, reset); // The asDff is a
asDff dff1( Q1, ~Q1, Q0, reset); // standard component,
asDff dff2( Q2, ~Q2, Q1, reset); // unique to one set of tools.
asDff dff3( Q3, ~Q3, Q2, reset);
endmodule
The
asDff
and other standard components are provided with the synthesis tool. The standard components have specific names and interfaces that are part of the software documentation. When we use a standard component such as
asDff
we are saying: “I want a D flip-flop, but I do not know which ASIC technology I want to use—give me a generic version. I do not want to write a Verilog model for the D flip-flop myself because I do not want to bother to synthesize each and every instance of a flip-flop. When the time comes, just map this generic flip-flop to whatever is available in the technology-dependent (vendor-specific) library.”
If we try and simulate
Count4
we will get an error,
:Count4.v: L5: error: Module 'asDff' not defined
(and three more like this) because
asDff
is not a primitive Verilog model. The synthesis tool should provide us with a model for the standard component. For example, the following code models the behavior of the standard component,
asDff
:
module
asDff (D, Q, Clk, Rst);
parameter
width = 1, reset_value = 0;
input
[width-1:0] D;
output
[width-1:0] Q; reg [width-1:0] Q;
input
Clk,Rst;
initial
Q = {width{1'bx}};
always
@ (
posedge
Clk
or
negedge
Rst )
if
( Rst==0 ) Q <= #1 reset_value;
else
Q <= #1 D;
endmodule
When the synthesizer compiles the HDL code in
Count4
, it does not parse the
asDff
model. The software recognizes
asDff
and says “I see you want a flip-flop.” The first steps that the synthesis software and the simulation software take are often referred to as compilation, but the two steps are different for each of these tools.
Synopsys has an extensive set of libraries, called
DesignWare
, that contains standard components not only for flip-flops but for arithmetic and other complex logic elements. These standard components are kept protected from optimization until it is time to map to a vendor technology. ASIC or EDA companies that produce design software and cell libraries can tune the synthesizer to the silicon and achieve a more efficient mapping. Even though we call them standard components, there are no standards that cover their names, use, interfaces, or models.
12.5.12
Datapath Synthesis in Verilog
Datapath synthesis is used for bus-wide arithmetic and other bus-wide operations. For example, synthesis of a 32-bit multiplier in random logic is much less efficient than using datapath synthesis. There are several approaches to datapath synthesis:
-
Synopsys VHDL DesignWare. This models generic arithmetic and other large functions (counters, shift registers, and so on) using standard components. We can either let the synthesis tool map operators (such as
'+'
) to VHDL DesignWare components, or we can hand instantiate them in the code. Many ASIC vendors support the DesignWare libraries. Thus, for example, we can instantiate a DesignWare counter in VHDL and map that to a cell predesigned and preoptimized by Actel for an Actel FPGA.
-
Compiler directives. This approach uses
synthesis directives in the code to steer the mapping of datapath operators either to specific components (a two-port RAM or a register file, for example) or flags certain operators to be implemented using a certain style (
'+'
to be implemented using a ripple-carry adder or a carry-lookahead adder, for example).
-
X-BLOX is a system from Xilinx that allows us to keep the logic of certain functions (counters, arithmetic elements) together. This is so that the layout tool does not splatter the synthesized CLBs all over your FPGA, reducing the performance of the logic.
-
LPM (
library of parameterized modules) and
RPM (
relationally placed modules) are other techniques used principally by FPGA companies to keep logic that operates on related data close together. This approach is based on the use of the EDIF language to describe the modules.
In all cases the disadvantage is that the code becomes specific to a certain piece of software. Here are two examples of datapath synthesis directives:
module
DP_csum(A1,B1,Z1);
input
[3:0] A1,B1;
output
Z1;
reg
[3:0] Z1;
always
@(A1
or
B1) Z1 <= A1 + B1;//Compass adder_arch cond_sum_add
endmodule
module
DP_ripp(A2,B2,Z2);
input
[3:0] A2,B2;
output
Z2;
reg
[3:0] Z2;
always
@(A2
or
B2) Z2 <= A2 + B2;//Compass adder_arch ripple_add
endmodule
These directives steer the synthesis of a conditional-sum adder (usually the fastest adder implementation) or a ripple-carry adder (small but slow).
There are some limitations to datapath synthesis. Sometimes, complex operations are not synthesized as we might expect. For example, a datapath library may contain a subtracter that has a carry input; however, the following code may synthesize to random logic, because the synthesizer may not be able to infer that the signal
CarryIn
is a subtracter carry:
module
DP_sub_A(A,B,OutBus,CarryIn);
input
[3:0] A, B ;
input
CarryIn ;
output
OutBus ;
reg
[3:0] OutBus ;
always
@(A
or
B
or
CarryIn) OutBus <= A - B - CarryIn ;
endmodule
If we rewrite the code and subtract the carry as a constant, the synthesizer can more easily infer that it should use the carry-in of a datapath subtracter:
module
DP_sub_B (A, B, CarryIn, Z) ;
input
[3:0] A, B, CarryIn ;
output
[3:0] Z;
reg
[3:0] Z;
always
@(A
or
B
or
CarryIn)
begin
case
(CarryIn)
1'b1 : Z <= A - B - 1'b1;
default
: Z <= A - B - 1'b0;
endcase
end
endmodule
This is another example of thinking like the hardware in order to help the synthesis tool infer what we are trying to imply.
[ Chapter start ] [ Previous page ] [ Next page ] |