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,

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,

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.