程序代写案例-COMP12111
时间:2021-11-16
COMP12111 – Verilog Primer Ver 2021
1
COMP12111: Fundamentals of Computer Engineering
Verilog Primer The following is a brief review of the common Verilog language syntax suitable for implementing designs at the Register Transfer Level (RTL) of abstraction.
Verilog Files A Verilog file is simply a text file with a “.v” extension. You can use any editor to produce a Verilog test file. The file is made up of lexical tokens (strings of one or more characters) that form the Verilog language. White space is used to separate these tokens. The Verilog language consists of a number of keywords that identify fixed operations. These keywords must always be written in lowercase. Identifiers are used to identify objects, such as a module, reg or wire variables, in the code. An identifier can be any sequences of letters (lower- or upper-case – case-sensitive), digits, dollar sign ($) and underscore, ‘_’. However, the first character of the identifier must not be a dollar sign or a number. Comments in Verilog code can be inserted by using //, which states that the text from this point to the end of the line is a comment, or they can be encapsulated (across multiple lines) using /* at the start and */ at the end. By default, numbers in Verilog are decimal. The numerical base can be specified as follows:
‘b - prefixes a binary number
‘h – prefixes a hexidecimal number
‘d - prefixes a decimal number (may be omitted) A decimal number in front of this specifies the number of bits in the number specified. Thus, “16’h1234” specifies a 16-bit number. Decimal: 16’d87 – 16-bit decimal number 87 Hexidecimal: 64’h57 – 64-bit hexadecimal number 57 Octal: 32’o127 – 32-bit octal number 127 Binary: 8’b01010111 – 8-bit binary number 01010111 Negative numbers are expressed in 2’s complement form.
COMP12111 – Verilog Primer Ver 2021
2
Data Types Variables can take one of four values: 0 – logical ‘0’ 1 – logical ‘1’ Z – high impedance, not driven, floating X – unknown – the simulator can’t decide on the value, may be 0, 1, or Z. Net data types represent physical connections between elements in a design, these are usually of type wire or reg.
wire represents a net, i.e. connections between components. They do not hold their value and must be driven by a source such as a gate or using a continuous assignment. If a declaration does not specify a width then it is assumed to be a single bit, i.e.
wire car, boat, house, plane; // single bit wires If a width is specified, (specified after the type declaration and before the variable name) then it represents a bus, i.e.

wire [7:0] banana; // 8-bit bus

reg represents data storage and will hold the value until explicitly assigned a new value. Again, single bit and buses can be defined:

reg car, boat, house, plane; // single bit wires
reg [7:0] banana; // 8-bit bus A variable declared as reg does not necessarily result in a register being implemented in hardware – it is important to remember this.
Integers are variables that are of register type. They are often used for counting in behavioural descriptions and have a predefined width of 32 bits. Integers are treated as signed, two’s complement, values.

integer w, x, y, z; We often use integers for counting in for loops. Parameters represent constants in the code. They are very useful and enable easier changes by only having to change a single value definition.

parameter clock = 100; // defined clock period




COMP12111 – Verilog Primer Ver 2021
3
Operators Verilog understands some simple operators:
Operator Type Symbol Operation Performed Arithmetic + addition - subtraction * multiplication / division % modulus Logic ~ negation (complement) (bit-wise or reduction) & AND | OR Ù XOR Logical ! negation && AND || OR != NOT equal Shift >> shift right << shift left { , } concatenation Relational > greater than < less than (comparison that == equality return a Boolean != inequality value) >= greater than or equal <= less than or equal
Concatenation, { and }, is very useful for joining values to form a single vector, creating a collection of signals to form a bus for example.
If apple, orange and pear are defined as 8-bit buses, then the operation {apple, orange, pear} will result in a 24-bit bus being created from apple, orange and pear, with apple forming the most significant 8-bits, orange forming the central 8-bits, and pear forming the least significant 8-bits. We can specify multiple instances of a signal using concatenation: {4{car}} is equivalent to {car, car, car, car}
COMP12111 – Verilog Primer Ver 2021
4
{grape,{2{mango, banana}}} is equivalent to {grape, mango, banana, mango, banana}
Assignments Within a module (or a test stimulus) we often have to assign values to variables (wire or reg), there are three ways we can assign variables and the way values are assigned will affect how they get synthesized.
Continuous Assignment Continuous assignment is performed using the assign keyword and is associated with structural modelling and the implementation of (simple) combinatorial logic. The variable that is assigned must be declared as a wire (NOT a reg - this will give a error on compilation). Continuous assignments are always evaluated as soon as one of the operands on the right hand side of the assignment changes value. Syntax:
assign ; Consider the full adder example:

module full_adder (input wire a, b, c_in,
output wire s, c_out);

assign s = a ^ b ^ c_in;
assign c_out = (a & b) | (a & c_in) | (b & c_in);

