Unit 3
Unit 3
Up to this point, we have described the basic features of Verilog and how they can
be used in the digital system design process. In this chapter, we describe additional
features of Verilog that illustrate its power and flexibility. Verilog functions and
tasks are presented. Several additional features such as user-defined primitives,
generate statements, compiler directives, built-in primitives, file I/O, and others
are presented. A simple memory model is presented to illustrate the use of tristate
signals.
A function call can be used anywhere that an expression can be used. For example,
if A 5 10010101, the statement
B <= rotate_right(A);
would set B equal to 11001010 and leave A unchanged.
The general form of a function declaration is
function <range or type> function-name
input [declarations]
<declarations> // reg, parameter, integer, etc.
begin
sequential statements
end
endfunction
The number and type of parameters on the input-argument-list must match the
input [declaration] in the function declaration. The parameters are treated as input
values and cannot be changed during the execution of the function.
Write a Verilog function for generating an even parity bit for a 4-bit number. The input is a
Example 4-bit number and the output is a code word that contains the data and the parity bit.
The answer is shown in Figure 8-1. The function name and the final output to be
returned from the function must be the same.
begin
^ ^ ^
temp_parity = A[0] A[1] A[2] A[3];
8.1 Verilog Functions 433
parity = {A, temp_parity};
end
endfunction
If parity circuits are used in several parts in a system, one could call the function
each time it is desired. The function can be called as follows:
module function_test(Z);
output reg [4:0] Z;
reg [3:0] INP;
initial
begin
INP = 4'b0101;
Z = parity(INP);
end
endmodule
Figure 8-2 illustrates a function using a for loop. In Figure 8-2, the loop index i
will be initialized to 0 when the for loop is entered, and the sequential statements
will be executed. Execution will be repeated for i 5 1, i 5 2, and i 5 3; then the
loop will terminate.
begin
integer i;
for (i=0; i<=3; i=i+1)
begin
cout = (A[i] & B[i]) | (A[i] & cin) | (B[i] & cin);
sum[i] = A[i] ^ B[i] ^ cin;
cin = cout;
end
sum[4] = cout;
add4 = sum;
end
endfunction
434 Chapter 8 Additional Topics in Verilog
The function given in Figure 8-2 adds two 4-bit vectors plus a carry and returns
a 5-bit vector as the sum. The function name is add4; the input arguments are A,
B, and carry; and the add4 will be returned. The local variables Cout and sum are
defined to hold intermediate values during the calculation. When the function is
called, Cin will be initialized to the value of the carry. The for loop adds the bits of
A and B serially in the same manner as a serial adder.
The first time through the loop, Cout and sum[0] are computed using A[0], B[0],
and Cin. Then the Cin value is updated to the new Cout value, and execution of the
loop is repeated. During the second time through the loop, Cout and sum[1] are com-
puted using A[1], B[1], and the new Cin. After four times through the loop, all values
of sum[i] have been computed and sum is returned. The function call is of the form
add4(A, B, carry)
A and B may be replaced with any expressions that evaluate to a return value with
dimensions 3 downto 0, and carry may be replaced with any expression that evalu-
ates to a bit. For example, the statement
Z <= add4(X, ~Y, 1);
calls the function add4. Parameters A, B, and carry are set equal to the values
of X, ~Y, and 1, respectively. X and Y must be multiple bits dimensioned 3:0.
The function computes
Sum 5 A 1 B 1 carry 5 X 1 ~Y 1 1
and returns this value. Since ~Y 1 1 equals the 2’s complement of Y, the compu-
tation is equivalent to subtracting by adding the 2’s complement. If we ignore the
carry stored in Z[4], the result is Z[3:0] 5 X – Y.
Figure 8-3 illustrates the function to compute square of numbers. The function
as well as the function call is illustrated. In the illustrated call to the function, the
number is 4 bits wide.
input CLK;
reg[3:0] FN;
reg[7:0] answer;
begin
squares = Number * Number;
end
endfunction
initial
begin
8.2 Verilog Tasks 435
FN = 4'b0011;
end
A function executes in zero simulation time. Functions must not contain any
timing control statements or delay events. Functions must have at least one input
argument. Functions cannot have output or inout arguments.
Functions can be recursive. Recursive functions must be declared as automatic,
which causes distinct memory to be allocated each time a function is called.
Otherwise, if a function is called recursively, each call will overwrite the memory
allocated in the previous call.
The formal-parameter-list specifies the inputs and outputs to the task and
their types. A task call is a sequential or concurrent statement of the form
task_name(actual-parameter-list);
Unlike functions, a task can be executed in non-zero simulation time. Tasks may
contain delay, event, or timing control statements. Tasks can have zero or more
arguments of type input, output, or inout. Tasks do not return with a value, but they
can pass multiple values through output and inout arguments.
As an example, we will write a task Addvec, which will add two 4-bit data and a
carry and return a 4-bit sum and a carry. We will use a task call of the form
Addvec(A, B, Cin, Sum, Cout);
where A, B, and Sum are 4-bit data and Cin and Cout are 1-bit data.
Figure 8-4 gives the task definition. Add1, Add2, and Cin are input parameters,
and sum and Cout are output parameters. The addition algorithm is essentially the
436 Chapter 8 Additional Topics in Verilog
task Addvec;
input [3:0] Add1;
input [3:0] Add2;
input Cin;
output [3:0] sum;
output cout;
reg C;
begin
C = Cin;
integer i;
for(i = 0; i <= 4; i = i + 1)
begin
sum[i] = Add1[i] ^ Add2[i] ^ C ;
C = (Add1[i] & Add2[i]) | (Add1[i] & C) | (Add2[i] & C);
end
cout = C ;
end
endtask
same as the one used in the add4 function. C must be a variable, since the new value
of C is needed each time through the loop; hence it is declared as reg. After 4 times
through the loop, all 4 bits of sum have been computed. It is desirable not to mix
blocking and non-blocking statements in tasks.
The tasks can be defined in the module that the functions will be used in. The tasks
can also be defined in separate files, and the compile directive ‘include should be used
(‘) (described in Section 8.12) will be used to include the task in the file. The variables
declared within the task are local to the task. When no local variables are used, global
variables will be used for input and output. When only local variables are used within
the task, the variables from the last execution within the task will be passed to the caller.
Functions:
At least one input arguments, but no output or inout arguments
Returns a single value by assigning the value to the function name
Can call other functions, but cannot call tasks
Cannot embed delays, wait statements or any time-controlled statement
Executes in zero time
Can be recursive
Cannot contain non-blocking assignment or procedural continuous
assignments
8.3 Multivalued Logic and Signal Resolution 437
Tasks:
Any number of input, output or inout arguments
Outputs need not use task name
Can call other functions or tasks
May contain time-controlled statements
Task can recursively call itself
d f
c
438 Chapter 8 Additional Topics in Verilog
corresponding Verilog representation in two different ways. Data type X01Z, which
can have the four values X, 0, 1, and Z, is assumed. The tristate buffers have an
active-high output enable, so that when b 5 1 and d 5 0, f 5 a; when b 5 0 and d 5 1,
f 5 c; and when b 5 d 5 0, the f output assumes the high-Z state. If b 5 d 5 1, an
output conflict can occur. Tristate buffers are used for bus management whereby
only one active-high output should be enabled to avoid the output conflict.
input a;
input b;
input c;
input d;
output f;
reg f;
always @(a or b)
begin : buff1
if (b == 1'b1)
f = a ;
else
f = 1'bz ; //"drive" the output high Z when not enabled
end
always @(c or d)
begin : buff2
if (d == 1'b1)
f = c ;
else
f = 1'bz ; //"drive" the output high Z when not enabled
end
endmodule
assign f = b ? a: 1'bz ;
assign f = d ? c: 1'bz ;
endmodule
This table gives the resolved value of a signal for each pair of input values: Z
resolved with any value returns that value, X resolved with any value returns X, and
0 resolved with 1 returns X. If individual wires s(0), s(1), s(2) were to change
as indicated in the following table at the times indicated, signal R in the last column
shows the result of the resolution
Time s(0) s(1) s(2) R
0 Z Z Z Z
2 0 Z Z 0
4 0 1 Z X
6 Z 1 Z 1
8 Z 1 1 1
10 Z 1 0 X
AND and OR functions for the 4-valued logic may be defined using the following
tables:
AND X 0 1 Z OR X 0 1 Z
X X 0 X X X X X 1 X
0 0 0 0 0 0 X 0 1 X
1 X 0 1 X 1 1 1 1 1
Z X 0 X X Z X X 1 X
The first table corresponds to the way an AND gate with 4-valued inputs would
work. If one of the AND gate inputs is 0, the output is always 0. If both inputs are 1,
the output is 1. In all other cases, the output is unknown (X ), since a high-Z gate
input may act like either a 0 or a 1. For an OR gate, if one of the inputs is 1, the out-
put is always 1. If both inputs are 0, the output is 0. In all other cases, the output is X.
gate-level primitives with drive strength among other things. There are 14 prede-
fined logic gate primitives and 12 predefined switch primitives to provide the gate-
and switch-level modeling facility. Modeling at the gate and switch level has several
advantages:
(i) Synthesis tools can easily map it to the desired circuitry since gates provide a
very close one-to-one mapping between the intended circuit and the model.
(ii) There are primitives such as the bidirectional transfer gate that cannot be
otherwise modeled using continuous assignments.
The Verilog module in Figure 2-7 is defined in Figure 8-7 using built-in primi-
tives. The and and or are built-in primitives with one output and multiple inputs.
The output terminal must be listed first followed by inputs as in
and (out, in_1, in_2, ..., in_n);
Figure 8-7: Verilog Gate Module Using Built-In Primitives
module two_gates (A, B, D, E); // Figure 2-7 shows the same module using
output E; // concurrent statements instead of
input A, B, D; // built-in primitives
wire C;
endmodule
Verilog also provides built-in primitives for tristate gates. The bufif0 is a non-
inverting buffer primitive with active-low control input while bufif1 has active-high
control input. The notif0 and notif1 are inverting buffers with active-low and active-
high controls, respectively. These primitives can support multiple outputs. The
outputs must be listed first followed by input and finally the tristate control input.
An array of four inverting tristate buffers can be created as shown in Figure 8-8.
endmodule
The statement
bufif0
buf_array[3:0] (out, in, tri_en);
// instance name is
// indexed here
8.4 Built-in Primitives 441
in Figure 8-8 uses buf_array[3:0] as instance name. Notice that the instance name
is indexed here. This statement could be replaced using
bufif0
buf_array3 (out[3], in[3], tri_en);
// instance name not
// indexed
bufif0 buf_array2 (out[2], in[2], tri_en);
bufif0 buf_array1 (out[1], in[1], tri_en);
bufif0 buf_array0 (out[0], in[0], tri_en);
where the instance names are not indexed. The instance names are not signal names.
The circuit in Figure 8-5, presented using the behavioral model in Figure 8-6, can
be written using Verilog built-in primitives as in Figure 8-9.
endmodule
Table 8.1 lists the built-in primitives in Verilog. The first 12 are gate-level primi-
tives, and the rest are switch-level primitives. Switch-level models are not discus-
seded in this book, but they are useful if low-level models of designs are to be built.
The built-in primitives can have an optional delay specification. A delay spec-
ification can contain up to three delay values, depending on the gate type. For a
three-delay specification,
(i) the first delay refers to the transition to the rise delay (i.e., transition to 1),
(ii) the second delay refers to the transition to the fall delay (i.e., transition
to 0), and
(iii) the third delay refers to the transition to the high-impedance value (i.e.,
turn-off).
If only one delay is specified, it is rise delay. If there are only two delays specified,
they are rise delay and fall delay. If turn-off delay must be specified, all three delays
must be specified. The pullup and pulldown instance declarations must not include
442 Chapter 8 Additional Topics in Verilog
delay specifications. The following are examples of built-in primitives with one,
two, and three delays:
and #
(10) a1 (out, in1, in2); // only one delay
// (so rise delay510)
and #
(10,12) a2 (out, in1, in2); // rise delay510 and
// fall delay512
bufif0 #
(10,12,11) b3 (out, in, ctrl); // rise, fall, and
// turn-off delays
table
// A I0 I1 F
0 1 0 : 1 ;
8.5 User-Defined Primitives 443
0 1 1 : 1 ;
0 1 x : 1 ;
0 0 0 : 0 ;
0 0 1 : 0 ;
0 0 x : 0 ;
1 0 1 : 1 ;
1 1 1 : 1 ;
1 x 1 : 1 ;
1 0 0 : 0 ;
1 1 0 : 0 ;
1 x 0 : 0 ;
x 0 0 : 0 ;
x 1 1 : 1 ;
endtable
endprimitive
The first entry in the truth table in Figure 8-10 can be explained as follows:
when A equals 0, I0 equals 1, and I1 equals 0, then output F equals 1. The input
combination 0xx (A=0, I0=x, I1=x) is not specified. If this combination occurs
during simulation, the value of output port F will become x. Each row of the table
in the UDP is terminated by a semicolon.
The multiplexer model can also be specified more concisely in a UDP by using ?.
The ? means that the signal listed with ? can take the values of 0,1, or x. Using ?
the multiplexer can be defined as in Figure 8-11.
table
// A I0 I1 F
0 1 ? : 1 ; // ? can equal 0, 1, or x
0 0 ? : 0 ;
1 ? 1 : 1 ;
1 ? 0 : 0 ;
x 0 0 : 0 ;
x 1 1 : 1 ;
endtable
endprimitive
UDPs are instantiated just as are built-in primitives. For instance, the preceding
multiplexer can be instantiated by using the statement
mux1 (outF, Sel, in1, in2)
where outF, Sel, in1, and in2 are the names of the output, select, data input1,
and data input2 signals.
444 Chapter 8 Additional Topics in Verilog
reg Q;
table
//
CLK,
D, Q, Q+
(01) 0 : ? : 0 ; //rising edge with input 0
(01) 1 : ? : 1 ; //rising edge with input 1
(0?) 1 : 1 : 1 ; //Present state 1, either rising edge or steady clock
(?0) ? : ? : - ; //Falling edge or steady clock, no change in output
? (??) : ? : - ; //Steady clock, ignore inputs, no change in output
endtable
endprimitive
The (01) in the first line of the table indicates a rising edge clock. The ‘-’ in the out-
put column means that the output should not change for any of the circumstances
covered by that line. For instance, the line
(?0) ? ? : - ;
means that if there is a falling edge or steady clock, whether the input and present
state are 0,1, or x, the output must not change. This line actually represents 27 pos-
sibilities, because each ? represents three possibilities. If this line was omitted in
the code, the simulator would yield an x output in these situations. It is important
to make the truth table as unambiguous as possible by specifying all possible cases.
The last line of code clarifies further that under steady clock (i.e., no clock edges),
the flip-flop must ignore all inputs.
? (??) : ? : - ;
The rows in the primitive truth table do not need to be in order. Hence the row order
0 1 0 : 1 ;
0 0 0 : 0 ;
is acceptable. A simulator scans the truth table from top to bottom. If level-sensitive
behavior such as asynchronous set and reset are in a table along with edge-sensitive
behavior for data, the level-sensitive behavior should be listed before the edge-
sensitive behavior.
8.6 SRAM Model 445
OE static
Data
RAM input/output
WE
The truth table for the RAM (Table 8-2) describes its basic operation. High-Z
in the I/O column means that the output buffers have high-Z outputs and the data
inputs are not used. In the read mode, the address lines are decoded to select m
memory cells, and the data comes out on the I/O lines after the memory access
time has elapsed. In the write mode, input data is routed to the latch inputs in the
selected memory cells when WE is low, but writing to the latches in the memory
cells is not completed until either WE goes high or the chip is deselected. The truth
table does not take memory timing into account.
TABLE 8-2: Truth Table CS OE WE Mode I/O pins
for Static RAM H X X not selected high-Z
L H H output disabled high-Z
L L H read data out
L X L write data in
446 Chapter 8 Additional Topics in Verilog
We now write a simple Verilog model for the memory that does not take timing
considerations into account. In Figure 8-14, the RAM memory array is represented
by an array of registers (RAM1). This memory has 256 words, each of which are
8 bits. The RAM process sets the I/O lines to high-Z if the chip is not selected. If
We_b 5 1, the RAM is in the read mode and IO is the data read from the memory
array. If We_b 5 0, the memory is in the write mode, and the data on the I/O lines
is stored in RAM1 on the rising edge of We_b. This is a RAM with asynchronous
read and synchronous write.
input Cs_b;
input We_b;
input Oe_b;
input[7:0] Address;
inout[7:0] IO;
reg[7:0] RAM1[0:255];
OE WE
ld_data en_data
inc_addr
S1
S3
inc_data
N
addr = 32
Y
done
448 Chapter 8 Additional Topics in Verilog
Figure 8-17 shows the Verilog code for the RAM system. The first always state-
ment represents the SM chart, and the second always statement is used to update
the registers on the rising edge of the clock. A short delay is added when the address
is incremented to make sure the write to memory is completed before the address
changes. A concurrent statement is used to simulate the tristate buffer, which
enables the Data Register output to go onto the I/O lines.
reg[1:0] state;
reg[1:0] next_state;
reg inc_adrs;
reg inc_data;
reg ld_data;
reg en_data;
reg Cs_b;
reg clk;
reg Oe_b;
reg done;
reg We_b;
reg[7:0] Data;
reg[7:0] Address;
wire[7:0] IO;
initial
begin
inc_adrs = 1'b0;
inc_data = 1'b0;
ld_data = 1'b0;
en_data = 1'b0;
clk = 1'b0;
Cs_b = 1'b0;
Oe_b = 1'b0;
done = 1'b0;
We_b = 1'b1; //initialize to read mode
Address = 8'b00000000; //address register
end
case (state)
0 :
begin
Oe_b = 1'b0 ;
ld_data = 1'b1 ;
next_state = 1 ;
end
1 :
begin
inc_data = 1'b1 ;
next_state = 2 ;
end
2 :
begin
We_b = 1'b0 ;
en_data = 1'b1 ;
inc_adrs = 1'b1 ;
next_state = 3 ;
end
3 :
begin
if (Address == 8'b00100000)
begin
done = 1'b1 ;
next_state = 3 ;
end
else
begin
next_state = 0 ;
end
end
endcase
end
//Concurrent statements
always #100 clk = ~clk ;
assign IO = (en_data ==1'b1) ? Data : 8'bZZZZZZZZ ;
endmodule
This system can be modified to include all memory locations for testing the
correctness of the entire SRAM. Memory systems are often tested by writing
checkerboard patterns (alternate 0s and 1s) in all locations. For instance, one can
write 01010101 (55 hexadecimal) into all odd addresses and 10101010 (hexadecimal
AA) into all even addresses. Then the odd and even locations can be swapped.
Developing Verilog code for such a system is left as an exercise problem.
where 3 ns is the added delay for each load. Otherwise, a falling output has just
occurred, and the gate delay is computed as
Tfall + 2 ns * load
parameter Trise = 3;
parameter Tfall = 2;
parameter load = 1;
8.9 Named Association 451
input a;
input b;
output c;
reg c;
wire nand_value;
always @ (nand_value)
begin
if (nand_value == 1'b1)
#(Trise + 3 * load) c = 1'b1;
else
#(Tfall + 3 * load) c = 1'b0;
end
endmodule
module NAND2_test (in1, in2, in3, in4, out1, out2);
input in1;
input in2;
input in3;
input in4;
output out1;
output out2;
NAND2 #(2, 1, 2) U1 (in1, in2, out1);
NAND2 U2 (in3, in4, out2);
endmodule
The module NAND2_test tests the NAND2 component. The parameter decla-
ration in the module specifies default values for Trise, Tfall, and load. When U1 is
instantiated, the parameter map specifies different values for Trise, Tfall, and load.
When U2 is instantiated, no parameter map is included, so the default values are
used. Another way to instantiate U1 is by using defparam as follows:
defparam U1.Trise = 2;
defparam U1.Tfall = 1;
defparam U1.load = 2;
NAND2 U1 (in1,in2, out1);
NAND2 U2 (in3, in4, out2);
output Cout;
output Sum;
input X;
input Y;
input Cin;
………
endmodule
The statement
FullAdder FA0 (Co[0], S[0], A[0], B[0], Ci[0]);
creates a full adder and connects A[0] to the X input of the adder, B[0] to the Y
input, Ci[0] to the Cin input, Co[0] to the Cout output, and S[0] to the Sum output
of the adder. The first signal in the port map is associated with the first signal in the
module declaration, the second signal with the second signal, and so on.
As an alternative, we can use named association, in which each signal in the port
map is explicitly associated with a signal in the port of the module declaration. For
example, the statement
FullAdder FA0 (.Sum(S[0]), .Cout(Co[0]), .X(A[0]), .Y(B[0]),
.Cin(Ci[0]));
makes the same connections as the previous instantiation statement—that is, Sum
connects to S[0], Cout connects to Co[0], X connects to A[0], and so on. When
named association is used, the order in which the connections are listed is not
important, and any port signals not listed are left unconnected. Use of named asso-
ciation makes code easier to read, and it offers more flexibility in the order in which
signals are listed.
When named association is used with a parameter map, any unassociated
parameter assumes its default value. For example, if we replace the statement in
Figure 8-18 labeled U1 with the following:
NAND2 #(.load(3), .Trise(4)) U1 (in1, in2, out1);
wire[4:0] C;
assign C[0] = Ci ;
genvar i;
generate
for (i=0; i<4; i=i+1)
begin: gen_loop
FullAdder FA (A[i], B[i], C[i], C[i11], S[i]);
end
endgenerate
assign Co = C[4] ;
endmodule
as C(0) and Cout the same as C(4). The for loop generates four copies of the full
adder, each with the appropriate port map to specify the interconnections between
the adders.
Another example where the generate statement would have been very useful
is the array multiplier. The Verilog code for the array multiplier (Chapter 4) made
repeated use of port map statements in order to instantiate each module instance.
They could have been replaced with generate statements.
In the preceding example, we used a generate statement of the form
genvar gen_variable1,…;
generate
for ( for_loop_condition with gen_variables )
concurrent statement(s)
endgenerate
454 Chapter 8 Additional Topics in Verilog
inside the generate clause creates the effect of the following four statements:
FullAdder FA (A[0], B[0], C[0], C[1], S[0]);
FullAdder FA (A[1], B[1], C[1], C[2], S[1]);
FullAdder FA (A[2], B[2], C[2], C[3], S[2]);
FullAdder FA (A[3], B[3], C[3], C[4], S[3]);
Conditional Generate
A generate statement with an if clause may be used to conditionally generate a set
of concurrent statement(s). This type of generate statement has the form
generate
if condition
concurrent statement(s)
endgenerate
In this case, the concurrent statements(s) are generated at compile time only if the con-
dition is true. Conditional compilation can also be accomplished using the `ifdef com-
piler directive, which is explained in Section 8-12. Conditional compilation is useful for
(i) Selectively including behavioral, structural, or switch-level models as
desired.
(ii) Selectively including different timing or structural information.
(iii) Selectively including different stimuli for different runs under different
scenarios.
(iv) Selectively adapting the module functionality to similar but different needs
from different customers.
Figure 8-20 illustrates the use of conditional compilation using a generate state-
ment with an if clause. An N-bit left-shift register is created if Lshift is true, using
the statement
generate
if(Lshift)
assign shifter = {Q[N - 1:1], Shiftin} ;
else
assign shifter = {Shiftin, Q[N:2]} ;
endgenerate
output[N:1] Qout;
input[N:1] D;
input CLK;
input Ld;
input Sh;
input Shiftin;
reg[N:1] Q;
wire[N:1] shifter;
assign Qout = Q ;
generate
if(Lshift)
assign shifter = {Q[N - 1:1], Shiftin} ; //left shift register
else
assign shifter = {Shiftin, Q[N:2]} ; //right shift register
endgenerate
Systems tasks mainly include display tasks for outputting text or data during
simulation, file I/O tasks, and simulation control tasks such as $finish and $stop. We
describe a few of the system tasks/functions in this section.
$time Returns an integer that is a 64-bit time, scaled to the timescale unit of
the module that invoked it.
$stime Returns an unsigned integer that is a 32-bit time, scaled to the time-
scale unit of the module that invoked it. If the actual simulation time
does not fit in 32 bits, the low-order 32 bits of the current simulation
time are returned.
$realtime Returns a real number time that, like $time, is scaled to the time unit
of the module that invoked it.
real sig;
wire [64:1] net_bus = $realtobits(sig);
endmodule
The variable sig, which is in real type is converted to bits using the statement
wire [64:1] net_bus = $realtobits(sig);
(ASCII 0x27). The scope of a compiler directive extends from the point where it is
processed, across all files processed, to the point where another compiler directive
supersedes it or the processing completes. There is no semicolon (;) at the end of
the line with the compiler directive. This section describes a few compiler directives.
8.12.1 `define
The directive `define creates a macro for text substitution. This directive can be
used both inside and outside module definitions. After a text macro is defined, it can
be used in the source description by using the (‵) character, followed by the macro
name. This directive can be used to define constants or expressions. For example,
`define wordsize 16
causes the string wordsize to be replaced by 16. Such replacements can increase
the readability and modifiability of the code.
In the following example, a macro max is defined to represent an expression. In
order to invoke the macro, one must write `max. For example, the statement
`define max(a,b)((a) > (b) ? (a) : (b))
defines the macro. The larger of the two operands is returned by this macro. The
statement
n = `max(p+q, r+s) ;
Here, the larger of the two expressions out of (p + q) and (r + s) will be evaluated
twice.
8.12.2 `include
This compiler directive is used to insert the contents of one source file into another
file during compilation (i.e., for file inclusion). The result is as though the contents of
the included source file appear in place of the `include compiler directive. The `include
compiler directive can be used to include global or commonly used definitions and
tasks without encapsulating repeated code within module boundaries. It helps modu-
lar code development and facilitates structured organization of the files in the design.
It seriously contributes to the convenience in managing source code of a design.
The compiler directive is followed by the filename to be included. Only white
space or a comment may appear on the same line as the `include compiler direc-
tive. A file included in the source using the `include compiler directive may contain
other `include compiler directives. The number of nesting levels allowed might vary
in different Verilog compilers. The following are valid examples of the `include
compiler directive:
`include "lab3/sevenseg.v"
`include "myfile"
`include "myfile"
// including myfile. A comment or only white
// space allowed
8.12 Compiler Directives 459
If file sub.v contains
reg g;
initial
g = 0;
8.12.3 `ifdef
Verilog contains several compiler directives for conditional compilation. These
directives allow one to optionally include lines of a Verilog HDL source description
during compilation. Conditional compilation can also be accomplished with the con-
ditional generate statement as in Section 8.10; however, the `ifdef compiler directive
is convenient due to the existence of similar directives in high-level languages such
as C. Many designers are already familiar with similar directives from C.
The `ifdef compiler directive is followed by a text_macro_name in the code.
At compile time, the compiler checks for the definition of the text_macro_name.
If the text_macro_name is defined, then the lines following the `ifdef directive are
included. Otherwise, the statements in the `else clause are included. The end of
the construct is marked with the `endif. As mentioned in Section 8.10, conditional
compilation is useful for
(i) Selectively including behavioral, structural, or switch-level models as
desired.
(ii) Selectively including different timing or structural information.
(iii) Selectively including different stimulus for different runs under different
scenarios.
(iv) Selectively adapting the module functionality to similar but different needs
from different customers.
The example that follows shows a simple usage of an `ifdef directive for selecting
between a behavioral design and a structural design. If the identifier behavioral
is defined, a continuous net assignment will be compiled; otherwise, a built-in
gate primitive and will be instantiated. In and a1 (f,a,b), a1 is the instance
460 Chapter 8 Additional Topics in Verilog
name of the instantiated gate and and is the built-in gate primitive introduced in
Section 8.4.
module selective_and (f, a, b);
output f;
input a, b;
`ifdef behavioral
wire f = a & b;
`else
and a1 (f,a,b);
`endif
endmodule
eof = $feof(file);
8.13 File I/O Functions 461
$ferror returns the error status of a file. If an error has occurred while
reading a file, $ferror returns a non-zero value; otherwise, it
returns a 0.
integer file;
reg error;
error = $ferror(file);
$fgetc reads a single character from the specified file and returns it. If
the end-of-file is reached, $fgetc returns EOF.
integer file, char;
char = $fgetc(file);
$fputc writes a single character to a file. It returns EOF if there is an
error, 0 otherwise.
integer stream, r, char;
r = $fputc(stream, char);
$fscanf
parses formatted text from a file according to the format and
writes the results to args.
integer file, count, tmp_a;
count = $fscanf(file,"%d", tmp_a);
// count = $fscanf(file, format, args);
$fprintf writes a formatted string to a file.
integer file, tmp_a, tmp_b, ret;
file = $fopen("test.log", w);
ret = $fprintf(file, "%d : %x", tmp_a, tmp_b);
$fread reads binary data from the file specified by the file descriptor
into a register or into a memory.
integer rd, file;
reg rd_value;
rd = $fread(file, rd_value);
$fwrite writes binary data from a register to the file specified by the file
descriptor.
integer file;
reg tmp_a, tmp_b;
tmp_a = 0;
tmp_b = 0;
file = $fopen("test.log");
$fwrite(file,"A=%d B=%d",tmp_a,tmp_b);
$readmemb To read data from a file and store it in memory, use the
$readmemh functions $readmemb and $readmemh. The $readmemb task
reads binary data, and $readmemh reads hexadecimal data.
Data has to be present in a text file.
462 Chapter 8 Additional Topics in Verilog
Figure 8-21 gives a Verilog code that reads hexadecimal data from a file using
$readmemh. Four 32-bit data are stored in a file “data.txt”. $readmemh will read data
and store it to the storage.
Figure 8-21: Verilog Code to Read Hexadecimal Data from a File Using $readmemh
module example_readmemh;
reg [31:0] data [0:3];
integer i;
initial begin
$display("read hexa_data:");
for (i=0; i < 4; i=i+1)
$display("%d:%h",i,data[i]);
end
endmodule
Figure 8-22 shows a Verilog code that read a file with commands, addresses, and
data values. The code uses $fopen for accessing the file and $fscanf for parsing
each line. In Figure 8-22, the disable statement is used to terminate the block
named ‘file_read’. The disable statement followed by a task name or block name
will disable only tasks and named blocks. It cannot disable functions.
Figure 8-22: Verilog Code to Read and Parse a File Using $fopen
`define NULL 0
`define EOF 32'hffffffff
module file_read;
integer file, ret;
reg [31:0] r_w, addr, data;
initial
begin : file_read
ret = $fclose(file);
end
endmodule