UNIT-3
HARDWARE DESCRIPTIONAL LANGUAGE
The structural specification of logic circuits in Verilog describes a circuit in terms of its
components (gates, modules, etc.) and how they are interconnected. It's akin to drawing a
schematic at the code level, where each module or gate is instantiated, and the connections
between inputs and outputs are explicitly defined. This approach contrasts with behavioral
specifications, where the desired function is described without focusing on the circuit's
structure.
Key Concepts of Structural Specification in Verilog
1. Module Instantiation: A structural description uses existing modules, which can be primitive
gates (e.g., AND, OR, NOT) or user-defined modules, connected together. Each module
represents a component in the circuit.
2. Port Connections: When you instantiate a module or gate, you specify how its inputs and
outputs connect to other parts of the circuit using signals (wires or registers).
3. Wires: In Verilog, `wire` is a fundamental data type used to connect the inputs and outputs
of different gates or modules. They represent continuous assignment.
4. Hierarchy: Structural Verilog allows for hierarchical design by instantiating modules within
other modules, enabling the reuse of code and easier debugging.
Example of Verilog Structural Design
Here’s a simple example that illustrates the structural specification of a full adder using
Verilog. We will build the full adder by connecting basic gates (AND, OR, XOR):
```verilog
module half_adder (
input wire A, B,
output wire Sum, Carry
);
xor (Sum, A, B); // Sum = A ⊕ B
and (Carry, A, B); // Carry = A & B
endmodule
module full_adder (
input wire A, B, Cin,
output wire Sum, Cout
);
wire Sum1, Carry1, Carry2;
// Instantiate two half adders
half_adder HA1 (.A(A), .B(B), .Sum(Sum1), .Carry(Carry1));
half_adder HA2 (.A(Sum1), .B(Cin), .Sum(Sum), .Carry(Carry2));
// OR gate for final carry out
or (Cout, Carry1, Carry2);
endmodule
```
Explanation of the Example:
Half Adder Module: This module implements a half adder using XOR for the sum and AND
for the carry.
Full Adder Module: It instantiates two half adders. The first adds `A` and `B`, and the second
adds the sum of the first adder to the carry input (`Cin`). The final carry-out (`Cout`) is the OR
of the two carry bits.
Characteristics of Structural Verilog:
Modular and Hierarchical: Components are reusable and modular, making the design scalable
and easy to manage.
Gate-Level Design: It gives a low-level view of the circuit, allowing fine control over how
components are connected.
Wires and Ports: Explicit wiring between the components is required, reflecting the physical
connections in the actual hardware.
When to Use Structural Specification:
Low-level hardware design: Structural Verilog is useful when designing circuits at the gate
level or working on hardware like FPGAs and ASICs, where control over the circuit layout is
important.
Optimization: It provides the ability to manually optimize critical paths or layout.
Schematic Capture: It closely aligns with the physical design, making it easy to translate
between schematics and Verilog code.
By using structural Verilog, designers can implement complex logic circuits by interconnecting
smaller components, much like building a circuit with physical logic gates on a breadboard.
The behavioral specification of logic circuits in Verilog describes how the circuit should
behave in terms of its functional output for given inputs, without focusing on the physical
structure or how individual gates are interconnected. This type of specification allows designers
to abstract away the implementation details and focus more on the functionality of the design.
In behavioral Verilog, the code resembles software programming in which the circuit's
behavior is described using high-level constructs like `if-else`, `case`, and loops. The synthesis
tool is responsible for converting the behavior into actual hardware (gate-level logic).
Key Features of Behavioral Specification in Verilog
1. Always Block: The `always` block is central to behavioral modeling. It defines how the
circuit reacts to changes in inputs. There are two types of `always` blocks:
Combinational Logic: Use `always @(*)` to model circuits where the output depends purely
on the current inputs (like multiplexers, adders, etc.).
Sequential Logic: Use `always @(posedge clk)` to model clocked circuits such as flip-flops
and registers.
2. Procedural Constructs: Behavioral Verilog uses programming constructs like `if`, `else`,
`case`, and loops to describe how inputs are processed.
3. Registers (`reg`) and Wires (`wire`): Registers are used to store values in behavioral
descriptions, while wires are used for continuous assignments.
4. High-Level Abstraction: Behavioral models describe "what" the circuit should do, leaving
the "how" (implementation) to the synthesis tools.
Example of Behavioral Design
Let’s write a simple 4:1 multiplexer using behavioral Verilog:
```verilog
module mux_4to1 (
input wire [1:0] sel, // 2-bit select line
input wire [3:0] in, // 4-bit input (4 inputs)
output reg out // 1-bit output
);
always @(*) begin
case (sel)
2'b00: out = in[0]; // Select input 0
2'b01: out = in[1]; // Select input 1
2'b10: out = in[2]; // Select input 2
2'b11: out = in[3]; // Select input 3
default: out = 1'b0; // Default case
endcase
end
endmodule
```
Explanation of the Example:
Multiplexer (MUX) Example: The `mux_4to1` module selects one of the 4 inputs (`in[0]` to
`in[3]`) based on the value of the 2-bit select line `sel`. This is modeled using a `case` statement
inside the `always @(*)` block, which specifies that the output `out` will be assigned based on
the value of `sel`.
Behavioral Description: The functionality of the multiplexer is described by specifying how
the output changes depending on the input selection lines, without worrying about the
underlying logic gates or hardware structure.
Behavioral Specification Characteristics:
1. Describes Functionality, Not Structure: Behavioral modeling focuses on the function of the
circuit, not how it is physically constructed. It allows the design to be written more concisely
and abstractly.
2. Flexible and Abstract: It's easier to describe complex algorithms or control logic using
behavioral constructs, and the synthesis tool handles the details of how the circuit is built.
3. Use of Control Statements: You can use constructs like `if-else`, `case`, and `for` to describe
complex decision-making logic or repetitive tasks.
4. Easier to Write and Debug: Compared to structural descriptions, behavioral code is often
more compact and closer to software programming, making it easier to write and debug.
Another Example: 4-bit Counter
A 4-bit up-counter using behavioral Verilog is another common example. This circuit
increments its output by 1 every clock cycle.
```verilog
module counter (
input wire clk, // Clock signal
input wire reset, // Reset signal
output reg [3:0] count // 4-bit output
);
always @(posedge clk or posedge reset) begin
if (reset)
count <= 4'b0000; // Reset the counter to 0
else
count <= count + 1; // Increment the counter
end
endmodule
```
Explanation:
Sequential Circuit: This counter increments its value on every rising edge of the clock. If the
reset signal is high, the counter resets to 0.
Behavioral Description: The functionality is captured using an `always` block triggered by the
clock and reset. The `if-else` statement handles the reset logic.
Use Cases of Behavioral Verilog
Complex Combinational Logic: Easier to write multiplexers, ALUs, or priority encoders using
behavioral constructs.
Finite State Machines (FSMs): Behavioral Verilog is ideal for FSMs, where the states and
transitions can be expressed concisely using `case` or `if-else` constructs.
Algorithmic Designs: Behavioral Verilog is useful for describing operations like filtering, data
processing, and control flow logic.
Sequential Logic: Flip-flops, counters, registers, and pipelines can be easily modeled with
`always @(posedge clk)` constructs.
Behavioral vs Structural Verilog
Feature Behavioral Verilog Structural Verilog
Low-level gate and module
Focus Functionality and behavior of the circuit
interconnections
Level of
High-level, abstract Low-level, close to hardware
Abstraction
Ease of
Easier to write and debug More verbose and detailed
Writing
Control logic, algorithms, FSMs, Circuit implementation with
Use Case
combinational/sequential circuits known gate/module connections
High, often used for describing functional Moderate, mainly for reusable
Reusability
blocks structural designs
Behavioral Verilog simplifies the design process by focusing on how the circuit should behave,
leaving the actual implementation to be inferred by the synthesis tool. It's widely used for
writing concise, functional descriptions of digital circuits.
Hierarchical Verilog design involves organizing a complex system into smaller, manageable
components (modules), each responsible for a particular functionality. This modular approach
makes the design scalable and reusable. Each module can be instantiated in a higher-level
module, forming a hierarchy.
Here’s an example of a hierarchical Verilog code where a 4-bit ripple carry adder is built using
full adder modules, which in turn are built using half adders. Each level represents a different
module in the hierarchy.
Example: 4-bit Ripple Carry Adder
Step 1: Half Adder Module
The half adder is the most basic component and is responsible for adding two single bits.
```verilog
module half_adder (
input wire A, B, // Two 1-bit inputs
output wire Sum, Carry // 1-bit outputs
);
assign Sum = A ^ B; // XOR for sum
assign Carry = A & B; // AND for carry
endmodule
```
Step 2: Full Adder Module
A full adder is constructed using two half adders. It adds two bits plus a carry-in bit.
```verilog
module full_adder (
input wire A, B, Cin, // Two inputs (A, B) and a carry-in (Cin)
output wire Sum, Cout // Sum and carry-out
);
wire Sum1, Carry1, Carry2;
// Instantiate two half adders
half_adder HA1 (.A(A), .B(B), .Sum(Sum1), .Carry(Carry1));
half_adder HA2 (.A(Sum1), .B(Cin), .Sum(Sum), .Carry(Carry2));
// Generate carry-out
assign Cout = Carry1 | Carry2;
endmodule
```
Step 3: 4-bit Ripple Carry Adder Module
Now, we create a 4-bit adder by instantiating four full adders and connecting them in series, so
the carry-out of one adder is passed to the carry-in of the next adder.
```verilog
module ripple_carry_adder_4bit (
input wire [3:0] A, B, // 4-bit inputs A and B
input wire Cin, // Carry-in
output wire [3:0] Sum, // 4-bit sum output
output wire Cout // Carry-out
);
wire C1, C2, C3; // Internal carry wires
// Instantiate 4 full adders for 4 bits
full_adder FA0 (.A(A[0]), .B(B[0]), .Cin(Cin), .Sum(Sum[0]), .Cout(C1));
full_adder FA1 (.A(A[1]), .B(B[1]), .Cin(C1), .Sum(Sum[1]), .Cout(C2));
full_adder FA2 (.A(A[2]), .B(B[2]), .Cin(C2), .Sum(Sum[2]), .Cout(C3));
full_adder FA3 (.A(A[3]), .B(B[3]), .Cin(C3), .Sum(Sum[3]), .Cout(Cout));
endmodule
```
Explanation:
Half Adder: Adds two single bits (`A` and `B`), producing a sum and a carry-out.
Full Adder: Adds two bits and a carry-in (`Cin`). It is built using two half adders.
4-bit Ripple Carry Adder: This is the top-level module that adds two 4-bit numbers. It
instantiates four full adders, connecting the carry-out (`Cout`) of each adder to the carry-in of
the next.
Hierarchy Overview:
Top-Level (ripple_carry_adder_4bit): Contains four instances of the full_adder module.
Middle Level (full_adder): Contains two instances of the half_adder module.
Bottom Level (half_adder): The basic building block.
Testbench for Hierarchical Design
To verify the functionality, you can write a testbench that stimulates the 4-bit ripple carry adder:
```verilog
module tb_ripple_carry_adder_4bit;
reg [3:0] A, B;
reg Cin;
wire [3:0] Sum;
wire Cout;
// Instantiate the 4-bit ripple carry adder
ripple_carry_adder_4bit RCA (.A(A), .B(B), .Cin(Cin), .Sum(Sum), .Cout(Cout));
initial begin
// Test case 1
A = 4'b0001; B = 4'b0011; Cin = 1'b0; #10;
$display("A = %b, B = %b, Cin = %b, Sum = %b, Cout = %b", A, B, Cin, Sum, Cout);
// Test case 2
A = 4'b0110; B = 4'b0101; Cin = 1'b1; #10;
$display("A = %b, B = %b, Cin = %b, Sum = %b, Cout = %b", A, B, Cin, Sum, Cout);
// Test case 3
A = 4'b1111; B = 4'b1111; Cin = 1'b0; #10;
$display("A = %b, B = %b, Cin = %b, Sum = %b, Cout = %b", A, B, Cin, Sum, Cout);
// Test case 4
A = 4'b1010; B = 4'b1100; Cin = 1'b1; #10;
$display("A = %b, B = %b, Cin = %b, Sum = %b, Cout = %b", A, B, Cin, Sum, Cout);
end
endmodule
```
Explanation of the Testbench:
- The testbench instantiates the **ripple_carry_adder_4bit** module and applies several test
cases to validate the design.
- The `$display` statements print the inputs and outputs after each test case to observe the
behavior of the adder.
Benefits of Hierarchical Design:
1. Modularity: Each module performs a specific function, making it easier to design,
understand, and reuse.
2. Scalability: Adding more bits to the adder (e.g., an 8-bit or 16-bit adder) can be easily done
by instantiating more full adders.
3. Maintainability: Changing the functionality of the adder at a low level (e.g., updating the
half adder) doesn't require modifications in the top-level module.
4. Reusability: Each module (e.g., `full_adder`, `half_adder`) can be reused in other designs.
Hierarchical Verilog coding is a powerful method for designing complex systems by dividing
them into smaller, more manageable submodules that can be easily understood and tested
independently.
In Verilog, combinational circuits can be described using conditional operators (`?:`) and `if-
else` statements. These constructs allow you to define logic based on certain conditions. Below
are examples for both methods.
1. Conditional Operator (`?:`)
The conditional operator in Verilog works similarly to the ternary operator in C. It selects one
of two values based on the evaluation of a condition. The syntax is:
```verilog
(condition) ? true_value : false_value;
```
Example: 2:1 Multiplexer Using Conditional Operator
```verilog
module mux_2to1_conditional (
input wire A, B, // 1-bit inputs
input wire sel, // Select line
output wire Y // Output
);
assign Y = (sel) ? B : A; // If sel is 1, output B, otherwise output A
endmodule
```
Explanation:
- The conditional operator `(sel) ? B : A` checks the value of `sel`. If `sel` is `1`, the output `Y`
is `B`; otherwise, it’s `A`.
---
2. If-Else Statement
The `if-else` construct is a standard way to describe conditional logic. It allows for more
complex conditions and can be nested or chained with multiple conditions.
Example: 4:1 Multiplexer Using `if-else`
```verilog
module mux_4to1_ifelse (
input wire [1:0] sel, // 2-bit select line
input wire [3:0] in, // 4-bit input (4 inputs)
output reg Y // Output (reg because we are using procedural assignments)
);
always @(*) begin
if (sel == 2'b00)
Y = in[0]; // If sel is 00, select in[0]
else if (sel == 2'b01)
Y = in[1]; // If sel is 01, select in[1]
else if (sel == 2'b10)
Y = in[2]; // If sel is 10, select in[2]
else
Y = in[3]; // Otherwise (sel == 11), select in[3]
end
endmodule
```
Explanation:
- The `always @(*)` block is used for combinational logic. This ensures the block is sensitive
to any changes in the inputs.
- The `if-else` construct allows for checking multiple conditions (based on the value of `sel`)
to determine which input to output.
---
3. Case Statement (Alternative to `if-else`)
For multiple conditions, like in the example above, a `case` statement can be more readable
than chaining `if-else` statements.
Example: 4:1 Multiplexer Using `case`
```verilog
module mux_4to1_case (
input wire [1:0] sel, // 2-bit select line
input wire [3:0] in, // 4-bit input (4 inputs)
output reg Y // Output
);
always @(*) begin
case (sel)
2'b00: Y = in[0]; // If sel is 00, select in[0]
2'b01: Y = in[1]; // If sel is 01, select in[1]
2'b10: Y = in[2]; // If sel is 10, select in[2]
2'b11: Y = in[3]; // If sel is 11, select in[3]
default: Y = 1'b0; // Default case (optional)
endcase
end
endmodule
```
Explanation:
- The `case` statement matches the value of `sel` with different cases, making it more concise
when you have several conditions to check, especially in multiplexers or decoders.
---
Testbench for 4:1 Multiplexer
To verify any of the above multiplexer designs, you can write a testbench like the following:
```verilog
module tb_mux_4to1;
reg [1:0] sel;
reg [3:0] in;
wire Y;
// Instantiate the mux (change the module name based on the design you want to test)
mux_4to1_case uut (
.sel(sel),
.in(in),
.Y(Y)
);
initial begin
// Test Case 1: sel = 00, expect Y = in[0]
in = 4'b1010; sel = 2'b00; #10;
$display("sel = %b, in = %b, Y = %b", sel, in, Y);
// Test Case 2: sel = 01, expect Y = in[1]
sel = 2'b01; #10;
$display("sel = %b, in = %b, Y = %b", sel, in, Y);
// Test Case 3: sel = 10, expect Y = in[2]
sel = 2'b10; #10;
$display("sel = %b, in = %b, Y = %b", sel, in, Y);
// Test Case 4: sel = 11, expect Y = in[3]
sel = 2'b11; #10;
$display("sel = %b, in = %b, Y = %b", sel, in, Y);
end
endmodule
```
Explanation of the Testbench:
- The testbench applies various input values (`in` and `sel`) to the 4:1 multiplexer and observes
the output (`Y`).
- The `$display` statement prints the values to the simulation console for verification.
---
Conclusion
- The conditional operator (`?:`) is useful for simple conditions, where the logic can be written
compactly on one line.
- The `if-else` statement is better suited for more complex conditions and provides greater
flexibility when modeling combinational circuits.
- The `case` statement is a cleaner and more readable option when dealing with multiple
conditions, especially for multiplexers, decoders, or state machines.
Each of these methods can be used in different contexts, depending on the complexity of the
logic and the readability of the code.
In Verilog, loops (such as `for` loops) are commonly used to describe repeated hardware
behavior, particularly when dealing with sequential circuits like shift registers, counters, or
memory-related designs. These loops are primarily used in procedural blocks like `always`
blocks to describe sequential logic.
Below is an example of using a for` loop within a sequential circuit. The circuit will be a shift
register where data is shifted on each clock cycle.
Example: Shift Register Using `for` Loop
Shift Register Description:
- A shift register is a sequential circuit that shifts its stored data on each clock cycle. It’s often
used for serial-to-parallel or parallel-to-serial conversion.
- We will create an 8-bit shift register where data shifts left on each clock edge.
Verilog Code for Shift Register with a `for` Loop
```verilog
module shift_register (
input wire clk, // Clock signal
input wire rst, // Reset signal (active high)
input wire shift_in, // New data bit to shift in
output reg [7:0] data_out // 8-bit output
);
integer i; // Integer for loop index
always @(posedge clk or posedge rst) begin
if (rst) begin
// Reset the register to zero
data_out <= 8'b0;
end else begin
// Shift data left and insert new bit
for (i = 7; i > 0; i = i - 1) begin
data_out[i] <= data_out[i-1];
end
// Insert new bit at LSB
data_out[0] <= shift_in;
end
end
endmodule
```
Explanation:
Shift Register Operation:
- On each positive clock edge (`posedge clk`), the bits of the register are shifted to the left.
- The new bit `shift_in` is inserted at the least significant bit (LSB).
- The `for` loop shifts the contents of the register by moving the value of each bit to the next
higher bit position.
- Reset Logic:
- If the reset signal (`rst`) is active, the entire register is cleared to 0.
- The `data_out` signal is 8 bits wide and represents the current value of the shift register.
CAD Tool Flow for Sequential Circuits
When you implement sequential circuits like this with a CAD tool (e.g., Vivado, Quartus,
Synopsys Design Compiler), you typically follow these steps:
1. Write the Verilog Code: Design the shift register (or other sequential circuits) using Verilog.
This can be done using any text editor or the built-in editor in the CAD tool.
2. Simulation:
- Write a testbench to simulate the design and verify its functionality.
- Example of a testbench for the above shift register:
```verilog
module tb_shift_register;
reg clk, rst;
reg shift_in;
wire [7:0] data_out;
// Instantiate the shift register
shift_register uut (
.clk(clk),
.rst(rst),
.shift_in(shift_in),
.data_out(data_out)
);
// Clock generation
initial begin
clk = 0;
forever #5 clk = ~clk; // Toggle clock every 5 time units
end
// Test stimulus
initial begin
rst = 1; shift_in = 0;
#10 rst = 0; // Release reset after 10 time units
#10 shift_in = 1; // Shift in 1
#10 shift_in = 0; // Shift in 0
#10 shift_in = 1; // Shift in 1
#10 shift_in = 1; // Shift in 1
#10 shift_in = 0; // Shift in 0
#50 $stop; // End simulation after 50 time units
end
endmodule
```
- In the testbench, we generate the clock signal and apply the reset and `shift_in` inputs. The
simulation will show the shifting behavior of the register on each clock cycle.
3. Synthesis:
- Use the CAD tool to synthesize the design. During synthesis, the tool will convert the
Verilog RTL code into a gate-level netlist.
- Example flow in Vivado:
- Create a new project and add the Verilog source file.
- Run synthesis, and check the resource utilization (LUTs, FFs) and timing analysis.
- In Quartus, the synthesis process is similar:
- Create a project, import the Verilog files, and compile the design to generate the netlist.
4. Implementation (Place and Route):
- Once synthesis is complete, you can proceed with **place and route** to map the design
to the actual FPGA resources.
- You can perform timing analysis to verify if the design meets your clock frequency
constraints.
5. Verification on FPGA:
- After place and route, generate a bitstream (for FPGAs) and upload it to an FPGA board
(e.g., Xilinx boards).
- You can observe the shift register behavior by connecting the FPGA’s output to an
oscilloscope or using a testbench with I/O devices.
Key Points for Using `for` Loops in Sequential Circuits
1. Sequential Execution: In sequential circuits, loops like `for` are executed every time the
clock edge occurs. The loop doesn't represent "parallel" hardware but instead performs
sequential bit-level operations during each clock cycle.
2. Synthesis Considerations:
- CAD tools like Vivado or Quartus can handle `for` loops in Verilog code efficiently. During
synthesis, the loop is "unrolled" and translated into a series of gates and flip-flops.
- Ensure that the loop doesn't create unnecessary or combinational logic inside a sequential
block, which could affect timing or result in synthesis failures.
3. Use in Finite State Machines (FSMs):
- `for` loops can be useful in FSMs where a certain number of iterations are needed to
complete a task (e.g., counting, memory initialization).
4. Hardware Representation:
- Keep in mind that every loop in Verilog corresponds to real hardware. If you describe a
loop that shifts 8 bits, the CAD tool will synthesize 8 flip-flops and the corresponding logic.
Conclusion
Using a `for` loop in Verilog for sequential circuits allows for compact, readable designs,
especially for repetitive tasks like shifting, counting, or iterating over arrays. CAD tools can
handle these constructs efficiently, translating them into hardware that performs the required
sequential logic operations.