endmodule You never see continuous assignments inside an initial or always block – this is syntactically incorrect. They may exist within the same module as initial and always blocks, but not in the blocks themselves. Condition expressions can be performed as part of a continuous assignment using the ? conditional operator. Consider:
assign result = (A == 1) ? 1’b0 : 1’b1; the syntax of the ? operator is:
condition ? value_assigned_if_true : value_assigned_if_false
COMP12111 – Verilog Primer Ver 2021
5
So, in the example shown, the condition is whether A is equal to 1 (we could just write this as (A)). If true, then result is assigned the value 0, otherwise it is assigned the value 1. We can also nest conditional ? operators, for example:
assign sig_out = signal[1] ? (signal[0] ? 1’b0 : 1’b1)
: (signal[0] ? 1’b1 : 1’b0); In which will first conditional test looks at bit 1 of the bus signal, depending on the result of this then one of two further conditional tests are performed to assign a value to
sig_out depending on the value of bit 0 of the bus signal.
Blocking Assignments - = Procedural assignments only assign values to register (and integer) types using blocking (=) and non-blocking (<=) assignments. They take place in always and initial blocks. The name blocking comes from the fact that the assignment must be completed without interruption from any other assignments, so it can be seen to “block” later assignments until it is completed. Hence, blocking assignments enforce sequential order when evaluating a block of assignments and are associated with the definition of combinatorial logic. Any variables assigned using blocking assignments must be defined as reg. Consider the following always block:
always @ (*)
begin
a = a + 1;
b = a + 2;
c = a + b;
end
Here, the assignments in the always block will be executed in the order given. If we assume that a is initially 1, then after evaluation of the always block:
a = 2 (1 + 1),
b = 4 (2 + 2) and
c = 6 (2 + 4)
Non-blocking Assignment - <= Unlike blocking assignments, non-blocking assignments are executed in parallel, so there is no guarantee of the order in which they will be evaluated. They do not block the flow of assignments. They are associated with the definition of sequential logic and found in an always block that has a sensitivity list that contains a dependency on the edge of a global clock signal.
COMP12111 – Verilog Primer Ver 2021
6
Consider the following always block:
always @ (posedge clock)
begin
a <= a + 1;
b <= a + 2;
c <= a + b;
end
Here, the assignments in the always block will be executed at the same time. If we assume that a is initially 1 and b is initially 2, then after evaluation of the always block:
a = 2 (1 + 1),
b = 3 (1 + 2) and
c = 3 (1 + 2) and you can see that the old values of a and b are used in the assignments and NOT the new evaluated values. Blocking and non-blocking assignments are found in initial and always blocks.
Never mix blocking and non-blocking assignments in the same block. Don’t make assignments to the same variable in different always blocks - all assignments to a particular variable should be contained within the same block (initial or always block) in a module. The effect of blocking and non-blocking assignments is illustrated below:
Blocking Non-blocking
a = 1; a <= 1;
b = a; b <= a;
c = b; c <= b; Results in the following after execution: Blocking: a = b = c = 1 Non-blocking: a = 1, b = old value of a, c = old value of b and will result in very different implementations when synthesized! In the case of the blocking code example, this will result in a chain of combinatorial logic. The flip-flop will exist due to the need to ‘store’ the value of ‘1’. Its existence depends upon how the statements are encapsulated in a block. In the case of the non-blocking code example, this implies retention of values so the result will be a 3-bit shift register arrangement.
COMP12111 – Verilog Primer Ver 2021
7
In general, model latches/flip-flops with non-blocking assignments, model combinatorial logic in an always block with blocking assignments.
Verilog Modules The module is the basic building block for describing a digital system in Verilog. More complex designs can be realised by connecting modules together – remember hierarchy! The module takes input signals and performs some operation on them (a = b + c in the example below) to set the status of the output signals.
The module is analogous to the symbol in the schematic. Modules can contain other modules, tasks (more later) and functions (more later). A module usually performs a distinct function, e.g. if describing a RISC processor at the architectural level there may be the following modules in the datapath:
• register bank module
• execution module (the ALU)
• data interface module
• address interface module plus a control module. A module begins with the keyword “module” followed by the module name, followed by a defined list of outputs, bidirectional signals and inputs within brackets ( ), with names separated by commas. All Verilog statements end with a semicolon, ;. Verilog names are case sensitive and should start with a letter or underscore, however, try to make names unique. Statements provide the assignments required to implement the function required by the module. A module always terminates with endmodule without a semicolon. The 1995 standard for defining the module header, is slightly different to what you will be exposed to:

COMP12111 – Verilog Primer Ver 2021
8
module full_adder_4bit(s, c_out, a, b, c_in);
output [3:0] s;
output c_out, c_in;
input wire [3:0] a, b;

reg [3:0] s;
reg c_out;

//assignments

endmodule compared to the more recent 2001 standard, where the variables are defined in the module header, i.e.
module full_adder_4bit(output reg [3:0] s,
output reg c_out,
input wire [3:0] a,
input wire [3:0] b,
input wire c_in);

//assignments

