EEE333 Verilog HDL Language Specification
Verilog HDL Language Specification
Modules
A module is declared by writing text describing its functionality and its ports to/from the environment.
Input ports are always a wire.
You never assign a value to inputs inside your module; they’re passed through the port.
Module Syntax:
‘‘‘systemverilog
module module_name( input [3:0] A, B, output [3:0] Y); //module stmnt
// [3:0] is an array declaration; A & B are inputs; Y is an output
wire [3:0] X; //wire declaration
// continuous assign statements:
assign X = (B==4’b0101) ? A:B; //ternary if else
assign Y = X;
endmodule
’’’
Notes on the Module
Multiple modules can occupy the same file.
Internal wires are declared, along with IO ports.
Variables in a module are local.
Ports
There are 3 types of ports:
- Input: Passed from parent to child.*
- Output: Passed from child to parent.
- Inout: Bidirectional, we won’t use this.
*The parent module is the module that instantiates the child module. Child modules are instantiated inside the parent module.
Note: instantiation is different from a subroutine call in a procedural code; netlist components are instantiated.
‘‘‘systemverilog module anyname (output out1, input in1, in2, in3, in4, in5); wire y1, y2; //notice these are out of order compared to what we would expect nor N1 (out1, y1, y2); //that’s because this is a connected netlist and A1 (y1, in1, in2); and A2 (y2, in3, in4, in5); endmodule
//Or, you can actually declare after module initiation:
module anyname (out1, in1, in2);
output out1;
input in1, in2;
’’’
Recall Python’s style of parameter passing: ports have an implicit order that must be followed in both definition and declaration. The ports must also be declared with the same size as in the module definition.
Module instatiation follows the same rules.
And again similar to Python, there is “Association by Name” for long lists of ports. The names can be listed in any order
All declarations are mandatory in HDL; there is no “default” value.
Some Port Rules
- Variables defined in ports in the module statement cannot be redefined.
- Variables declared are assumed to be wires unless specfied as reg.
- Remember wires are nets, not locations for storage; they must be continuously driven.
- Variables declared reg do store values, but they are not “registers” (like in a Microprocessors course).
Module output is either continuously driven (by a continuous assign statement) or assigned in an always block (the former must be declared wire and the latter must be declared reg).
// set y to x
module example1(input x, output y);
assign y = x; // this is without error specifically because y is a wire; setting this in an always block would cause a compile error
endmodule
// example 2
module example2(input x, output reg y);
//must use an always bc y is a reg
always @(*)
begin
y = x;
end
endmodule
//now let's get more complicated
//imagine you have a port output definition of wire, and the variable is set in an always block.
//You'll need to interface between the two
module example3(input x, output y);
reg yy; //declared to store value
assign y = yy;
always @ (*)
yy = x;
endmodule
// More contrivances
// set y to x with a module instance
module example5(input x, output y);
example2 ex2 (x,y);
//notice y is a reg in ex2, which means that the stored y in example2 drives the example5 wire (and therefore the example5 output)
endmodule
//in the next case our always block "prepares inputs and collects outputs"
module example6(input z, output reg yy);
reg x;
wire y;
example2 ex2 (x,y); //notice once again we can mismatch reg/wire assignments if we have a correct interface
always @ (*) begin
//only items declared in an always block can be reg
x = z;
yy = y;
end
endmodule
1’s and 2’s Complement
2’s complement is the most common way to represent negative numbers, digitally.
Simply take the 1’s complement of your binary input, and then add 1.
2’s complement does take a bit away from your representation, so 4 bits becomes 3 bits to represent magnitude and 1 bit to represent sign.
To subtract A from B, take the 2’s complement of A, then add it to B.
Top-Down for 2’s Complement Machine
Design Specification:
- Operate on 4-bit numbers
- Take a 2’s comp (do 1’s comp, then add 1)
- Output the 2’s comp
- Be compiled, sim’d, and tested
We need to partition the problem into modules: half-adder, full-adder, ripple-adder, 2’s complement.
We’ll start with the utility modules at the bottom of the hierarchy.
//Full adder
module FA (input A, B, Cin, output Sum, Cout);
wire w1, w2, w3;
HA A1 (A, B, w1, w2);
HA A2 (w1, Cin, Sum, w3);
or X1 (Cout, w2, w3);
endmodule
//Half adder (no carry in)
module HA (input A, B, output Sum, Cout);
assign Sum = A^B;
assign Cout = A&B;
endmodule
module comp2s(input [3:0] A, output [3:0] C);
wire [3:0] nA;
wire Cout;
assign nA = ~A;
// Cout can be discarded since it doesn't impact 2's comp
RA add (nA, 4'b0, 1'b1, C, Cout);
endmodule
//Ripple adder
module RA (input [3:0] A, B, input Cin, output [3:0] Sum, output Cout);
wire Cout1, Cout2, Cout3;
FA A0 (A[0], B[0], Cin, Sum[0], Cout1);
FA A1 (A[1], B[1], Cout1, Sum[1], Cout1);
FA A2 (A[2], B[2], Cout2, Sum[2], Cout2);
FA A3 (A[3], B[3], Cout3, Sum[3], Cout3);
endmodule
//testbench with 1 input
module test2scomp();
reg [3:0] A;
wire [3:0] C;
comp2s A1 (A, C);
initial begin
A = 4'b0101; #10 // during this delay (10ps), comp2 runs
$display( " A = %b C = %b ", A, C);
end
endmodule
//An alternative 2's comp:
module comp2s_(input [3:0] A, output [3:0] C);
assign C = ~A + 4'd1; //synethesizer makes its own ripple-adder for this
endmodule
Other Operators and Boolean Logic
Bit Shifts
module shift1(input [7:0] X, ShB, output [7:0] YL, YR, YAL, YAR);
assign YL = X << ShB;
assign YR = X >> ShB;
assign YAL = X <<< ShB; //arithmetic shift (use only w/ signed int)
assign YAR = x >>> ShB;
endmodule
Concatenation and Replication
Concatenation is accomplished using {}
.
assign a[15:0] = {b[3:0], c[7:0], d[3:0]};
This is somewhat self-explanatory.
We can precede the {}
with a number (n) to get n number of replications of that data.
For example, if we wanted to do assign a[15:0] = {c[7:0], c[7:0]};
, we could write it as assign a[15:0] = {2{c[7:0]}};
Blocking vs. Non-Blocking Assignments

