7.1 RTL Design with VHDL
Register Transfer Level (RTL) design is a high-level abstraction used in digital circuit design, representing the flow of data between registers and the operations performed on that data during clock cycles. VHDL (VHSIC Hardware Description Language) is widely used for RTL design, allowing designers to model and implement digital systems effectively. This section covers the basics of RTL design using VHDL, including key concepts, structure, and examples.
1. Key Concepts of RTL Design
- Register Transfer: RTL design focuses on how data is transferred between registers and how operations are performed on that data during each clock cycle.
- Clock Signal: The clock signal synchronizes the operations in the design, determining when data is transferred and when operations are performed.
- Data Path: The path through which data flows between registers and functional units (like adders, multiplexers) in a design.
- Control Logic: Logic that determines when and how data transfers occur, often implemented using state machines or combinational logic.
2. Structure of RTL Design in VHDL
An RTL design in VHDL typically consists of the following components:
- Entity: Defines the interface of the design, including inputs, outputs, and any parameters.
- Architecture: Describes the internal structure and behavior, detailing how data is processed and transferred.
- Signal Declarations: Used to declare internal signals that facilitate communication between components.
- Processes: Define sequential and concurrent behavior, describing how data is manipulated over time.
- State Machines (if applicable): Control the flow of operations based on the current state.
3. Example: 4-bit Synchronous Counter
Here’s a simple example of an RTL design for a 4-bit synchronous counter using VHDL:
3.1 Entity Declaration
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity Counter is
Port (
clk : in STD_LOGIC; — Clock input
reset : in STD_LOGIC; — Asynchronous reset
count : out STD_LOGIC_VECTOR(3 downto 0) — 4-bit counter output
);
end Counter;
architecture Behavioral of Counter is
signal temp_count : STD_LOGIC_VECTOR(3 downto 0) := “0000”; — Internal signal for count
begin
process(clk, reset)
begin
if reset = ‘1’ then
temp_count <= “0000”; — Reset counter to 0
elsif rising_edge(clk) then
temp_count <= temp_count + “0001”; — Increment counter
end if;
end process;
count <= temp_count; — Assign internal count to output
end Behavioral;
3.3 Explanation of the Code
- Entity: The
Counter
entity has three ports: a clock input (clk
), a reset input (reset
), and a 4-bit output (count
).
- Architecture:
- An internal signal
temp_count
is declared to hold the counter value.
- A process block triggers on the
clk
and reset
signals.
- When
reset
is high, the counter resets to “0000”.
- On the rising edge of the clock, the counter increments by 1.
- The final count is assigned to the output port.
4. Simulation and Testing
Simulation tools can be used to test the functionality of the RTL design. A testbench can be created to apply various clock and reset signals and observe the counter’s behavior.
Example Testbench for the Counter
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity tb_Counter is
end tb_Counter;
architecture testbench of tb_Counter is
signal clk : STD_LOGIC := ‘0’;
signal reset : STD_LOGIC := ‘0’;
signal count : STD_LOGIC_VECTOR(3 downto 0);
— Instantiate the Counter
component Counter
Port (
clk : in STD_LOGIC;
reset : in STD_LOGIC;
count : out STD_LOGIC_VECTOR(3 downto 0)
);
end component;
begin
UUT: Counter port map (clk, reset, count); — Unit Under Test
— Clock Generation
clk_process : process
begin
clk <= ‘0’;
wait for 10 ns; — Clock period
clk <= ‘1’;
wait for 10 ns;
end process clk_process;
— Stimulus Process
stimulus: process
begin
reset <= ‘1’; — Assert reset
wait for 20 ns;
reset <= ‘0’; — Deassert reset
wait for 200 ns;
wait; — End of simulation
end process stimulus;
end testbench;
7.1.1 Shape of VHDL
The “shape” of VHDL typically refers to the structural organization and syntax of VHDL code. Understanding the basic shape helps designers effectively communicate hardware designs and behaviors. Below are the primary components and structure of VHDL code, often visualized in a hierarchical or modular form.
1. Basic Structure of VHDL Code
A complete VHDL program generally consists of two main parts: Entity Declaration and Architecture Body.
- Entity Declaration
- Defines the interface of the design (inputs, outputs, and other parameters).
- Acts as the contract for what the component can do without exposing its internal workings.
Example:
entity ExampleEntity is
Port (
input_a : in STD_LOGIC; — Input signal
input_b : in STD_LOGIC; — Input signal
output_c : out STD_LOGIC — Output signal
);
end ExampleEntity;
- Architecture Body
- Describes the internal workings of the entity.
- Specifies the behavior or structure of the circuit.
- Can contain processes, signal declarations, and component instantiations.
Example:
architecture Behavioral of ExampleEntity is
signal temp : STD_LOGIC; — Internal signal declaration
begin
process(input_a, input_b)
begin
temp <= input_a AND input_b; — Combinational logic operation
end process;
output_c <= temp; — Assign internal signal to output
end Behavioral;
2. Hierarchical Design
VHDL supports hierarchical design, which allows you to build complex systems by composing simpler components. This approach enhances modularity, reusability, and maintainability.
- Top-Level Design: Contains instances of lower-level components (sub-components).
Example:
entity TopLevel is
Port (
clk : in STD_LOGIC;
rst : in STD_LOGIC;
output_signal : out STD_LOGIC
);
end TopLevel;
architecture Structural of TopLevel is
— Component Declaration
component SubComponent
Port (
clk : in STD_LOGIC;
rst : in STD_LOGIC;
output_signal : out STD_LOGIC
);
end component;
— Component Instantiation
begin
U1: SubComponent port map(clk, rst, output_signal);
end Structural;
3. Data Flow vs. Behavioral vs. Structural Models
- Data Flow Model: Represents the flow of data between registers and functional units.
Example:
architecture DataFlow of ExampleEntity is
begin
output_c <= input_a AND input_b; — Direct signal assignment
end DataFlow;
- Behavioral Model: Describes how the system behaves in response to inputs, often using processes and conditional statements.
Example:
architecture Behavioral of ExampleEntity is
begin
process(input_a, input_b)
begin
if input_a = ‘1’ then
output_c <= input_b; — Behavior based on input
else
output_c <= ‘0’;
end if;
end process;
end Behavioral;
Structural Model: Shows how components are interconnected and how they interact with each other.
- Example:
architecture Structural of TopLevel is
component AND_Gate
Port (
A : in STD_LOGIC;
B : in STD_LOGIC;
Y : out STD_LOGIC
);
end component;
signal a, b, y : STD_LOGIC; — Internal signals
begin
U1: AND_Gate port map (A => a, B => b, Y => y); — Connecting components
end Structural;
4. VHDL Syntax Basics
- Comments: Use
--
for single-line comments, and /* ... */
for multi-line comments.
- Signals: Declared using
signal
keyword, used for internal connections.
- Variables: Declared within processes using
variable
keyword, used for local data manipulation.
- Processes: A sequential block of statements that respond to events.
7.1.2 Data Types
In VHDL (VHSIC Hardware Description Language), data types are essential for defining the kinds of data that can be manipulated in a design. Each data type specifies a set of valid values and operations that can be performed on those values. This section outlines the primary data types in VHDL and their characteristics.
1. Basic Data Types
- BIT
- Represents a binary value, either
0
or 1
.
- Example:
signal a : BIT;
a <= ‘1’; — Assigning a binary value
- BOOLEAN
- Represents a logical value, either
TRUE
or FALSE
.
- Example:
signal b : BOOLEAN;
b <= TRUE; — Assigning a boolean value
- INTEGER
- Represents whole numbers, typically in the range of -2,147,483,648 to 2,147,483,647.
- Example:
signal count : INTEGER;
count <= 5; — Assigning an integer value
- REAL
- Represents real numbers (floating-point values).
- Example:
signal temperature : REAL;
temperature <= 36.5; — Assigning a real number
2. Composite Data Types
- ARRAY
- A collection of elements of the same type, indexed by an integer or enumerated type.
- Example:
type ByteArray is array (0 to 7) of BIT; — Declaring an array of BITs
signal myByte : ByteArray;
myByte(0) <= ‘1’; — Assigning a value to an array element
- RECORD
- A collection of related data items of different types grouped together.
- Example:
type Student is record
name : STRING(1 to 20);
age : INTEGER;
end record;
signal student1 : Student; — Declaring a record type
student1.age <= 20; — Assigning values to the record fields
3. Access Types
- ACCESS
- A pointer to a dynamically allocated object. Similar to pointers in programming languages like C.
- Example:
type IntPointer is access INTEGER; — Declaring an access type
signal p : IntPointer;
4. File Types
- FILE
- Represents a file type that can be used for reading and writing data to files.
- Example:
file myFile : TEXT; — Declaring a text file type
5. Enumeration Types
- ENUMERATED
- A user-defined type that consists of a set of named values (enumerators).
- Example:
type State is (IDLE, RUNNING, STOPPED); — Declaring an enumerated type
signal currentState : State;
currentState <= RUNNING; — Assigning an enumerated value
6. Subtypes
- VHDL allows you to create subtypes, which are derived from existing types with specific constraints.
- Example:
subtype PositiveInteger is INTEGER range 1 to 100; — Defining a subtype
signal age : PositiveInteger;
age <= 25; — Assigning a value to the subtype
7. Type Conversion
- VHDL supports type conversion to enable the assignment of values between different data types.
- Example:
signal a : INTEGER;
signal b : BIT;
a <= 1;
b <= BIT’VAL(a); — Converting INTEGER to BIT
7.1.3 Concurrent Statements
In VHDL, concurrent statements are used to define the behavior of hardware that operates simultaneously. Unlike sequential statements, which execute in a specific order within a process, concurrent statements are executed concurrently, reflecting the parallel nature of hardware operations. This section covers the key types of concurrent statements in VHDL, their syntax, and examples.
1. Concurrent Signal Assignment
This statement assigns values to signals based on the specified conditions. It continuously updates the signal when any of the source signals change.
Syntax:
signal_name <= expression;
Example:
signal a, b, c : BIT;
c <= a AND b; — Concurrent assignment of the AND operation
2. Component Instantiation
Component instantiation allows you to use previously defined entities as components in a higher-level design. Each instance of a component can have its own set of signal mappings.
Syntax:
u1: component_name port map ( port1 => signal1, port2 => signal2);
Example:
— Assuming a component called AND_Gate is already defined
U1: AND_Gate port map (A => a, B => b, Y => c); — Instantiating an AND gate
3. Block Statements
A block statement groups multiple concurrent statements, allowing for hierarchical design and the application of specific configurations or declarations. Blocks can also define their own signals and variables.
Syntax:
block block_label is
— Declarations
begin
— Concurrent statements
end block block_label;
Example:
my_block: block
signal temp : BIT;
begin
temp <= a AND b; — Concurrent statement inside the block
c <= temp OR a; — Another concurrent statement
end block my_block;
4. Generate Statements
Generate statements allow for the conditional or iterative instantiation of components. They are useful for creating repetitive structures or conditional hardware configurations.
Syntax:
gen_label: for i in range loop
— Component instantiation
end loop gen_label;
Example:
gen_gates: for i in 0 to 3 generate
U1: AND_Gate port map (A => a(i), B => b(i), Y => c(i)); — Generating multiple AND gates
end generate gen_gates;
5. Process Statements
Although process statements are technically sequential, they can still be part of concurrent statements when used within an architecture. The process statement allows for sequential execution of statements while remaining part of the concurrent context of the design.
Example:
architecture Behavioral of MyDesign is
begin
process(a, b)
begin
if (a = ‘1’) then
c <= b;
else
c <= ‘0’;
end if;
end process;
end Behavioral;
In this example, the process listens for changes in a
and b
, but it is still part of the overall concurrent execution within the architecture.
7.1.4 Processes and Variables
In VHDL, processes and variables are essential constructs for modeling behavior in digital designs. While processes allow for sequential execution of statements, variables provide a way to store and manipulate data within these processes. This section covers the definition, usage, and examples of processes and variables in VHDL.
1. Processes
A process in VHDL is a sequential block of statements that executes in response to changes in signals or events. Processes are useful for describing behaviors such as combinational logic or sequential logic.
Key Characteristics:
- A process can have one or more sensitivity lists, indicating which signals will trigger its execution.
- Within a process, statements are executed in the order they are written.
- Processes can contain variable declarations, signal assignments, and conditional logic.
Syntax:
process(sensitivity_list)
begin
— Sequential statements
end process;
Example:
architecture Behavioral of MyDesign is
signal a, b : BIT;
signal c : BIT;
begin
process(a, b) — Sensitivity list
begin
if (a = ‘1’ and b = ‘1’) then
c <= ‘1’; — Assign value based on conditions
else
c <= ‘0’;
end if;
end process;
end Behavioral;
2. Variables
A variable in VHDL is a data storage element that can hold a value within a process. Variables can be updated without waiting for the process to complete, making them useful for temporary storage or intermediate calculations.
Key Characteristics:
- Variables are declared within a process and can only be accessed within that process.
- When a variable is assigned a value, the update occurs immediately within the process’s scope.
- Variables are typically used for holding intermediate results in sequential logic.
Syntax:
variable variable_name : data_type;
Example:
process(clk)
variable temp : BIT; — Variable declaration
begin
if rising_edge(clk) then
temp := a AND b; — Immediate assignment
c <= temp; — Signal assignment
end if;
end process;
3. Differences Between Signals and Variables
- Scope:
- Signals are accessible throughout the entire architecture, while variables are local to the process where they are declared.
- Update Timing:
- Signal assignments occur at the end of the process execution, meaning they reflect the new value after the process completes. In contrast, variable assignments take effect immediately.
- Use Cases:
- Signals are typically used for inter-process communication or to represent outputs, while variables are used for temporary storage and intermediate calculations within a process.
4. Example of Process and Variable Usage
Here’s a complete example illustrating both processes and variables in a simple flip-flop design:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity D_FlipFlop is
Port (
clk : in STD_LOGIC;
d : in STD_LOGIC;
q : out STD_LOGIC
);
end D_FlipFlop;
architecture Behavioral of D_FlipFlop is
signal q_temp : STD_LOGIC; — Signal declaration
begin
process(clk)
variable d_temp : STD_LOGIC; — Variable declaration
begin
if rising_edge(clk) then
d_temp := d; — Assign input to variable
q_temp <= d_temp; — Assign variable to signal
end if;
end process;
q <= q_temp; — Continuous assignment to output
end Behavioral;
In this example:
- A D flip-flop is modeled using a process that triggers on the rising edge of the clock.
- A variable (
d_temp
) is used to store the input (d
) temporarily before updating the signal (q_temp
).
- The output (
q
) is continuously assigned the value of the signal (q_temp
).
7.1.5 Simulating a Simple Design
Simulation is an essential step in the VHDL design process, allowing designers to verify the functionality of their digital circuits before implementation. This section outlines the steps to simulate a simple VHDL design, including creating a simple design, writing a testbench, and running the simulation.
1. Creating a Simple VHDL Design
Let’s consider a basic 2-input AND gate as our design example.
VHDL Code for the AND Gate:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity AND_Gate is
Port (
A : in STD_LOGIC;
B : in STD_LOGIC;
Y : out STD_LOGIC
);
end AND_Gate;
architecture Behavioral of AND_Gate is
begin
Y <= A AND B; — Concurrent signal assignment
end Behavioral;
This code defines a simple AND gate with two inputs (A and B) and one output (Y).
2. Writing a Testbench
A testbench is a separate VHDL file used to simulate and test the design. It instantiates the design entity and applies various input stimuli to verify its behavior.
VHDL Code for the Testbench:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity Testbench is
end Testbench;
architecture Behavioral of Testbench is
signal A : STD_LOGIC;
signal B : STD_LOGIC;
signal Y : STD_LOGIC;
— Instantiate the AND gate
component AND_Gate
Port (
A : in STD_LOGIC;
B : in STD_LOGIC;
Y : out STD_LOGIC
);
end component;
begin
UUT: AND_Gate port map (A => A, B => B, Y => Y); — Unit Under Test
— Stimulus process to apply input combinations
stimulus: process
begin
— Test case 1
A <= ‘0’;
B <= ‘0’;
wait for 10 ns; — Wait time for simulation
assert (Y = ‘0’) report “Test Case 1 Failed” severity ERROR;
— Test case 2
A <= ‘0’;
B <= ‘1’;
wait for 10 ns;
assert (Y = ‘0’) report “Test Case 2 Failed” severity ERROR;
— Test case 3
A <= ‘1’;
B <= ‘0’;
wait for 10 ns;
assert (Y = ‘0’) report “Test Case 3 Failed” severity ERROR;
— Test case 4
A <= ‘1’;
B <= ‘1’;
wait for 10 ns;
assert (Y = ‘1’) report “Test Case 4 Failed” severity ERROR;
— End of simulation
wait; — Wait indefinitely to end simulation
end process;
end Behavioral;
In this testbench:
- Signals
A
, B
, and Y
are declared to connect to the inputs and output of the AND gate.
- The
AND_Gate
component is instantiated as UUT
(Unit Under Test).
- A stimulus process applies different input combinations and checks the output using assertions. If the output does not match the expected result, an error message is reported.
3. Running the Simulation
To run the simulation, follow these general steps (assuming you have a VHDL simulation tool like ModelSim or GHDL):
- Compile the Design and Testbench:
- Open the simulation tool.
- Load the VHDL files for both the design (AND gate) and the testbench.
- Compile both files to check for syntax errors.
- Simulate the Testbench:
- Run the testbench simulation. This will execute the stimulus process and check the output of the AND gate against the expected results.
- Observe the simulation log for any assertion failures or error messages.
- Review Simulation Results:
- If all assertions pass, the simulation is successful, indicating that the AND gate functions correctly.
- If any assertions fail, the error messages will help identify which test case did not pass, allowing you to debug the design as necessary.
4. Example Simulation Output
Here’s an example of what you might see in the simulation log:
# Running Test Case 1
# Test Case 1 Passed
# Running Test Case 2
# Test Case 2 Passed
# Running Test Case 3
# Test Case 3 Passed
# Running Test Case 4
# Test Case 4 Passed
If all test cases pass, the output will indicate successful execution. If a test case fails, the log will show the specific test case number that did not pass.
7.1.6 Creating Memory in VHDL
In VHDL, creating memory structures such as RAM (Random Access Memory) and ROM (Read-Only Memory) is essential for storing data in digital designs. This section outlines how to define and use memory in VHDL, focusing on both ROM and RAM implementations.
1. Creating Read-Only Memory (ROM)
ROM is a type of non-volatile memory used to store fixed data that does not change during operation. In VHDL, ROM can be implemented using an array of signals initialized with specific values.
Example of ROM Implementation:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL; — For integer type
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity ROM is
Port (
address : in INTEGER; — Address input
data : out STD_LOGIC_VECTOR(7 downto 0) — 8-bit data output
);
end ROM;
architecture Behavioral of ROM is
type rom_type is array (0 to 15) of STD_LOGIC_VECTOR(7 downto 0); — 16 entries of 8-bit data
constant ROM_CONTENT : rom_type := (
X”00″, X”01″, X”02″, X”03″,
X”04″, X”05″, X”06″, X”07″,
X”08″, X”09″, X”0A”, X”0B”,
X”0C”, X”0D”, X”0E”, X”0F”
); — Initialization of ROM contents
begin
process(address)
begin
data <= ROM_CONTENT(address); — Output the data at the specified address
end process;
end Behavioral;
In this example:
- The ROM entity takes an address as input and outputs 8 bits of data.
- The
ROM_CONTENT
constant is an array initialized with hexadecimal values, simulating the stored data in ROM.
2. Creating Random Access Memory (RAM)
RAM is a type of volatile memory that allows both reading and writing of data. In VHDL, RAM can be created using an array of signals and providing mechanisms to read from and write to this array.
Example of RAM Implementation:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL; — For integer type
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity RAM is
Port (
clk : in STD_LOGIC; — Clock input
we : in STD_LOGIC; — Write enable
address : in INTEGER; — Address input
data_in : in STD_LOGIC_VECTOR(7 downto 0); — Data input
data_out : out STD_LOGIC_VECTOR(7 downto 0) — Data output
);
end RAM;
architecture Behavioral of RAM is
type ram_type is array (0 to 15) of STD_LOGIC_VECTOR(7 downto 0); — 16 entries of 8-bit data
signal RAM_CONTENT : ram_type; — Signal to hold RAM data
begin
process(clk)
begin
if rising_edge(clk) then
if we = ‘1’ then
RAM_CONTENT(address) <= data_in; — Write operation
end if;
data_out <= RAM_CONTENT(address); — Read operation
end if;
end process;
end Behavioral;
In this example:
- The RAM entity includes inputs for the clock (
clk
), write enable (we
), address, and data input (data_in
), as well as an output for the data read from the memory (data_out
).
- The RAM_CONTENT signal acts as the memory array. On the rising edge of the clock, if
we
is high, it writes data_in
to the specified address
. Regardless of the write operation, it also reads from the specified address and updates data_out
.
3. Instantiating and Testing Memory
To test these memory components, you can create a testbench similar to the one used for the AND gate. Here’s a simple testbench for the RAM implementation:
Testbench for RAM:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity RAM_Testbench is
end RAM_Testbench;
architecture Behavioral of RAM_Testbench is
signal clk : STD_LOGIC := ‘0’;
signal we : STD_LOGIC;
signal address : INTEGER;
signal data_in : STD_LOGIC_VECTOR(7 downto 0);
signal data_out : STD_LOGIC_VECTOR(7 downto 0);
— Instantiate the RAM
component RAM
Port (
clk : in STD_LOGIC;
we : in STD_LOGIC;
address : in INTEGER;
data_in : in STD_LOGIC_VECTOR(7 downto 0);
data_out : out STD_LOGIC_VECTOR(7 downto 0)
);
end component;
begin
UUT: RAM port map (clk => clk, we => we, address => address, data_in => data_in, data_out => data_out); — Unit Under Test
— Clock generation
clk_process: process
begin
while true loop
clk <= ‘0’;
wait for 5 ns;
clk <= ‘1’;
wait for 5 ns;
end loop;
end process;
— Test process
stimulus: process
begin
— Write to RAM
we <= ‘1’; — Enable write
address <= 0;
data_in <= X”AB”; — Write data
wait for 10 ns;
address <= 1;
data_in <= X”CD”;
wait for 10 ns;
— Read from RAM
we <= ‘0’; — Disable write
address <= 0; — Read from address 0
wait for 10 ns;
address <= 1; — Read from address 1
wait for 10 ns;
— End of simulation
wait; — Wait indefinitely to end simulation
end process;
end Behavioral;
In this testbench:
- A clock signal is generated.
- The stimulus process writes data to specific addresses and then reads from those addresses to verify the contents
7.1.7 Finite State Machines
Finite State Machines (FSMs) are a fundamental concept in digital design, used to model the behavior of systems with a finite number of states. FSMs can be classified into two types: Mealy machines and Moore machines. In VHDL, FSMs are implemented using processes, signals, and state variables. This section covers the definition of FSMs, their components, and examples of both Mealy and Moore machines.
1. Definition of Finite State Machines (FSMs)
A Finite State Machine is defined by:
- A finite number of states.
- A set of transitions between states based on input conditions.
- Outputs that depend on the current state and/or input (depending on whether it’s a Mealy or Moore machine).
Types of FSMs:
- Mealy Machine: The output depends on the current state and the current input.
- Moore Machine: The output depends only on the current state.
2. Components of FSMs
- States: The various configurations the FSM can be in.
- Inputs: External signals that affect state transitions.
- Outputs: Signals generated based on the current state and input.
- State Transition Table: A table that describes how the FSM transitions from one state to another based on inputs.
3. Example of a Moore Machine
Let’s design a simple 2-bit binary counter using a Moore FSM. The FSM will have four states (00, 01, 10, 11), and it will cycle through these states on each clock cycle.
VHDL Code for the Moore Machine:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity Moore_Counter is
Port (
clk : in STD_LOGIC; — Clock input
reset : in STD_LOGIC; — Reset input
count : out STD_LOGIC_VECTOR(1 downto 0) — 2-bit counter output
);
end Moore_Counter;
architecture Behavioral of Moore_Counter is
type state_type is (S0, S1, S2, S3); — State declaration
signal state, next_state: state_type; — Current and next state signals
begin
— State transition process
process(clk, reset)
begin
if reset = ‘1’ then
state <= S0; — Reset to initial state
elsif rising_edge(clk) then
state <= next_state; — Transition to next state
end if;
end process;
— Next state logic
process(state)
begin
case state is
when S0 =>
next_state <= S1; — Transition to S1
count <= “00”; — Output for state S0
when S1 =>
next_state <= S2; — Transition to S2
count <= “01”; — Output for state S1
when S2 =>
next_state <= S3; — Transition to S3
count <= “10”; — Output for state S2
when S3 =>
next_state <= S0; — Transition back to S0
count <= “11”; — Output for state S3
when others =>
next_state <= S0; — Default case
count <= “00”;
end case;
end process;
end Behavioral;
In this example:
- The FSM has four states representing the 2-bit binary count.
- The
next_state
logic is defined in a separate process, using a case statement to handle state transitions based on the current state.
- The output (
count
) is assigned based on the current state.
4. Example of a Mealy Machine
Now, let’s implement a simple Mealy FSM that detects a specific sequence of inputs (e.g., “110”). The output will be high when the sequence is detected.
VHDL Code for the Mealy Machine:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity Mealy_Sequence_Detector is
Port (
clk : in STD_LOGIC; — Clock input
reset : in STD_LOGIC; — Reset input
input_signal : in STD_LOGIC; — Input signal
output_signal : out STD_LOGIC — Output signal
);
end Mealy_Sequence_Detector;
architecture Behavioral of Mealy_Sequence_Detector is
type state_type is (S0, S1, S2); — State declaration
signal state, next_state: state_type; — Current and next state signals
begin
— State transition process
process(clk, reset)
begin
if reset = ‘1’ then
state <= S0; — Reset to initial state
elsif rising_edge(clk) then
state <= next_state; — Transition to next state
end if;
end process;
— Next state and output logic
process(state, input_signal)
begin
case state is
when S0 =>
if input_signal = ‘1’ then
next_state <= S1; — Transition to S1
else
next_state <= S0;
end if;
output_signal <= ‘0’; — No output
when S1 =>
if input_signal = ‘1’ then
next_state <= S2; — Transition to S2
else
next_state <= S0;
end if;
output_signal <= ‘0’; — No output
when S2 =>
if input_signal = ‘0’ then
next_state <= S0; — Sequence detected
output_signal <= ‘1’; — Output high when sequence is detected
else
next_state <= S1; — Stay in S1 if input is 1
output_signal <= ‘0’; — No output
end if;
when others =>
next_state <= S0; — Default case
output_signal <= ‘0’;
end case;
end process;
end Behavioral;
In this Mealy machine:
- The FSM detects the sequence “110”. The output signal goes high when the sequence is completed.
- The next state and output logic are handled together in a single process, making the output dependent on both the current state and the input signal.
5. Testing FSMs
To test FSMs, you can create a testbench similar to the previous examples. For instance, for the Mealy sequence detector, you would provide various input sequences to check if the output behaves as expected.
Testbench for Mealy Sequence Detector:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity Mealy_Testbench is
end Mealy_Testbench;
architecture Behavioral of Mealy_Testbench is
signal clk : STD_LOGIC := ‘0’;
signal reset : STD_LOGIC;
signal input_signal : STD_LOGIC;
signal output_signal : STD_LOGIC;
— Instantiate the Mealy FSM
component Mealy_Sequence_Detector
Port (
clk : in STD_LOGIC;
reset : in STD_LOGIC;
input_signal : in STD_LOGIC;
output_signal : out STD_LOGIC
);
end component;
begin
UUT: Mealy_Sequence_Detector port map (clk => clk, reset => reset, input_signal => input_signal, output_signal => output_signal); — Unit Under Test
— Clock generation
clk_process: process
begin
while true loop
clk <= ‘0’;
wait for 5 ns;
clk <= ‘1’;
wait for 5 ns;
end loop;
end process;
— Stimulus process
stimulus: process
begin
reset <= ‘1’; — Activate reset
wait for 10 ns;
reset <= ‘0’; — Release reset
— Input sequence: 1, 1, 0
input_signal <= ‘1’; wait for 10 ns;
input_signal <= ‘1’; wait for 10 ns;
input_signal <= ‘0’; wait for 10 ns;
— Input sequence: 1, 1, 0
input_signal <= ‘1’; wait for 10 ns;
input_signal <= ‘1’; wait for 10 ns;
input_signal <= ‘0’; wait for 10 ns;
— End of simulation
wait; — Wait indefinitely to end simulation
end process;
end Behavioral;
7.1.8 Loops and Conditional Elaboration
In VHDL, loops and conditional statements are essential constructs for controlling the flow of execution in a design. They allow you to implement repetitive operations and make decisions based on certain conditions, which is particularly useful for generating complex designs and managing resources efficiently. This section explores the different types of loops, conditional elaboration, and their usage in VHDL.
1. Conditional Statements
Conditional statements in VHDL are used to execute certain parts of code based on specified conditions. The most common conditional statement is the if
statement.
Example of an If Statement:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity ConditionalExample is
Port (
input_signal : in STD_LOGIC;
output_signal : out STD_LOGIC
);
end ConditionalExample;
architecture Behavioral of ConditionalExample is
begin
process(input_signal)
begin
if input_signal = ‘1’ then
output_signal <= ‘0’; — Set output to 0 if input is 1
else
output_signal <= ‘1’; — Set output to 1 otherwise
end if;
end process;
end Behavioral;
In this example:
- The process checks the value of
input_signal
. If it is high (‘1’), it sets output_signal
to low (‘0’); otherwise, it sets it to high (‘1’).
2. Case Statement
The case
statement is another way to perform conditional operations based on the value of a variable or signal. It’s especially useful when there are multiple possible conditions to check.
Example of a Case Statement:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity CaseExample is
Port (
input_signal : in INTEGER;
output_signal : out STD_LOGIC_VECTOR(3 downto 0)
);
end CaseExample;
architecture Behavioral of CaseExample is
begin
process(input_signal)
begin
case input_signal is
when 0 =>
output_signal <= “0000”; — Output for case 0
when 1 =>
output_signal <= “0001”; — Output for case 1
when 2 =>
output_signal <= “0010”; — Output for case 2
when others =>
output_signal <= “1111”; — Default output
end case;
end process;
end Behavioral;
In this example:
- The output depends on the value of
input_signal
. Each possible value has a corresponding output.
3. Loops in VHDL
Loops in VHDL are used to repeat a block of statements multiple times. There are three main types of loops:
- For Loop
- While Loop
- Loop (infinite loop)
3.1 For Loop
The for
loop is used when the number of iterations is known beforehand.
Example of a For Loop:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity ForLoopExample is
Port (
count : in INTEGER;
output_signal : out STD_LOGIC_VECTOR(3 downto 0)
);
end ForLoopExample;
architecture Behavioral of ForLoopExample is
begin
process(count)
variable temp_output : STD_LOGIC_VECTOR(3 downto 0) := “0000”; — Temporary output variable
begin
for i in 0 to count loop
temp_output := temp_output + “0001”; — Increment output for each iteration
end loop;
output_signal <= temp_output; — Assign final value to output
end process;
end Behavioral;
In this example:
- The loop iterates from 0 to
count
, incrementing the temp_output
variable in each iteration.
3.2 While Loop
The while
loop continues as long as a specified condition is true.
Example of a While Loop:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity WhileLoopExample is
Port (
start_value : in INTEGER;
output_signal : out INTEGER
);
end WhileLoopExample;
architecture Behavioral of WhileLoopExample is
begin
process(start_value)
variable counter : INTEGER := start_value; — Initialize counter variable
begin
while counter < 10 loop
counter := counter + 1; — Increment counter
end loop;
output_signal <= counter; — Assign final value to output
end process;
end Behavioral;
In this example:
- The loop increments the
counter
variable until it reaches 10.
3.3 Loop (Infinite Loop)
The generic loop
construct is used when the number of iterations is not predetermined.
Example of an Infinite Loop:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity InfiniteLoopExample is
Port (
output_signal : out STD_LOGIC
);
end InfiniteLoopExample;
architecture Behavioral of InfiniteLoopExample is
begin
process
begin
output_signal <= ‘1’; — Set output to 1
wait; — Wait indefinitely (infinite loop)
end process;
end Behavioral;
In this example:
- The process sets the output to ‘1’ and then waits indefinitely.
4. Conditional Looping
You can also combine loops with conditional statements to create complex behavior.
Example of a Conditional Loop:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity ConditionalLoopExample is
Port (
input_signal : in INTEGER;
output_signal : out INTEGER
);
end ConditionalLoopExample;
architecture Behavioral of ConditionalLoopExample is
begin
process(input_signal)
variable result : INTEGER := 0; — Initialize result variable
begin
for i in 1 to input_signal loop
if (i mod 2) = 0 then — Check if i is even
result := result + i; — Add even numbers to result
end if;
end loop;
output_signal <= result; — Assign final result to output
end process;
end Behavioral;
In this example:
- The loop iterates through a range and adds only even numbers to the
result
variable, demonstrating how loops and conditionals can be combined.
7.1.9 Attributes in VHDL
Attributes in VHDL provide additional information about objects such as signals, variables, types, and entities. They are useful for obtaining properties of these objects at runtime or during compilation. Attributes can be applied to various elements in VHDL, including data types, signals, and more, allowing for enhanced functionality and flexibility in design. This section will cover the types of attributes available in VHDL, their usage, and examples to illustrate their application.
1. Types of Attributes
VHDL defines several standard attributes that can be used with various types of objects. Some common categories include:
- Attributes of Data Types: These attributes provide information about types, such as their size.
- Attributes of Signals: These attributes give information about signal characteristics, such as whether a signal is stable or has a certain value.
- Attributes of Variables: These attributes help in retrieving information about variable states and properties.
- Attributes of Types: These attributes allow you to query properties of defined types.
2. Commonly Used Attributes
Here are some commonly used attributes in VHDL:
'length
: Returns the length of an array or string type.
'high
: Returns the highest index of an array.
'low
: Returns the lowest index of an array.
'event
: Indicates if a signal has changed.
'stable
: Checks if a signal has remained stable for a specified duration.
'last
: Used with ranges to return the last value.
Example:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity AttributeExample is
Port (
data : in STD_LOGIC_VECTOR(3 downto 0);
length_output : out INTEGER;
high_index : out INTEGER;
low_index : out INTEGER
);
end AttributeExample;
architecture Behavioral of AttributeExample is
begin
process(data)
begin
length_output <= data’length; — Get length of the vector
high_index <= data’high; — Get high index of the vector
low_index <= data’low; — Get low index of the vector
end process;
end Behavioral;
In this example:
- The length, high index, and low index of the
data
signal are computed and assigned to the respective outputs.
3. Signal Attributes
Signal attributes can provide valuable information about signal states and behaviors:
signal_name'event
: Indicates whether the signal has changed.
signal_name'stable
: Indicates whether the signal has been stable (unchanged) for a given time.
Example:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity SignalAttributeExample is
Port (
clk : in STD_LOGIC;
data : in STD_LOGIC;
event_detected : out BOOLEAN
);
end SignalAttributeExample;
architecture Behavioral of SignalAttributeExample is
begin
process(clk)
begin
if rising_edge(clk) then
event_detected <= data’event; — Check if data signal changed
end if;
end process;
end Behavioral;
In this example:
- The process checks if the
data
signal has changed during the rising edge of the clk
signal.
4. Type Attributes
You can also define attributes for user-defined types, which can be useful for more complex designs:
Example:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity SignalAttributeExample is
Port (
clk : in STD_LOGIC;
data : in STD_LOGIC;
event_detected : out BOOLEAN
);
end SignalAttributeExample;
architecture Behavioral of SignalAttributeExample is
begin
process(clk)
begin
if rising_edge(clk) then
event_detected <= data’event; — Check if data signal changed
end if;
end process;
end Behavioral;
In this example:
- The length of a user-defined array type (
MyArray
) is calculated.
5. Using Attributes in Generics and Configuration
Attributes can also be used in generics and configuration, which allows you to write more flexible and reusable code.
Example:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity GenericAttributeExample is
generic (
SIZE : INTEGER := 4
);
Port (
data : in STD_LOGIC_VECTOR(SIZE-1 downto 0);
output_size : out INTEGER
);
end GenericAttributeExample;
architecture Behavioral of GenericAttributeExample is
begin
process(data)
begin
output_size <= SIZE; — Using generic size
end process;
end Behavioral;
In this example:
- A generic size is defined, and it is used within the architecture.
7.1.10Functions and Procedures
In VHDL, functions and procedures are subprograms that allow for modular programming and code reuse. They help to encapsulate behavior and enable the implementation of complex designs in a structured manner. Understanding the differences between functions and procedures, as well as their syntax and usage, is essential for efficient VHDL programming.
1. Overview of Functions and Procedures
- Functions: A function is a subprogram that computes a value and returns it. It must return a single value and can be used in expressions. Functions are primarily used for computations.
- Procedures: A procedure is a subprogram that performs a specific task but does not return a value. Instead, procedures can have input and output parameters. They are often used for tasks that involve multiple operations or side effects.
2. Syntax and Declaration
2.1 Function Declaration
The syntax for declaring a function is as follows:
function function_name(parameter_list) return return_type is
— Declarations (variables, types, etc.)
begin
— Function body
return value; — Returning the computed value
end function_name;
Example of a Function:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
— Function to add two integers
function add(a : INTEGER; b : INTEGER) return INTEGER is
begin
return a + b; — Return the sum of a and b
end add;
entity FunctionExample is
Port (
input_a : in INTEGER;
input_b : in INTEGER;
output_sum : out INTEGER
);
end FunctionExample;
architecture Behavioral of FunctionExample is
begin
process(input_a, input_b)
begin
output_sum <= add(input_a, input_b); — Call the function
end process;
end Behavioral;
In this example:
- The
add
function takes two integer inputs and returns their sum. It is called in a process to compute the output.
2.2 Procedure Declaration
The syntax for declaring a procedure is as follows:
procedure procedure_name(parameter_list) is
— Declarations (variables, types, etc.)
begin
— Procedure body
end procedure_name;
Example of a Procedure:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
— Procedure to swap two integer values
procedure swap(signal a : inout INTEGER; signal b : inout INTEGER) is
variable temp : INTEGER;
begin
temp := a; — Store the value of a in temp
a := b; — Assign the value of b to a
b := temp; — Assign the stored value to b
end swap;
entity ProcedureExample is
Port (
input_a : inout INTEGER;
input_b : inout INTEGER
);
end ProcedureExample;
architecture Behavioral of ProcedureExample is
begin
process(input_a, input_b)
begin
swap(input_a, input_b); — Call the procedure
end process;
end Behavioral;
In this example:
- The
swap
procedure takes two integer signals as input and swaps their values using a temporary variable.
3. Key Differences Between Functions and Procedures
Aspect |
Function |
Procedure |
Return Value |
Returns a single value |
Does not return a value |
Usage |
Can be used in expressions |
Cannot be used in expressions |
Parameters |
Can take input parameters only |
Can take input and output parameters |
Encapsulation |
Generally used for calculations |
Generally used for tasks with side effects |
4. Using Functions and Procedures
Functions and procedures can be used to encapsulate complex logic or repetitive tasks, improving code readability and maintainability. They can also be organized within packages for reuse across multiple design units.
Example of Using Functions and Procedures in a Package:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
package MathOperations is
function add(a : INTEGER; b : INTEGER) return INTEGER;
procedure swap(signal a : inout INTEGER; signal b : inout INTEGER);
end MathOperations;
package body MathOperations is
function add(a : INTEGER; b : INTEGER) return INTEGER is
begin
return a + b;
end add;
procedure swap(signal a : inout INTEGER; signal b : inout INTEGER) is
variable temp : INTEGER;
begin
temp := a;
a := b;
b := temp;
end swap;
end MathOperations;
entity PackageExample is
Port (
input_a : inout INTEGER;
input_b : inout INTEGER;
output_sum : out INTEGER
);
end PackageExample;
architecture Behavioral of PackageExample is
use work.MathOperations.all; — Use the package
begin
process(input_a, input_b)
begin
output_sum <= add(input_a, input_b); — Call the function
swap(input_a, input_b); — Call the procedure
end process;
end Behavioral;
In this example:
- A package
MathOperations
contains a function add
and a procedure swap
. These can be used in any design unit that imports the package.