endmodule Both approaches work fine, although you should aim to use the 2001 standard in your designs.
initial & always Blocks
initial and always procedural blocks contain code that manipulate data, they do this by assigning values to variables and create structure to the module.
• a module can contain any number of initial and always blocks
• initial and always blocks operate from time 0
• initial blocks operate once at time 0
• initial blocks are used to set up the initial value of variables
• always blocks execute repeatedly from time 0 (unless there is a sensitivity list)
• initial and always blocks operate concurrently, have to be careful when assigning variable values in different blocks
• initial and always blocks run until they encounter a delay or need to wait for an event
• nets are driven by an output, so they cannot be assigned in an initial or
always block
• make sure you make code in blocks readable and use sensible names! The begin and end keywords encapsulate the statements within the initial and
always blocks in the case where there are more than one statement in the block.
COMP12111 – Verilog Primer Ver 2021
9
Concurrency It is important to appreciate that both initial and always statements are the basic constructs for describing concurrency in real systems. Remember, hardware is inherently parallel in nature, with things happening at the same time across the circuit. Contrast this to software when instructions are executed in sequence. The job of the simulator is to mimic this concurrency in order to give you a real understanding of how the circuit will operate when synthesized – more on this later. For example, in the following example for a clock in a stimulus file:
initial
clk = 1; // Clock initialisation

always
#100 clk = ~clk; // Clock switching The initial and always blocks will be executed at the SAME time, starting from time = 0. However, the delay in the always block ensures that the clock signal, clk, is changed 100 timestamps after it has been initialised to 1.
The Sensitivity List Execution of always blocks can be controlled using a sensitivity list to determine an event when execution should occur. For example, consider the following always block
always @(a, b, c_in)
begin

// statements

end Here, the always block will only be executed if there is a change in the value of a, b, or
c_in. The list of variables in the sensitivity list essentially represents an OR function. For example
always @(a, b, c_in) means when a or b or c_in change do the following … It is possible to state this explicitly in the sensitivity list, i.e.

always @(a or b or c_in) What if you forgot a variable? For example, consider the following examples:

COMP12111 – Verilog Primer Ver 2021
10
always @(a, b) always @(a)
begin begin
s = (a & ~b) | (~a & b); s = (a & ~b) | (~a & b);
c_out = a & b; c_out = a & b;
end end Code sequence 1 Code sequence 2 Both code sequences should produce a half adder. However, in code sequence 2, what will happen to the outputs s and c_out when b changes? In this case the circuit will use the old value for b resulting in a flip-flop being introduced and the circuit may not behave as expected. Instead of listing ALL the possible signals in the sensitivity list, it is better to use a “*” to identify that ALL input signals present should cause the always block to execute, i.e.
always @(*)
begin
s = (a & ~b) | (~a & b);
c_out = a & b;
end
The sensitivity list and timed events We can also use the edge of a signal, such as a clock to initiate execution of the always block, i.e.

always @(posedge clock)

always @(negedge clock)

In the case of sequential logic blocks the sensitivity list should only include clock edges, reset
and present signals. In the case of the tools available in the labs all signals should be respect
to the edge of the signals, for example

always @(posedge clock, negedge reset)
begin
if(reset == 0)
// do something
else
// do something else
end

Control Statements A number of control statements exist in Verilog to enable conditional statements to be implemented.
COMP12111 – Verilog Primer Ver 2021
11
if … else The if … else (more specifically if … then … else) statement is one of the most useful to control the flow of behavioural Verilog (along with the case statement).

module mux2_1(output reg f,
input wire a, b, sel, clk);

always @(posedge clk)
begin
if (sel) f <= b;
else f <= a;
end

endmodule
You can test more than one condition by chaining if … else statements, i.e.

module counter(output reg [3:0] count,
input wire clk, reset, enable);

always @(posedge clk)
begin
if (reset == 1)
count <= 0;
else if(enable == 1)
if( count == 7) count <= 0;
else count <= count+1;
else
count <= count;
end

endmodule

if … else - precautions

If writing a combinatorial block ensure that your code doesn't imply a register by accident.
Consider the following examples:

1. always @ (b, c, x) if (x == 0) a = b;
else a = c;
2. always @ (b, c, x) begin
a = c;
if (x == 0) a = b;
end
3. always @ (b, x) if (x == 0) a = b;

COMP12111 – Verilog Primer Ver 2021
12
The first two examples are fine because a can be determined without reference to previous
values of a. In the first example, either the if or the else must be taken. In the second
example a is given a default value and then, possibly, redefined. In both cases the effect is
the same.
However, in the third case, if x != 0 then a is not specified which means it retains its previous
value. The consequence of this is that the synthesis software will introduce a register in the
circuit in order to ‘hold’ the value of a, which was probably not intended (it certainly is then
no longer combinatorial in nature).
Some more examples:

a) always @ (posedge clk) if (x == 0) a <= b;

is fine because a is expected to be a register (the posedge clock tells us this is a sequential
block).

b) always @ (posedge clk) if (x == 0) a <= b; else a <= a;

yields the same result and is not necessary.