Most classical digital circuits like flip-flops or registers need to remain non-blocking since the real-life circuit acts in a non-blocking manner. Non-blocking assignments use <=
.
System Verilog
System Verilog is a superset containing Verilog. Verilog code can be run on a System Verilog compiler.
File extension .sv
. System Verilog also offered c-like datatypes (int, struct, enum, typedef). It also has some dynamic datatypes, which are exclusively used for testbenches. On the controls side, you have foreach loops, returns, breaks and continues. System Verilog also has classes for object oriented programming, but these are also mainly used for testbenches.
Data Lifetimes: There are two types of data lifetimes. Automatic and Static. We generally use static; automatic is useful for recursion, and automatic variables are created (automatically) when variables are created within a scope. Otherwise, variables are implicitly static.
New Data Types
Logic: logic [31:0] var;
Logic can be used instead of reg or wire; the synthesizer decides which should be used. This reduces error and makes writing easier.
System Verilog also allows “two dimensions are the left side,” meaning an indexxed array. This is also called “multidimensional packed array.”
logic [1:0][2:0] mvar;
Packed and Unpacked Data
Imagine bits are stored in a linear array:
The indices that aren’t adjacent (nvar, 4-0) are unpacked. Between them are packed arrays, because bits are stored sequentially.

Enumerated Types and Structures
typedef enum logic [2:0] {
red, green, blue, cyan, magenta, yellow
} color_t;
typedef struct packed {
bit[10:0] expo;
bit sign;
bit [51:0] mant;
} FF;
FF zero = 64'b0
//this allows a pattern to be mapped on
//a packed array of bits with contents in contiguous memory
//functions can be called inside always blocks
function datatype funnam;
input var1, var2... ;
begin
funnam = var1*var2; //result is stored back IN FUNCTION NAME
end
endfunction
3 Always Blocks
always_ff is for describing flipflop behavior; it only reevaluates if the value in the sensitivity list changes. It is edge-sensitive. It has non-blocking assigns.
always_ff @ (posedge clk, ...)
always_comb means always @ (*)
. Must describe a combinatorial, and not a latch.
always_comb begin ...

An example of what not to do with an always_comb block:
Interface
If you have a complex structure of inputs and outputs used in multiple modules, the interface statement is warranted.
interface Bus;
logic [7:0] Addr, Data;
logic RWn;
endinterface
Even more data types
Strings! string s = "Hello;"
;
Key Rules
Avoid two always statements modifying the same value, within a module.
No mixing level sensitivity and edge sensitivity in the same always block (always @ (posedge clk or reset)
). Similarly, avoid positive/negative triggered flipflops in the same block.
Use a continuous assignment statement whenever you can.
Don’t mix blocking and non-blocking in the same always block.
always_ff
: always non-blocking. always_comb
: always blocking. Combinatorial blocks do not contain resets.
To avoid inferred latches: flipflops are always a better way to store memory; use always_ff and always_comb. Use default conditions in else statements.