c) always @ (posedge clk) if (x == 0) a <= b; else a <= c; implements both the multiplexer and a register on its output. So be careful how you use if … else statements. It is safe to use dangling elses if you expect the circuit to be sequential. Otherwise, for combinatorial circuits, always make sure the value is always defined (or just make sure you have an else statement!).
Another Example Let’s look at another example using if … else statements, a comparator. The module compares two 4-bit inputs and returns 1 if they are the same, 0 otherwise.
module comparator(input wire [3:0] a,
input wire [3:0] b,
output wire c);

always @ (*)
if(a == b)
c = 1’b1;
else
c = 1’b0;

endmodule
The always block will execute whenever one of the input variables, a and b, changes – due to the * in the sensitivity list. The if statement is comparing the values of a and b (using the ==), if they are the same then the output c is assigned the value 1, otherwise it is assigned the value 0.
COMP12111 – Verilog Primer Ver 2021
13
This block will synthesize into a block of purely combinatorial logic. Alternatively, we could implement the comparator using a simple assignment:
module comparator(input wire [3:0] a,
input wire [3:0] b,
output wire c);

assign c = (a == b);

endmodule

case statements The case statement is useful when selecting from many choices, such as the case of the multiplexer. A 4:1 MUX with control inputs a and b can be used to select from 4 16-bit values to give one 16-bit output. The Verilog code describing this operation (without definitions) may be
case ({b,a})
2’b00: c = d0;
2’b01: c = d1;
2’b10: c = d2;
2’b11: c = d3;
endcase However, what if a or b take value X (undefined) or Z (high impedance), what will be the operation then? We can use the default case to capture all other cases that we are not necessarily interested in, i.e.
case ({b,a})
2’b00: c = d0;
2’b01: c = d1;
2’b10: c = d2;
2’b11: c = d3;
default: c = 16’hxxxx;
endcase You can also use the default to capture all assignments that are assigning to the same value, for example:
case (data[1:0])
2’b00: result = 2`b00;
2’b01: result = 2`b01;
default: result = 2`b11;
endcase so for all cases apart from 00 and 01 the variable will be assigned 11.
COMP12111 – Verilog Primer Ver 2021
14
If more than one case have similar actions, then you can specify more than one case in the
case specification. If no action is required for a case, then it can just be omitted. For example:
case (INSTR[15:13])
0,1: // actions
2,3: // actions
4,5: // actions;
endcase Here, we do not have any actions for INSTR = 6, 7, or 8, so they have been omitted from the case list – you may want to insert a default though!
casex The case statement compares 0, 1, X, and Z with the specified condition bit by bit, so X is not treated as a don’t care condition. For example
case ({b, a})
2’b0X: // only does this for b = ‘0’ and a = ‘X’
2’bX1: // only does this for b = ‘X’ and a = ‘1’
endcase
If you want to include x in the case condition where you want to use x as a don’t care, then use casex. For example:
casex ({b, a})
2’b0X: // only does this for b = ‘0’; don’t care about a
2’bX1: // only does this for a = ‘1’; don’t care about b
endcase
Looping Statements
The Verilog language also provides looping statements that enable statements to be
executed a number of times, these are repeat, while and for.

repeat ()

while ()

for ()

These are only used within procedural blocks (initial and always) and because they
cannot necessarily be synthesized are limited to use in testing within testbenches.


COMP12111 – Verilog Primer Ver 2021
15
Structural Verilog When talking about Verilog designs, we have been discussing behavioural descriptions, i.e. the Verilog code effectively mimics the behaviour of the circuit we are implementing using always blocks and control statements such as if … else, case etc. However, Verilog can also be used to describe the structure of a circuit as a hierarchical interconnection of instantiated modules. Consider the higher-level module shown to the left in Figure 10. Internally, this design consists of two AND gates. We could implement this design schematically, as shown in the figure below, or we could represent this design structurally in Verilog.
If a module for the the 2-input AND gate is defined as:
module AND2 (input wire A,
input wire B,
output wire C); // C = A & B
assign C = A.B;
endmodule
Then we can create a module for the higher-level design, top, as:

module top (input wire W,
input wire X,
input wire Y,
output wire z);

// define internal connections
wire int;

AND2 I1(W, X, int); // first AND gate
AND2 I2(int, Y, Z); // second AND gate

endmodule
Here, we have instantiated the module AND as follows:
();
COMP12111 – Verilog Primer Ver 2021
16
It should be noted that:
• internal wires are defined as type wire.
• the name of each instantiated module should be unique.
• list the connections to the inputs/outputs of the instantiated module must be done in the same order as the module header.
Consider, the design of a 4:1 multiplexer using structural Verilog, as shown schematically below. Here, we have three Verilog designs available to us, a 3-input AND gate, a 4 input OR gate and an inverter, with headers:
module AND3(input wire A,
input wire B,
input wire C,
output wire D);

module OR4(input wire P,
input wire Q,
input wire R,
input wire S,
output wire T);

module inverter(input wire M,
output wire N);
Using a hierarchical approach we can construct the 4:1 MUX design form these modules:
COMP12111 – Verilog Primer Ver 2021
17
module mux4to1(input wire [3:0] D,
input wire [1:0] select,
output wire Q);

wire ns0, ns1, a0, a1, a2, a3; // local variables

not nots0(s[0], ns0);
not nots1(s[1], ns1);

and3 and_1(ns0, ns1, D[0], a0);
and3 and_2(s[0], ns1, D[1], a1);
and3 and_3(ns0, s[1], D[2], a2);
and3 and_4(s[0], s[1], D[3], a3);

or4 or_1(a0, a1, a2, a3, Q);

endmodule Where we have defined internal wires to represent the connections between components, so:
• ns0 – the output from the s[0] inverter
• ns1 – the output from the s[1] inverter
• a0 – the output from the top AND gate
• a1 – the output from the 2nd AND gate
• a2 – the output from the 3rd AND gate
• a3 – the output from the bottom AND gate

COMP12111 – Verilog Primer Ver 2021
18
Implementing modules at different levels of abstraction It is worth revisiting abstraction in Verilog to appreciate that there are many ways that that a design can be defined in Verilog, and at different levels of abstraction. Let’s look at the example of a 4:1 multiplexer.
Behavioural description:
module mux4to1(input wire [3:0] D,
input wire [1:0] select,
output wire reg Q);

always@(*) // whenever data or select change
Q = D[select];

endmodule

Behavioural with case statement description:
module mux4to1(input wire [3:0] D,
input wire [1:0] select,
output wire reg Q);

always@(*) // whenever data or select change
case(select)
2’b00: Q = D[0];
2’b01: Q = D[1];
2’b10: Q = D[2];
2’b11: Q = D[3];
default: Q = 4’hx;
endmodule

Structural description See the above discussion on structural Verilog.

Compiler directives Verilog provides a number of compiler directives that are instructions that affect the compilations of the code. One of the most useful is `define (note the direction of the ‘tick’) which can be used for text substitution, for example if you define: `
define FETCH 0 in your code (outside the module) then use `FETCH within your code, then during compilation `FETCH will be replaced with the value 0. This allows constants to be easily changed and also help make code more readable.
COMP12111 – Verilog Primer Ver 2021
19
Another useful compiler directive is `include, which can be used to include code from other files in your design without having to explicitly copy the code in. For example: `
include definitions.v will copy the contents of definitions.v into your file at compilation.








































= ; Consider the full adder example:

module full_adder (input wire a, b, c_in,
output wire s, c_out);

assign s = a ^ b ^ c_in;
assign c_out = (a & b) | (a & c_in) | (b & c_in);

endmodule You never see continuous assignments inside an initial or always block – this is syntactically incorrect. They may exist within the same module as initial and always blocks, but not in the blocks themselves. Condition expressions can be performed as part of a continuous assignment using the ? conditional operator. Consider:
assign result = (A == 1) ? 1’b0 : 1’b1; the syntax of the ? operator is:
condition ? value_assigned_if_true : value_assigned_if_false
COMP12111 – Verilog Primer Ver 2021
5
So, in the example shown, the condition is whether A is equal to 1 (we could just write this as (A)). If true, then result is assigned the value 0, otherwise it is assigned the value 1. We can also nest conditional ? operators, for example:
assign sig_out = signal[1] ? (signal[0] ? 1’b0 : 1’b1)
: (signal[0] ? 1’b1 : 1’b0); In which will first conditional test looks at bit 1 of the bus signal, depending on the result of this then one of two further conditional tests are performed to assign a value to
sig_out depending on the value of bit 0 of the bus signal.
Blocking Assignments - = Procedural assignments only assign values to register (and integer) types using blocking (=) and non-blocking (<=) assignments. They take place in always and initial blocks. The name blocking comes from the fact that the assignment must be completed without interruption from any other assignments, so it can be seen to “block” later assignments until it is completed. Hence, blocking assignments enforce sequential order when evaluating a block of assignments and are associated with the definition of combinatorial logic. Any variables assigned using blocking assignments must be defined as reg. Consider the following always block:
always @ (*)
begin
a = a + 1;
b = a + 2;
c = a + b;
end
Here, the assignments in the always block will be executed in the order given. If we assume that a is initially 1, then after evaluation of the always block:
a = 2 (1 + 1),
b = 4 (2 + 2) and
c = 6 (2 + 4)
Non-blocking Assignment - <= Unlike blocking assignments, non-blocking assignments are executed in parallel, so there is no guarantee of the order in which they will be evaluated. They do not block the flow of assignments. They are associated with the definition of sequential logic and found in an always block that has a sensitivity list that contains a dependency on the edge of a global clock signal.
COMP12111 – Verilog Primer Ver 2021
6
Consider the following always block:
always @ (posedge clock)
begin
a <= a + 1;
b <= a + 2;
c <= a + b;
end
Here, the assignments in the always block will be executed at the same time. If we assume that a is initially 1 and b is initially 2, then after evaluation of the always block:
a = 2 (1 + 1),
b = 3 (1 + 2) and
c = 3 (1 + 2) and you can see that the old values of a and b are used in the assignments and NOT the new evaluated values. Blocking and non-blocking assignments are found in initial and always blocks.
Never mix blocking and non-blocking assignments in the same block. Don’t make assignments to the same variable in different always blocks - all assignments to a particular variable should be contained within the same block (initial or always block) in a module. The effect of blocking and non-blocking assignments is illustrated below:
Blocking Non-blocking
a = 1; a <= 1;
b = a; b <= a;
c = b; c <= b; Results in the following after execution: Blocking: a = b = c = 1 Non-blocking: a = 1, b = old value of a, c = old value of b and will result in very different implementations when synthesized! In the case of the blocking code example, this will result in a chain of combinatorial logic. The flip-flop will exist due to the need to ‘store’ the value of ‘1’. Its existence depends upon how the statements are encapsulated in a block. In the case of the non-blocking code example, this implies retention of values so the result will be a 3-bit shift register arrangement.
COMP12111 – Verilog Primer Ver 2021
7
In general, model latches/flip-flops with non-blocking assignments, model combinatorial logic in an always block with blocking assignments.
Verilog Modules The module is the basic building block for describing a digital system in Verilog. More complex designs can be realised by connecting modules together – remember hierarchy! The module takes input signals and performs some operation on them (a = b + c in the example below) to set the status of the output signals.
The module is analogous to the symbol in the schematic. Modules can contain other modules, tasks (more later) and functions (more later). A module usually performs a distinct function, e.g. if describing a RISC processor at the architectural level there may be the following modules in the datapath:
• register bank module
• execution module (the ALU)
• data interface module
• address interface module plus a control module. A module begins with the keyword “module” followed by the module name, followed by a defined list of outputs, bidirectional signals and inputs within brackets ( ), with names separated by commas. All Verilog statements end with a semicolon, ;. Verilog names are case sensitive and should start with a letter or underscore, however, try to make names unique. Statements provide the assignments required to implement the function required by the module. A module always terminates with endmodule without a semicolon. The 1995 standard for defining the module header, is slightly different to what you will be exposed to:

COMP12111 – Verilog Primer Ver 2021
8
module full_adder_4bit(s, c_out, a, b, c_in);
output [3:0] s;
output c_out, c_in;
input wire [3:0] a, b;

reg [3:0] s;
reg c_out;

//assignments

endmodule compared to the more recent 2001 standard, where the variables are defined in the module header, i.e.
module full_adder_4bit(output reg [3:0] s,
output reg c_out,
input wire [3:0] a,
input wire [3:0] b,
input wire c_in);

//assignments

endmodule Both approaches work fine, although you should aim to use the 2001 standard in your designs.
initial & always Blocks
initial and always procedural blocks contain code that manipulate data, they do this by assigning values to variables and create structure to the module.
• a module can contain any number of initial and always blocks
• initial and always blocks operate from time 0
• initial blocks operate once at time 0
• initial blocks are used to set up the initial value of variables
• always blocks execute repeatedly from time 0 (unless there is a sensitivity list)
• initial and always blocks operate concurrently, have to be careful when assigning variable values in different blocks
• initial and always blocks run until they encounter a delay or need to wait for an event
• nets are driven by an output, so they cannot be assigned in an initial or
always block
• make sure you make code in blocks readable and use sensible names! The begin and end keywords encapsulate the statements within the initial and
always blocks in the case where there are more than one statement in the block.
COMP12111 – Verilog Primer Ver 2021
9
Concurrency It is important to appreciate that both initial and always statements are the basic constructs for describing concurrency in real systems. Remember, hardware is inherently parallel in nature, with things happening at the same time across the circuit. Contrast this to software when instructions are executed in sequence. The job of the simulator is to mimic this concurrency in order to give you a real understanding of how the circuit will operate when synthesized – more on this later. For example, in the following example for a clock in a stimulus file:
initial
clk = 1; // Clock initialisation

always
#100 clk = ~clk; // Clock switching The initial and always blocks will be executed at the SAME time, starting from time = 0. However, the delay in the always block ensures that the clock signal, clk, is changed 100 timestamps after it has been initialised to 1.
The Sensitivity List Execution of always blocks can be controlled using a sensitivity list to determine an event when execution should occur. For example, consider the following always block
always @(a, b, c_in)
begin

// statements

end Here, the always block will only be executed if there is a change in the value of a, b, or
c_in. The list of variables in the sensitivity list essentially represents an OR function. For example
always @(a, b, c_in) means when a or b or c_in change do the following … It is possible to state this explicitly in the sensitivity list, i.e.

always @(a or b or c_in) What if you forgot a variable? For example, consider the following examples:

COMP12111 – Verilog Primer Ver 2021
10
always @(a, b) always @(a)
begin begin
s = (a & ~b) | (~a & b); s = (a & ~b) | (~a & b);
c_out = a & b; c_out = a & b;
end end Code sequence 1 Code sequence 2 Both code sequences should produce a half adder. However, in code sequence 2, what will happen to the outputs s and c_out when b changes? In this case the circuit will use the old value for b resulting in a flip-flop being introduced and the circuit may not behave as expected. Instead of listing ALL the possible signals in the sensitivity list, it is better to use a “*” to identify that ALL input signals present should cause the always block to execute, i.e.
always @(*)
begin
s = (a & ~b) | (~a & b);
c_out = a & b;
end
The sensitivity list and timed events We can also use the edge of a signal, such as a clock to initiate execution of the always block, i.e.

always @(posedge clock)

always @(negedge clock)

In the case of sequential logic blocks the sensitivity list should only include clock edges, reset
and present signals. In the case of the tools available in the labs all signals should be respect
to the edge of the signals, for example

always @(posedge clock, negedge reset)
begin
if(reset == 0)
// do something
else
// do something else
end

Control Statements A number of control statements exist in Verilog to enable conditional statements to be implemented.
COMP12111 – Verilog Primer Ver 2021
11
if … else The if … else (more specifically if … then … else) statement is one of the most useful to control the flow of behavioural Verilog (along with the case statement).

module mux2_1(output reg f,
input wire a, b, sel, clk);

always @(posedge clk)
begin
if (sel) f <= b;
else f <= a;
end

endmodule
You can test more than one condition by chaining if … else statements, i.e.

module counter(output reg [3:0] count,
input wire clk, reset, enable);

always @(posedge clk)
begin
if (reset == 1)
count <= 0;
else if(enable == 1)
if( count == 7) count <= 0;
else count <= count+1;
else
count <= count;
end

endmodule

if … else - precautions

If writing a combinatorial block ensure that your code doesn't imply a register by accident.
Consider the following examples:

1. always @ (b, c, x) if (x == 0) a = b;
else a = c;
2. always @ (b, c, x) begin
a = c;
if (x == 0) a = b;
end
3. always @ (b, x) if (x == 0) a = b;

COMP12111 – Verilog Primer Ver 2021
12
The first two examples are fine because a can be determined without reference to previous
values of a. In the first example, either the if or the else must be taken. In the second
example a is given a default value and then, possibly, redefined. In both cases the effect is
the same.
However, in the third case, if x != 0 then a is not specified which means it retains its previous
value. The consequence of this is that the synthesis software will introduce a register in the
circuit in order to ‘hold’ the value of a, which was probably not intended (it certainly is then
no longer combinatorial in nature).
Some more examples:

a) always @ (posedge clk) if (x == 0) a <= b;

is fine because a is expected to be a register (the posedge clock tells us this is a sequential
block).

b) always @ (posedge clk) if (x == 0) a <= b; else a <= a;

yields the same result and is not necessary.

c) always @ (posedge clk) if (x == 0) a <= b; else a <= c; implements both the multiplexer and a register on its output. So be careful how you use if … else statements. It is safe to use dangling elses if you expect the circuit to be sequential. Otherwise, for combinatorial circuits, always make sure the value is always defined (or just make sure you have an else statement!).
Another Example Let’s look at another example using if … else statements, a comparator. The module compares two 4-bit inputs and returns 1 if they are the same, 0 otherwise.
module comparator(input wire [3:0] a,
input wire [3:0] b,
output wire c);

always @ (*)
if(a == b)
c = 1’b1;
else
c = 1’b0;

endmodule
The always block will execute whenever one of the input variables, a and b, changes – due to the * in the sensitivity list. The if statement is comparing the values of a and b (using the ==), if they are the same then the output c is assigned the value 1, otherwise it is assigned the value 0.
COMP12111 – Verilog Primer Ver 2021
13
This block will synthesize into a block of purely combinatorial logic. Alternatively, we could implement the comparator using a simple assignment:
module comparator(input wire [3:0] a,
input wire [3:0] b,
output wire c);

assign c = (a == b);

endmodule

case statements The case statement is useful when selecting from many choices, such as the case of the multiplexer. A 4:1 MUX with control inputs a and b can be used to select from 4 16-bit values to give one 16-bit output. The Verilog code describing this operation (without definitions) may be
case ({b,a})
2’b00: c = d0;
2’b01: c = d1;
2’b10: c = d2;
2’b11: c = d3;
endcase However, what if a or b take value X (undefined) or Z (high impedance), what will be the operation then? We can use the default case to capture all other cases that we are not necessarily interested in, i.e.
case ({b,a})
2’b00: c = d0;
2’b01: c = d1;
2’b10: c = d2;
2’b11: c = d3;
default: c = 16’hxxxx;
endcase You can also use the default to capture all assignments that are assigning to the same value, for example:
case (data[1:0])
2’b00: result = 2`b00;
2’b01: result = 2`b01;
default: result = 2`b11;
endcase so for all cases apart from 00 and 01 the variable will be assigned 11.
COMP12111 – Verilog Primer Ver 2021
14
If more than one case have similar actions, then you can specify more than one case in the
case specification. If no action is required for a case, then it can just be omitted. For example:
case (INSTR[15:13])
0,1: // actions
2,3: // actions
4,5: // actions;
endcase Here, we do not have any actions for INSTR = 6, 7, or 8, so they have been omitted from the case list – you may want to insert a default though!
casex The case statement compares 0, 1, X, and Z with the specified condition bit by bit, so X is not treated as a don’t care condition. For example
case ({b, a})
2’b0X: // only does this for b = ‘0’ and a = ‘X’
2’bX1: // only does this for b = ‘X’ and a = ‘1’
endcase
If you want to include x in the case condition where you want to use x as a don’t care, then use casex. For example:
casex ({b, a})
2’b0X: // only does this for b = ‘0’; don’t care about a
2’bX1: // only does this for a = ‘1’; don’t care about b
endcase
Looping Statements
The Verilog language also provides looping statements that enable statements to be
executed a number of times, these are repeat, while and for.

repeat ()

while ()

for (; ; )

These are only used within procedural blocks (initial and always) and because they
cannot necessarily be synthesized are limited to use in testing within testbenches.


COMP12111 – Verilog Primer Ver 2021
15
Structural Verilog When talking about Verilog designs, we have been discussing behavioural descriptions, i.e. the Verilog code effectively mimics the behaviour of the circuit we are implementing using always blocks and control statements such as if … else, case etc. However, Verilog can also be used to describe the structure of a circuit as a hierarchical interconnection of instantiated modules. Consider the higher-level module shown to the left in Figure 10. Internally, this design consists of two AND gates. We could implement this design schematically, as shown in the figure below, or we could represent this design structurally in Verilog.
If a module for the the 2-input AND gate is defined as:
module AND2 (input wire A,
input wire B,
output wire C); // C = A & B
assign C = A.B;
endmodule
Then we can create a module for the higher-level design, top, as:

module top (input wire W,
input wire X,
input wire Y,
output wire z);

// define internal connections
wire int;

AND2 I1(W, X, int); // first AND gate
AND2 I2(int, Y, Z); // second AND gate

endmodule
Here, we have instantiated the module AND as follows:
();
COMP12111 – Verilog Primer Ver 2021
16
It should be noted that:
• internal wires are defined as type wire.
• the name of each instantiated module should be unique.
• list the connections to the inputs/outputs of the instantiated module must be done in the same order as the module header.
Consider, the design of a 4:1 multiplexer using structural Verilog, as shown schematically below. Here, we have three Verilog designs available to us, a 3-input AND gate, a 4 input OR gate and an inverter, with headers:
module AND3(input wire A,
input wire B,
input wire C,
output wire D);

module OR4(input wire P,
input wire Q,
input wire R,
input wire S,
output wire T);

module inverter(input wire M,
output wire N);
Using a hierarchical approach we can construct the 4:1 MUX design form these modules:
COMP12111 – Verilog Primer Ver 2021
17
module mux4to1(input wire [3:0] D,
input wire [1:0] select,
output wire Q);

wire ns0, ns1, a0, a1, a2, a3; // local variables

not nots0(s[0], ns0);
not nots1(s[1], ns1);

and3 and_1(ns0, ns1, D[0], a0);
and3 and_2(s[0], ns1, D[1], a1);
and3 and_3(ns0, s[1], D[2], a2);
and3 and_4(s[0], s[1], D[3], a3);

or4 or_1(a0, a1, a2, a3, Q);

endmodule Where we have defined internal wires to represent the connections between components, so:
• ns0 – the output from the s[0] inverter
• ns1 – the output from the s[1] inverter
• a0 – the output from the top AND gate
• a1 – the output from the 2nd AND gate
• a2 – the output from the 3rd AND gate
• a3 – the output from the bottom AND gate

COMP12111 – Verilog Primer Ver 2021
18
Implementing modules at different levels of abstraction It is worth revisiting abstraction in Verilog to appreciate that there are many ways that that a design can be defined in Verilog, and at different levels of abstraction. Let’s look at the example of a 4:1 multiplexer.
Behavioural description:
module mux4to1(input wire [3:0] D,
input wire [1:0] select,
output wire reg Q);

always@(*) // whenever data or select change
Q = D[select];

endmodule

Behavioural with case statement description:
module mux4to1(input wire [3:0] D,
input wire [1:0] select,
output wire reg Q);

always@(*) // whenever data or select change
case(select)
2’b00: Q = D[0];
2’b01: Q = D[1];
2’b10: Q = D[2];
2’b11: Q = D[3];
default: Q = 4’hx;
endmodule

Structural description See the above discussion on structural Verilog.

Compiler directives Verilog provides a number of compiler directives that are instructions that affect the compilations of the code. One of the most useful is `define (note the direction of the ‘tick’) which can be used for text substitution, for example if you define: `
define FETCH 0 in your code (outside the module) then use `FETCH within your code, then during compilation `FETCH will be replaced with the value 0. This allows constants to be easily changed and also help make code more readable.
COMP12111 – Verilog Primer Ver 2021
19
Another useful compiler directive is `include, which can be used to include code from other files in your design without having to explicitly copy the code in. For example: `
include definitions.v will copy the contents of definitions.v into your file at compilation.

学霸联盟


essay、essay代写