# Digital Sistem Design (PSD/DSG)

# Module 1 - Setup

For Digital Sistem Design (DSG) or Perancangan Sistem Digital (PSD) Practicum, students **must** <span>have already installed between </span>**Vivado**<span> only or </span>**ModelSim + Quartus Prime**

# Vivado Installation Tutorial

## 1.1 Vivado Explanation

**Vivado is an Integrated Design Environment (IDE) developed by Xilinx (now AMD) used for designing, simulating, and implementing digital circuits on FPGAs (Field-Programmable Gate Arrays).** It serves as the primary software tool to take a VHDL hardware description and turn it into a functional circuit on a physical chip. Vivado itself is a complete, integrated workshop for Xilinx FPGAs. It has all the tools you need (design, simulation, implementation) under one roof.

#### <span style="color: rgb(224, 62, 45);">**Before choosing Vivado be cautious that Vivado requires atleast 60gb of storage and a demanding CPU performance.**</span>

## 1.2 Vivado Installation

To install Vivado, please follow this link and proceed to do the next procedure until finished.

[Vivado Download Link](https://learn.digilabdte.com/bit.ly/VivadoPSD-New)

#### Step 1 : Download the Vivado Installer

Go into Vivado Archive and choose version 2022.2.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/image.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/image.png)

Then Scroll and choose Windows version and then <span style="color: rgb(191, 237, 210);">click the link</span>

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/HJlimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/HJlimage.png)

The link will take you into a login page, you may create a new account or if you already had one you can just log into your AMD account.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/W0bimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/W0bimage.png)

The page will take you into the download center where you will fill out your information, you may just fill up <span style="color: rgb(224, 62, 45);">only the required</span> part to download the installer. After filling all your information, you can just download the installer on the bottom of the page.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/qCzimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/qCzimage.png)

#### Step 2 : Install the Vivado 

Run the installation file

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/boMimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/boMimage.png)

Proceed to log into the same account you've just logged into/created before in **step 1** and then just choose Download and install now and proceed into the next step

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/248image.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/248image.png)

Choose Vivado and then proceed

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/Ymaimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/Ymaimage.png)

Choose Vivado ML Standard and proceed

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/TyVimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/TyVimage.png)

Just Check every box like in the screenshot below and make sure to have enough disk space required on the bottom left and then ou may proceed.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/xYRimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/xYRimage.png)

Just check all the agree box and you may proceed

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/J0oimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/J0oimage.png)

Choose where you want Vivado to be installed and then you may proceed to the installation part

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/H3iimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/H3iimage.png)

Wait until the installation is completed and then you may check in xilinx information center if the Vivado installation had completed. Make sure to check if the Version you've installed is correct.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/0Llimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/0Llimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/0KDimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/0KDimage.png)

You may open Vivado and voila you've installed Vivado

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/4aoimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/4aoimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/BfLimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/BfLimage.png)

# Vivado Simulation and Synthesis Tutorial

## 1.3 Vivado Tutorial

For this tutorial, we will use this code for reference :

```
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;
END Behavioral;
```

#### 1.3.1 Creating a new Vivado file

Create a new project

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/7T6image.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/7T6image.png)

Enter your project name and where you want it to be saved

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/UdMimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/UdMimage.png)

Choose RTL Project

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/HMUimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/HMUimage.png)

Change the target language into VHDL and add your VHDL code into the project

[![qHiimage.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/qhiimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/qhiimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/dGCimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/dGCimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/6drimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/6drimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/Raaimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/Raaimage.png)

You may skip the add constraints page and also the default part proceed into the project creation. Finally you've created a new project and this will be your screen now.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/ZVZimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/ZVZimage.png)

#### 1.3.2 Simulation Tutorial

Click "Run Simulation" on the left part of the screen. And choose "Run Behavorial Simulation"

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/Bbzimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/Bbzimage.png)

If there's any error warning, you may read and fix the error before proceeding into the simulation.

This will be your screen after you run the simulation.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/8UEimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/8UEimage.png)

To add a signal, you may change the value in the objects part, choose "Force Constant" and change according to what you want to do. Remember to change the **<span style="color: rgb(53, 152, 219);">INPUT </span>**not the **<span style="color: rgb(224, 62, 45);">OUTPUT</span>**

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/JxNimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/JxNimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/EtZimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/EtZimage.png)

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/cr9image.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/cr9image.png)

After changing the Value you may click the "Run for 10ns" on the top bar

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/YZiimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/YZiimage.png)

You may see that there's a new signal after you press the button

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/RzQimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/RzQimage.png)

You may also move the yellow line with your cursor to switch to a different period of time on the waveform

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/Snoimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/Snoimage.png)

##### NOTE : All of this is just a manual simulation tutorial. There are a way to do this automatically (Hint: Module 4).

To close simulation, you may click the top right button

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/LbFimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/LbFimage.png)

#### 1.3.3 Synthesis Tutorial

Go to the "RTL Analysis" and run "Schematic" and if there's a notification just select "ok"

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/ieqimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/ieqimage.png)

Wait until the elaborated design is finished and then you may see your VHDL code schematic.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/CJmimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/CJmimage.png)

# Quartus Prime Installation Tutorial

## 1.1 Quartus Prime Explanation

Intel Quartus Prime is a comprehensive software suite from Intel used for designing, synthesizing, and programming programmable logic devices (PLDs), such as Field-Programmable Gate Arrays (FPGAs) and Complex Programmable Logic Devices (CPLDs). The software provides a complete integrated development environment (IDE) for digital circuit engineers and designers.

## 1.2 Quartus Prime Installation

To install Quartus Prime, please follow this link and proceed to do the next procedure until finished.

[Quartus Prime Download Link](https://www.intel.com/content/www/us/en/software-kit/736572/intel-quartus-prime-lite-edition-design-software-version-21-1-1-for-windows.html)

#### Step 1 : Download the Quartus Prime Installer

Go into Multiple Download and then download the Intel Quartus Prime installer :

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/wEBimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/wEBimage.png)

The link will take you into an software license agreement, just accept the terms and proceed with the download.

#### Step 2 : Install the Quartus Prime

Extract the zip and then Run the installation file setup.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/574image.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/574image.png)

If the installer ask you to accept the agreement just accept and proceed with the next part. And then choose the directory for where you want the Quartus Prime to be installed.

#### NOTE : <span style="color: rgb(224, 62, 45);">Do not use space</span> in the folder naming.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/1oGimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/1oGimage.png)

Check every components and proceed with the installation.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/9uPimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/9uPimage.png)

Wait until the installation is finished and then voila you've installed Quartus Prime. To run it just choose to run the Quartus Prime software.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/GO4image.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/GO4image.png)

# Quartus Prime Synthesis Tutorial

## 1.3 Quartus Prime Tutorial

For this tutorial, we will use this code for reference :

```
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;
END Behavioral;
```

#### 1.3.1 Creating a New Quartus Prime Project

Create a new project by clicking new on file tab

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/U4Bimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/U4Bimage.png)

Select New Quartus Prime Project

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/1eTimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/1eTimage.png)

Select the directory where you want the project to be saved and also give the project a name and name your Top-Level Entity

#### NOTE : Remember to name your Top-Level Entity into the same name as your Top-Level entity on your .vhdl

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/BxZimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/BxZimage.png)

Choose empty project

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/mCzimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/mCzimage.png)

Add your .vhdl/.vhd file into the project

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/6zAimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/6zAimage.png)

Just select the default setting and proceed to finish

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/8CXimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/8CXimage.png)

#### 1.3.2 Synthesis Tutorial

Run the "Start Compilation" button on top bar

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/QUUimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/QUUimage.png)

Wait until the startup is finish and then go into Tools -&gt; Netlist Viewers -&gt; RTL Viewer

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/Zkrimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/Zkrimage.png)

You may see your VHDL schematic

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/hsmimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/hsmimage.png)

# ModelSim Installation Tutorial



# ModelSim Simulation Tutorial

# Module 2 - Dataflow Style

# 1. Introduction to VHDL

## 1.1 What is VHDL

VHDL is an acronym for VHSIC HDL or, more completely, **Very High-Speed Integrated Circuit Hardware Description Language**. VHDL is a language used to describe hardware, so its writing style cannot be equated with high/low-level programming languages. A VHDL model or program can be translated into an actual digital circuit quickly with the help of software and, of course, according to specific needs; this process is known as **synthesis**. The resulting circuit can also be tested using VHDL to ensure it works according to the user's requirements. Before proceeding to the main material, readers are reminded to understand the concepts of Basic Digital Circuits (DSD) for ease in describing and designing hardware.

## 1.2 VHDL Syntax

Because VHDL is a language used to describe hardware, in addition to the correct output, tidy program writing is also needed to make it easier to understand. The following are VHDL writing conventions that need to be observed:

- **Case Sensitivity** : VHDL is not case-sensitive, meaning that both uppercase and lowercase letters are recognized as the same object.
- **White Space** : VHDL is not sensitive to white space, meaning that creating space using either a tab or a space bar has the same meaning.
- **Comments** : Like programming languages in general, VHDL also has comments, which are made by using the `--` sign.
- **Parentheses** : In VHDL, there are open and close parentheses `()` which are used for precedence, giving a higher priority to the statement within them.
- **VHDL Statements** : Statements in VHDL always end with a `;` or semi-colon.
- **If, case, and loop Statements**
    
    
    - Every `if` statement is followed by a `then` component.
    - Every `if` statement ends with `end if;`.
    - `Else if` in VHDL is written as `elsif`.
    - Every `case` statement ends with `end case;`.
    - Every `loop` statement is terminated with `end loop;`.
- **Identifiers/Variables** : Identifiers or variables in VHDL can use a combination of letters (A-Z and a-z) and numbers (0-9), must not end with an `_`, and can have an unlimited length. It is important to name variables according to their function so they are easy to understand.

## 1.3 VHDL Operator

Operators in VHDL are grouped into 7 types: logical, relational, shift, adding, sign, multiplying, and others. The order of this list also describes the precedence of the operators. The following is a complete description of these operators:

- Logical

<table border="1" id="bkmrk-operator-type-logica"><colgroup><col></col><col></col><col></col><col></col><col></col><col></col><col></col></colgroup><thead><tr><td>Operator Type</td><td>  
</td><td>  
</td><td>  
</td><td>  
</td><td>  
</td><td>  
</td></tr></thead><tbody><tr><td>Logical</td><td>and</td><td>or</td><td>nand</td><td>nor</td><td>xor</td><td>xnor</td></tr></tbody></table>

- Relational

<table border="1" id="bkmrk-operator-name-explan"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><td>Operator</td><td>Name</td><td>Explanation</td></tr><tr><td>`A = B`</td><td>equivalence</td><td>is A equivalent to B?</td></tr><tr><td>`A /= B`</td><td>non-equivalence</td><td>is A not equivalent to B?</td></tr><tr><td>`A < B`</td><td>less than</td><td>is A less than B?</td></tr><tr><td>`A <= B`</td><td>less than or equal</td><td>is A less than or equal to B?</td></tr><tr><td>`A > B`</td><td>greater than</td><td>is A greater than B?</td></tr><tr><td>`A >= B`</td><td>greater than or equal</td><td>is A greater than or equal to B?</td></tr></tbody></table>

- Shift

<table border="1" id="bkmrk-%C2%A0-operator-name-exam"><colgroup><col></col><col></col><col></col><col></col><col></col></colgroup><tbody><tr><td class="align-center"> </td><td class="align-center">Operator</td><td class="align-center">Name</td><td class="align-center">Example</td><td class="align-center">Result</td></tr><tr><td class="align-center">logical</td><td class="align-center">sll</td><td class="align-center">shift left logical</td><td class="align-center">result &lt;= "10010101" sll 2</td><td class="align-center">"01010100"</td></tr><tr><td class="align-center"> </td><td class="align-center">srl</td><td class="align-center">shift right logical</td><td class="align-center">result &lt;= "10010101" srl 3</td><td class="align-center">"00010010"</td></tr><tr><td class="align-center">arithmetic</td><td class="align-center">sla</td><td class="align-center">shift left arithmetic</td><td class="align-center">result &lt;= "10010101" sla 3</td><td class="align-center">"10101111"</td></tr><tr><td class="align-center"> </td><td class="align-center">sra</td><td class="align-center">shift right arithmetic</td><td class="align-center">result &lt;= "10010101" sra 2</td><td class="align-center">"11100101"</td></tr><tr><td class="align-center">rotate</td><td class="align-center">rol</td><td class="align-center">rotate left</td><td class="align-center">result &lt;= "10100011" rol 2</td><td class="align-center">"10001110"</td></tr><tr><td class="align-center"> </td><td class="align-center">ror</td><td class="align-center">rotate right</td><td class="align-center">result &lt;= "10100011" ror 2</td><td class="align-center">"11101000"</td></tr></tbody></table>

- Arithmetic

<table border="1" id="bkmrk-%C2%A0-operator-name-comm"><colgroup><col></col><col></col><col></col><col></col></colgroup><tbody><tr><td class="align-center"> </td><td class="align-center">Operator</td><td class="align-center">Name</td><td class="align-center">Comment</td></tr><tr><td class="align-center">adding</td><td class="align-center">+</td><td class="align-center">addition</td><td class="align-center"> </td></tr><tr><td class="align-center"> </td><td class="align-center">-</td><td class="align-center">subtraction</td><td class="align-center"> </td></tr><tr><td class="align-center"> </td><td class="align-center">&amp;</td><td class="align-center">concatenation</td><td class="align-center"><div><div>can operate only on specific types</div></div></td></tr><tr><td class="align-center">sign</td><td class="align-center">+</td><td class="align-center">identity</td><td class="align-center">unary operator</td></tr><tr><td class="align-center"> </td><td class="align-center">-</td><td class="align-center">negation</td><td class="align-center">unary operator</td></tr><tr><td class="align-center">multiplying</td><td class="align-center">\*</td><td class="align-center">multiplication</td><td class="align-center"> </td></tr><tr><td class="align-center"> </td><td class="align-center">/</td><td class="align-center">division</td><td class="align-center"><div><div>often limited to powers of two</div></div></td></tr><tr><td class="align-center"> </td><td class="align-center">mod</td><td class="align-center">modulus</td><td class="align-center"><div><div>can operate only on specific types</div></div></td></tr><tr><td class="align-center"> </td><td class="align-center">rem</td><td class="align-center">remainder</td><td class="align-center"><div><div>can operate only on specific types</div></div></td></tr><tr><td class="align-center">miscellaneous</td><td class="align-center">\*\*</td><td class="align-center">exponentiation</td><td class="align-center"><div><div>often limited to powers of two</div></div></td></tr><tr><td class="align-center"> </td><td class="align-center">abs</td><td class="align-center">absolute value</td><td class="align-center"> </td></tr></tbody></table>

## 1.4 Design Units

There are two important parts when designing a digital circuit using VHDL: "**entity**" and "**architecture**." These two units form a hierarchical design consisting of a black box and the components within it.

- **Entity :** The entity is the first part of a VHDL design. It is the highest-level specification of a component or module in the design. In the entity, we define the component's external interface, including its inputs and outputs. The entity is the "black box" that describes what the component does and how it is accessed from the outside. The entity also defines the name of the component and the data types used.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/Dlyimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/Dlyimage.png)

```
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- The ENTITY describes the "black box" interface.
-- It defines the input and output ports.
entity Logic_Circuit is
    port (
        A, B, C : in  STD_LOGIC;  -- The three input pins
        Y       : out STD_LOGIC   -- The single output pin
    );
end Logic_Circuit;
```

- **Architecture :** The architecture is the second part of a VHDL design. This section describes how the component works internally, including how the signals defined in the entity are processed and connected within that component.

[![image.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/mrbimage.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/mrbimage.png)

```
-- The ARCHITECTURE describes the internal logic.
-- It explains HOW the inputs are used to create the output.
architecture Dataflow of Logic_Circuit is
begin
    -- This single line of code represents the AND gate followed by the OR gate.
    Y <= (A and B) or C;
end Dataflow;
```

## 1.5 Data Objects

In VHDL, there are 4 types of Data Objects: **signals**, **variables**, **constants**, and **files**. The way to declare a data object for all types is more or less the same, as follows:

<table border="1" id="bkmrk-vhdl-data-object-dec"><colgroup><col></col><col></col></colgroup><tbody><tr><td>VHDL data object</td><td>Declaration form</td></tr><tr><td>**Signal**</td><td>`signal sig_name : sig_type:=initial_value;`</td></tr><tr><td>**Variable**</td><td>`variable var_name : var_type:=initial_value;`</td></tr><tr><td>**Constant**</td><td>`constant const_name : const_type:=initial_value;`</td></tr></tbody></table>

### 1.5.1 VHDL Signals

A **signal** in VHDL is the primary way to model a physical **wire** or connection in a hardware design. Declared within an architecture, its main purpose is to communicate data between different concurrent components, such as processes or logic gates. A signal is assigned a value using the `<=` operator, and this assignment is **not immediate.** it is scheduled to occur at a specific future time, which accurately reflects the signal propagation delay found in real-world circuits. This delayed behavior is essential for correctly modeling how different parts of a hardware design interact with each other. Signals is also only Declared in the declarative part of an **`architecture`** or `package`. There are two ways to declare signals :

##### Port Signal

A **port signal** is declared in the **entity** section of a VHDL design and represents an external connection. Its purpose is to define how the module sends and receives data from the outside world. Think of ports as the plugs and sockets on an appliance—they are the only way to interact with what's inside. You must specify a direction (or mode) for each port, such as `in`, `out`, `inout`, or `buffer`.

```
-- Port signals are declared inside the ENTITY
entity D_Flip_Flop is
    port (
        d, clk, rst : in  STD_LOGIC;  -- Input ports
        q           : out STD_LOGIC   -- Output port
    );
end D_Flip_Flop;

architecture Behavioral of D_Flip_Flop is
-- ... logic using the ports ...
end Behavioral;
```

##### Intermediate Signal

An **intermediate signal** is declared in the declarative part of the **architecture** and acts as an internal wire within your design. It is not visible from outside the module. Intermediate signals are essential for connecting different internal processes or concurrent statements, breaking down complex logic into simpler steps, or holding a value that needs to be used in multiple places within the architecture.

```
entity Complex_Gate is
    port (
        a, b, c, d : in  STD_LOGIC;
        y          : out STD_LOGIC
    );
end Complex_Gate;

-- Intermediate signal is declared inside the ARCHITECTURE
architecture Dataflow of Complex_Gate is
    -- This signal is an internal "wire" to hold a temporary result
    signal and_result_1 : STD_LOGIC;
begin
    -- The intermediate signal connects the output of the first AND gate
    -- to the input of the OR gate.
    and_result_1 <= a and b;
    y <= and_result_1 or (c and d);
end Dataflow;
```

### 1.5.2 VHDL Variables

A **variable** acts as temporary, local **storage** for calculations and does not represent a physical wire. It can only be declared and used inside a sequential block, such as a `process` (this will be explained more in module 3 : Behavioral Style) . The key distinction of a variable is its **immediate** update behavior. when a value is assigned using the `:=` operator, the variable changes instantly and can be used in the very next line of code with its new value. This makes variables ideal for complex, multi-step algorithms where you need to store intermediate results without the delay and hardware overhead associated with a signal. Variables is also only Declared only inside a `process`, `function`, or `procedure`. It cannot be used to connect different processes.

```
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Entity defines the inputs and outputs
entity Bit_Counter is
    port (
        data_in  : in  STD_LOGIC_VECTOR(7 downto 0); -- 8-bit input bus
        count_out: out INTEGER range 0 to 8          -- The final count
    );
end Bit_Counter;

-- Architecture shows the internal logic
architecture Behavioral of Bit_Counter is
begin
    -- A process is needed to use a variable
    count_process: process(data_in)
        -- 1. The variable is declared here, inside the process.
        -- It is initialized to 0 every time the process runs.
        variable bit_count : INTEGER := 0;
    begin
        -- Loop through each bit of the input signal
        for i in data_in'range loop
            if data_in(i) = '1' then
                -- 2. The variable is updated IMMEDIATELY.
                bit_count := bit_count + 1;
            end if;
        end loop;
        
        -- 3. The final result is assigned to the output signal.
        count_out <= bit_count;
        
    end process count_process;
end Behavioral;
```

### 1.5.3 VHDL Constant

A **constant** in VHDL is a data object that holds a fixed value which cannot be changed after it is declared using the `constant NAME : TYPE := VALUE;` syntax. Its primary purpose is to improve code **readability** and **maintainability** by assigning a descriptive name to a value, like using `DATA_WIDTH` instead of the number `8`. This practice makes a design much easier to update, as changing the constant's value in its single declaration will automatically apply that change everywhere it's used, effectively acting like a labeled, unchangeable setting for your entire project. Constant can be declared in various places like a `package`, `entity`, `architecture`, or `process`.

```
-- First, define the constants in a package
package My_Design_Package is
    constant DATA_WIDTH : integer := 8;
    constant CLK_FREQ_HZ: integer := 50_000_000; -- 50 MHz
end package My_Design_Package;

-- Then, use the package in your design
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use work.My_Design_Package.all; -- Makes our constants visible

entity My_Register_File is
    port (
        clk      : in  std_logic;
        data_in  : in  std_logic_vector(DATA_WIDTH - 1 downto 0); -- Uses the constant
        data_out : out std_logic_vector(DATA_WIDTH - 1 downto 0)  -- Uses the constant
    );
end My_Register_File;
```

### 1.5.5 Standard Data Types

In VHDL (VHSIC Hardware Description Language), there are various data types used to define the properties and types of variables, signals, and other objects in a hardware design. Here are some common data types in VHDL:

- **Signed :** This data type is used to represent integers with a sign (signed integer). It is suitable for representing negative and positive numbers in hardware design.

```
-- Declaration
signal setpoint_signed    : SIGNED(7 downto 0);
signal feedback_signed    : SIGNED(7 downto 0);
signal error_value_signed : SIGNED(7 downto 0);

--  Usage Example: Calculate the difference between two signed values
error_value_signed <= setpoint_signed - feedback_signed;
```

- **Unsigned :** This data type is similar to "Signed," but it is only used to represent non-negative integers (unsigned integers).

```
-- Declaration
signal program_counter : UNSIGNED(15 downto 0);

-- Usage Example: Increment the counter in a clocked process
if rising_edge(clk) then
    program_counter <= program_counter + 1;
end if;
```

- **STD\_LOGIC :** This data type is used to represent a single logic signal that can have the values '0', '1', 'Z' (high impedance), 'U' (uninitialized), 'X' (don't care), 'W' (weak), or 'L' (weak low).

```
-- Declaration
signal clk   : STD_LOGIC;
signal rst   : STD_LOGIC;
signal q_out : STD_LOGIC;

-- Usage Example: Using a reset signal to clear a register
if rst = '1' then
    q_out <= '0';
elsif rising_edge(clk) then
    -- ... other logic ...
end if;
```

- **STD\_LOGIC\_VECTOR :** This is a data type used to represent a vector of STD\_LOGIC signals. You can use this type to represent a bus or a collection of logic signals in a hardware design (multiple bits).

```
-- Declaration
signal control_register : STD_LOGIC_VECTOR(7 downto 0);

-- Usage Example: Assigning a hexadecimal value to a control register
control_register <= x"A5"; -- Assigns the bit pattern "10100101"
```

- **Integer :** This data type is used to represent whole numbers, positive or negative. It is often used for counting and managing numerical values in a design.

```
-- Usage within a process
process(clk)
    -- Declaration (variable declared inside a process)
    variable i : INTEGER;
begin
    -- Usage Example: Controlling a loop a specific number of times
    for i in 0 to 15 loop
        -- ... perform an operation ...
    end loop;
end process;
```

- **Boolean :** This data type has two values, "True" or "False." It is used for conditioning and logical expressions.

```
-- Declaration
signal fifo_is_not_empty : BOOLEAN;
signal read_enable       : STD_LOGIC;

-- Usage Example: Using a boolean flag to control an operation
if fifo_is_not_empty = True then
    read_enable <= '1';
else
    read_enable <= '0';
end if;
```

## 1.6 VHDL Architecture Models

In VHDL, there are several approaches to explaining or describing an **architecture**. Hardware can be described using these styles or models according to its needs and complexity. These approaches are divided into three types:

- **Data-flow style :** The **data-flow** approach describes a circuit by showing the relationship between the inputs and outputs of the components in the VHDL language. **Concurrent signal assignment**, **conditional signal assignment**, and **selected signal assignment** are the statements used in the data-flow style.
- **Behavioral style :** The **behavioral style** approach doesn't describe how the circuit is implemented when synthesized. Instead, the behavioral style models how the circuit's **output reacts to its inputs**. The main component of the behavioral style is the **process statement**.
- **Structural style :** The **structural style** approach is essentially a method that supports the **interconnection of black boxes or entities**. This style enables modular design, allowing you to connect previously separate components into a single circuit or entity. The structural style is commonly used when a circuit becomes increasingly complex, as it simplifies the description process.

# 2. Dataflow Style in VHDL

## 2.1 What is Dataflow Style

The Dataflow style is built on concurrency because the **central idea is to model the system as a set of concurrent operations on signals**, which directly reflects the physical reality of hardware. In any integrated circuit, all components are active and processing data in parallel; they don't execute in a step-by-step sequence. To accurately describe this, VHDL uses concurrent statements as the foundation. This approach ensures that the model's behavior matches the hardware's true, simultaneous nature, where multiple data transformations happen at the same time.

This is why **the Dataflow style describes a circuit by focusing on the flow of data and the transformations applied to it, rather than specifying the exact gate-level structure.** Each concurrent statement you write whether it's a simple logical operation or a conditional assignment defines one of these parallel transformations. This method allows you to build a functional description of the circuit by defining the paths and changes that signals undergo. By focusing on this "flow," you let the synthesis tool determine the best gate-level implementation, making it an intuitive and powerful way to translate a high-level circuit diagram into code.

## 2.2 Concurrent Statement

In general, digital circuits are concurrent in nature (working in parallel or simultaneously). A change in the output of a concurrent circuit is directly influenced by a change in an input. Therefore, VHDL also adopts the concept of concurrency when processing the inputs and outputs of a circuit. A program in VHDL cannot be equated to software programming where instructions are executed sequentially. Instructions in VHDL are executed directly and simultaneously. However, with certain techniques, VHDL can also describe sequential circuits whose processes are executed in order.

A concurrent statement is a method used to describe the parallel operation of hardware in VHDL. There are four ways to describe a concurrent statement:

- **Concurrent signal assignment :** This is the most common method used to describe a concurrent statement. The output of the circuit can change at any time when one of its inputs changes. Example description of a 3-input NAND and AND gate using concurrent signal assignment:

```
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Entity defines the inputs and outputs (the "black box")
entity Logic_Gates is
    port (
        a, b, c : in  STD_LOGIC;          -- Three inputs
        y_and   : out STD_LOGIC;          -- Output for the AND gate
        y_nand  : out STD_LOGIC           -- Output for the NAND gate
    );
end Logic_Gates;

-- Architecture describes what happens inside the box
architecture Behavioral of Logic_Gates is
begin
    -- These two statements are CONCURRENT. They happen at the same time.
    y_and  <= a and b and c;
    y_nand <= a nand b and c;

end Behavioral;
```

- **Conditional signal assignment :** This method is used to describe a statement that has a single target signal but has more than one condition to evaluate. Example description of a 2-to-1 Mux using conditional signal assignment:

```
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Entity for a 2-to-1 Multiplexer
entity Mux_2_to_1_Conditional is
    port (
        i0, i1 : in  STD_LOGIC;  -- The two data inputs
        sel    : in  STD_LOGIC;  -- The select line
        y      : out STD_LOGIC   -- The single output
    );
end Mux_2_to_1_Conditional;

-- Architecture using the "when/else" concurrent statement
architecture Behavioral of Mux_2_to_1_Conditional is
begin
    -- The output 'y' gets the value of 'i0' WHEN 'sel' is '0',
    -- ELSE it gets the value of 'i1'.
    y <= i0 when sel = '0' else i1;

end Behavioral;
```

- **Selected signal assignment :** This method differs from the conditional signal assignment method. In a selected signal assignment, the target is based on the evaluation of an expression. Example description of a 2-to-1 Mux with selected signal assignment:

```
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Entity for a 2-to-1 Multiplexer
entity Mux_2_to_1_Selected is
    port (
        i0, i1 : in  STD_LOGIC;  -- The two data inputs
        sel    : in  STD_LOGIC;  -- The select line
        y      : out STD_LOGIC   -- The single output
    );
end Mux_2_to_1_Selected;

-- Architecture using the "with/select" concurrent statement
architecture Behavioral of Mux_2_to_1_Selected is
begin
    -- WITH the value of 'sel', SELECT the output 'y'
    with sel select
        y <= i0 when '0',       -- When sel is '0', y gets i0
             i1 when '1',       -- When sel is '1', y gets i1
             'X' when others;  -- For any other value (like 'U' or 'Z'), output 'X' (unknown)

end Behavioral;
```

- **Process Statement :** A method that can be used to execute many instructions sequentially. This section will be discussed in more detail in module 3 regarding Behavorial Style.

# Module 3 - Behavioural Style

A behavioral style in VHDL describes a digital system by specifying its functionality using high-level algorithms and sequential statements without detailing the underlying hardware structure.

# Understanding Behavioral Style

One of the three architecture models is the behavioral style. Unlike the data-flow style, a VHDL program written in behavioral style does not need to describe how the circuit will be implemented when synthesized. Instead, the behavioral style describes how the circuit’s output will react to the inputs given to the circuit. The core of writing a program using the behavioral style is the **process statement**.

Using **behavioral style** in VHDL is like writing a recipe. You don’t explain how the kitchen is built or how the oven is wired (the hardware implementation); instead, you describe the *steps* the cook should follow and how the dish will *turn out* depending on the ingredients (the inputs). The **process statement** is like the main set of instructions in the recipe that guides the entire cooking process.

[![icegif-292.gif](https://learn.digilabdte.com/uploads/images/gallery/2025-09/icegif-292.gif)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/icegif-292.gif)

# Process Statement

A **process statement** is a concurrent command that consists of a label, sensitivity list, declaration area, begin–end (body) area, and sequential statements. An example of a process statement description in VHDL is:

```vhdl
process (<Sensitivity List>)
  -- Variable declaration area
begin
  -- VHDL statement block here
end process;
```

The difference between a **concurrent signal assignment statement** and a **process statement** lies in the sequential statements. The syntax or statements inside the begin–end (body) section are executed sequentially, line by line, just like in general programming. The **process label** itself is simply a self-descriptive naming form to help us recognize which process is being executed in that section, so the naming can be changed or even omitted.

In a **concurrent statement**, every time a change occurs in the input, the output is re-evaluated. In a **behavioral style** model using a process statement, whenever a change occurs in a signal listed in the sensitivity list of the process, all the sequential statements within the process body are re-evaluated.

Since a **process statement** is itself a concurrent statement, if there are two processes in the architecture body, the execution of both processes will be carried out concurrently.

# Sequential Statement

In a process, the execution of sequential statements will be initiated when there is a change in the signals listed in the **sensitivity list**. In general, the execution of statements in the process body will be carried out **continuously** until the end of the process **sequentially**. There are **two types** of sequential statements that will be discussed in this module:  
● If statement  
The if statement is used to create a branch in the execution flow of sequential statements. In VHDL, the if statement can only be used inside the process body. Example of a NAND gate with if statement:

```
library ieee;
use ieee.std_logic_1164.all;

entity nand_gate is
    port(
        A, B : IN STD_LOGIC;
        Y    : OUT STD_LOGIC
    );
end nand_gate;

architecture behavioral of nand_gate is
begin
    nand_proc : process (A, B) is
    begin
        if (A = '1' AND B = '1') then
            Y <= '0';
        else
            Y <= '1';
        end if;
    end process nand_proc;
end behavioral;
```

● Case statement  
The case statement works in a way similar to the previous if statement. The difference is that the case statement will be more efficient to use when there are many variations of values. Example of a NAND gate using case statement:

```
library ieee;
use ieee.std_logic_1164.all;

entity nand_gate is
    port(
        A, B : IN STD_LOGIC;
        Y    : OUT STD_LOGIC
    );
end nand_gate;

architecture behavioral of nand_gate is
begin
    AB <= A & B;  -- combining signals A and B

    nand_proc : process (A, B) is
    begin
        case (AB) is
            when "11"    => Y <= '0';
            when others  => Y <= '1';
        end case;
    end process nand_proc;
end behavioral;
```

When using behavioral style, nested sequential statements are common and often used. This is also what makes behavioral style more **powerful** than data-flow style. However, even though behavioral style statements are used like programming in general, it should be noted that VHDL is a hardware description language, not a programming language. Also, try to always keep the process statement in the description you create as **simple** as possible to make hardware design easier when the circuit becomes more complex.

# Wait Statements

**Wait Statements**  
Wait statements are used to make a process wait for a certain condition, signal/variable, or a specific time interval. The following wait statements are used:

● **Wait until \[condition\]** and **wait on \[signal\]**  
`wait until [condition]` will block the process while checking whether a condition is true or false. The process will remain blocked until the condition being checked becomes true. Meanwhile, `wait on [signal]` will wait until there is a change in the specified signal. The syntax of `wait until` and `wait on` can be synthesized.

```
process is
begin
    signal1 <= '1';
    signal2 <= '0';

    wait until rst <= '1';

    signal1 <= '0';
    signal2 <= '1';

    wait on clk;
end process;
```

● **Wait for \[time period\]**  
`wait for [time period]` will block the process for the specified time period. The syntax of `wait for` cannot be synthesized but can be simulated in the waveform using ModelSim. Therefore, `wait for` is commonly used for a testbench, which will be studied in the next module.

```
process is
begin
    signal1 <= '1';
    signal2 <= '0';

    wait for 50 ps;

    signal1 <= '0';
    signal2 <= '1';

    wait for 100 ps;
end process;
```

# Report Statements

In VHDL, the **report statement** is used to generate text messages during simulation. This statement is useful for providing information about the status or certain values during simulation. The generated report will appear in the transcript to help with debugging.

**‘Image Attribute**  
In VHDL, the `'image` attribute is used to convert a certain data type into a string data type. This attribute is useful when you want to combine or display values of different data types in the form of a string. In a report statement, this attribute is used so that variables/signals can be printed to the transcript, which only accepts strings.

Here is an example of a report statement that also uses the `'image` attribute:

```vhdl
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;

ENTITY test IS
PORT (
    clk : IN STD_LOGIC
);
END test;

ARCHITECTURE Behavioral OF test IS
    SIGNAL counter : UNSIGNED(7 DOWNTO 0);
BEGIN
    PROCESS
    BEGIN
        WAIT FOR 50 ps;
        LOOP
            WAIT UNTIL rising_edge(clk);
            REPORT "Counter = " & INTEGER'image(conv_integer(counter));
            counter <= counter + 1;
        END LOOP;
    END PROCESS;
END Behavioral;
```

Example simulation:

[![Screenshot 2025-09-09 071418.png](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/screenshot-2025-09-09-071418.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/screenshot-2025-09-09-071418.png)

# Module 4 - Testbench

# 1. Understanding Testbench in VHDL

A VHDL testbench is a non-synthesizable VHDL entity used to **simulate and verify the functionality** of another VHDL entity, often referred to as the Unit Under Test (UUT). Think of it as a **virtual lab environment** where you can apply a sequence of inputs (stimulus) to your design and observe its behavior and outputs over time. Since the testbench itself is not meant to be turned into a physical chip, it can use more abstract and powerful VHDL constructs that are not available for synthesizable hardware descriptions.

A testbench has many uses, including:
- Simplifying and speeding up the entity testing process because inputs do not need to be manually entered one by one through a simulation tool.
- Allowing the entity's output to be compared against predetermined values to verify its correctness.
- Enabling the test results to be saved into files, such as a .csv file, so they can be used by other software like Python, Excel, or MATLAB for further analysis.

# 2. Components of a Testbench

### 2.1 Entity Declaration

The testbench entity is declared **without any ports**. It's a self-contained module because it doesn't connect to any higher-level design; it *is* the top-level entity for the simulation.

```vhdl
entity project_tb is
    -- Empty because testbench doesn't have any port
end project_tb
```

### 2.2 Architecture Declaration

#### 2.2.1 UUT Component Declaration

Inside the architecture, we first declare the entity we want to test (the UUT) as a component. The component declaration must match the entity declaration of the UUT.

For example, if we have a UUT with these entity declaration:
```vhdl
entity earth_destroyer is
    Port (
        clk, rst    : IN STD_LOGIC;
        input       : IN STD_LOGIC VECTOR(7 downto 0);
        mode        : IN STD_LOGIC_VECTOR(3 downto 0);
        output      : OUT STD_LOGIC_VECTOR(7 downto 0)
    );
end earth_destroyer;
```

The UUT component declaration for that entity will be:
```vhdl
component earth_destroyer is
    Port (
        clk, rst    : IN STD_LOGIC;
        input       : IN STD_LOGIC VECTOR(7 downto 0);
        mode        : IN STD_LOGIC_VECTOR(3 downto 0);
        output      : OUT STD_LOGIC_VECTOR(7 downto 0)
    );
end component;
```

As you can see, component declaration is almost the exact same as entity declaration. Just make sure you change `entity` to `component` and use `end component` instead of `end <entity name>`, and you're good to go.

#### 2.2.2 Signals Declaration

We **must** declare internal signals within the testbench architecture. You should **at least** declare all the signals that corresponds to the entity input/output ports. These signals will be **connected to the ports of the UUT** to drive its inputs and monitor its outputs. 

For example, if we have `earth_destroyer` entity as in Part 2.2.1, we should declare these signals:

```vhdl
signal clk_tb    : STD_LOGIC := '0';
signal rst_tb    : STD_LOGIC;
signal input_tb  : STD_LOGIC VECTOR(7 downto 0);
signal mode_tb   : STD_LOGIC_VECTOR(3 downto 0);
signal output_tb : STD_LOGIC_VECTOR(7 downto 0);
```

The name of the signal doesn't really matter, as long as its **data type** match the port it corresponds to.

### 2.3 Port Map

In VHDL, a `port map` is part of the **component instantiation** within an architecture that maps the input and output ports of an entity to local signals. By using `port map`, we can connect a testbench to the entity being tested, allowing inputs to be driven and outputs to be observed through the testbench.

The general syntax of `port map` in a testbench is:
```vhdl
UUT : entity_name port map (entity_port_name => local_signal_name);
```

For example, if we want to instantiate the component as in Part 2.2, we can write it like this:

```vhdl
UUT : earth_destroyer port map (
    clk    => clk_tb,
    rst    => rst_tb,
    input  => input_tb,
    mode   => mode_tb,
    output => output_tb
);
```

# 3. Testbench Architecture Models

### 3.1 Testbench for Combinational Circuit
There are **three architectural models** for changing the value of inputs in a testbench. For example, if we want to make a testbench for a half adder entity below, we could use three methods:

```vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.all;

entity half_adder is
    Port (
        a, b       : in STD_LOGIC;
        sum, carry : out STD_LOGIC
    );
end half_adder;
    
architecture arch of half_adder is
begin
    
    sum <= a xor b;
    carry <= a and b;
    
end arch;
```

#### 3.1.1 Simple Testbench

This method is very similar to the dataflow style of VHDL programming. Values are **directly assigned to input signals** using the `<=` symbol. The difference is that each value assignment is separated by the `after` keyword, which indicates that the value will change after the testbench has run for the specified amount of time. For example, `'1' after 60 ns` means the value of the signal will change from `'0'` to `'1'` after the simulation has run for 60 ns from the very beginning.

```vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.all;

entity half_adder_tb is
end half_adder_tb;
    
architecture tb of half_adder_tb is
    
    -- component declaration for half_adder
    component half_adder is
        Port (
            a, b       : in STD_LOGIC;
            sum, carry : out STD_LOGIC
        );
    end component;
    
    -- signal declaration for input/output stimulus
    signal a, b       : STD_LOGIC;    -- Input
    signal sum, carry : STD_LOGIC;    -- Output

begin
    -- component instantiation to connect tb to entity
    UUT: half_adder port map (
        a => a,
        b => b,
        sum => sum,
        carry => carry
    );
    
    -- input assignment (simple testbench)
    a <= '0', '1' after 20 ns, '0' after 40 ns, '1' after 60 ns;
    b <= '0', '1' after 40 ns;

end tb;
```

#### 3.1.2 Testbench Using a `process` Statement

This method is similar to the behavioral style of VHDL programming, which uses a `process` statement. In this approach, every line of code inside the process is executed sequentially, one by one, much like in a typical programming language. 

```vhdl
-- input assignment (process testbench)
tb1: process
    constant period    : time := 20 ns;
    begin
        a <= '0';
        b <= '0';
        wait for period;
            
        a <= '0';
        b <= '1';
        wait for period;
            
        a <= '1';
        b <= '0';
        wait for period;
            
        a <= '1';
        b <= '1';
        wait for period;
        
        wait; -- wait until the end of time
    end process; 
```

#### 3.1.3 Testbench Using a Look-up Table

This method is an extension of the process-based testbench. Instead of assigning each combination of values one by one, this method works by storing the combinations in a separate variable, called a look-up table (which can be a `signal` or a `constant`). 

```vhdl
-- input assignment (look-up table testbench)
tb1: process
    constant period    : time := 20 ns;
    constant stream_a  : STD_LOGIC_VECTOR(0 to 3) := ('0', '1', '0', '1');
    constant stream_b  : STD_LOGIC_VECTOR(0 to 3) := ('0', '0', '1', '1');

    begin
        a <= stream_a(0);
        b <= stream_b(0);
        wait for period;
            
        a <= stream_a(1);
        b <= stream_b(1);
        wait for period;
            
        a <= stream_a(2);
        b <= stream_b(2);
        wait for period;
            
        a <= stream_a(3);
        b <= stream_b(3);
        wait for period;
        
        wait; -- wait until the end of time
    end process; 
```

You can also use `for loop` for a more efficient implementation, but more of it will be covered in **Module 6** and it's not recommended for this module.

### 3.2 Testbench for Sequential Circuit

A testbench for a sequential circuit is not much different from a testbench for a combinational circuit. The main difference is the need for several additional inputs, including a **Clock** and a **Reset**.

#### 3.2.1 Clock Process
The clock is the heartbeat of a sequential circuit. In a testbench, the clock signal must be generated continuously throughout the simulation to drive the Unit Under Test (UUT). This is almost always done using a dedicated, independent `process`.

```vhdl
-- Inside of testbench architecture
    constant period    : time := 20 ns;    -- clock period
    signal clk         : STD_LOGIC;        -- clock signal

begin
    clk_generator : process
    begin
        clk <= '0';
        wait for period/2;
        clk <= '1';
        wait for period/2;
    end process;
```

#### 3.2.2 Example of Testbench for Sequential Circuit

For example, we want to make a testbench for this updown counter entity:

```vhdl
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity counter_updown is
    Port (
        RESET, CLK, LD, UP : in std_logic;
        DIN                : in std_logic_vector (3 downto 0);
        COUNT              : out std_logic_vector (3 downto 0)
    );
end counter_updown;

architecture my_count of counter_updown is
    signal t_cnt : unsigned(3 downto 0); -- internal counter signal
begin
    process (CLK, RESET)
    begin
        if (RESET = '1') then
            t_cnt <= (others => '0'); -- clear
        elsif (rising_edge(CLK)) then
            if (LD = '1') then t_cnt <= unsigned(DIN); -- load
            else
                if (UP = '1') then t_cnt <= t_cnt + 1; -- incr
                else t_cnt <= t_cnt - 1; -- decr
                end if;
            end if;
        end if;
    end process;
    COUNT <= std_logic_vector(t_cnt);
end my_count;
```

In this case, the testbench **combines** the three architectural models that were previously explained. The **Clock** signal uses a `process` statement, while the **Reset** signal uses the simple assignment method. The other inputs are assigned directly in the declaration section because their values will not change.

Additionally, it is important to note the presence of the `max_clk` variable, which is used to **limit the number of clock cycles** run by the testbench. If the clock cycles are not limited, the testbench will continue to run indefinitely unless the program is stopped manually.

```vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity counter_updown_tb is
end counter_updown_tb;

architecture arch of counter_updown_tb is
    component counter_updown is
        Port (
            RESET, CLK, LD, UP : in std_logic;
            DIN                : in std_logic_vector (3 downto 0);
            COUNT              : out std_logic_vector (3 downto 0)
        );
    end component;

    constant period    : time := 20 ns;    -- clock period
    constant max_clk   : integer := 11;    -- maximum clock cycle
    signal cnt         : integer := 0;     -- clock cycle counter

    constant LD        : std_logic := '0';              -- input
    constant UP        : std_logic := '1';              -- input
    signal DIN         : std_logic_vector(3 downto 0);  -- input
    signal RESET       : std_logic;                     -- input
    signal CLK         : std_logic;                     -- input
    signal COUNT       : std_logic_vector(3 downto 0);  -- output

begin
    UUT : counter_updown port map (RESET, CLK, LD, UP, DIN, COUNT);
        
    -- reset = '1' at the first clock cycle, then change to '0'
    reset <= '1', '0' after period/2;

    CLK_generator : process
    begin
        CLK <= '0';
        wait for period/2;
        CLK <= '1';
        wait for period/2;

        if(cnt < max_clk) then cnt <= cnt + 1;
        else wait;
        end if;
    end process;
end arch;
```

Here is the simulation result of the testbench above:
[![Result](https://learn.digilabdte.com/uploads/images/gallery/2025-09/scaled-1680-/screenshot-2025-09-15-231232.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-09/screenshot-2025-09-15-231232.png)

# 4. Assert and Report Statement

### 4.1 Assert Statement

The assert statement is used for creating **self-checking testbenches**. It acts like an automated check that constantly monitors a condition. If the condition is **false**, it "asserts" a message, alerting us to a problem without requiring us to manually inspect the waveforms.

The full syntax of an assert statement is:

```vhdl
ASSERT condition REPORT "message" SEVERITY level;
```

- `ASSERT condition`: This is the boolean expression that you expect to be true. For example, ``(actual_output = expected_output)``.
- `REPORT "message"`: This is the message that gets printed to the simulator's console **only if the condition is false**. It's used to provide context about the failure.
- `SEVERITY level`: This is a crucial part of the statement that tells the simulator how to react to the failure.

For example, if we want to implement the assert statement in our testbench from [section 3.1.2](https://learn.digilabdte.com/books/digital-sistem-design-psddsg/page/3-testbench-architecture-models), we can implement it as below:

```vhdl
tb1: process
    constant period    : time := 20 ns;
    begin
        a <= '0';
        b <= '0';
        wait for period;
            
        assert ((sum = '0') and (carry = '0'))
        report "tes gagal pada testcase ke-1" severity error;
            
        a <= '0';
        b <= '1';
        wait for period;
            
        assert ((sum = '1') and (carry = '0'))
        report "tes gagal pada testcase ke-2" severity error;
            
        a <= '1';
        b <= '0';
        wait for period;
            
        assert ((sum = '1') and (carry = '0'))
        report "tes gagal pada testcase ke-3" severity error;
            
        a <= '1';
        b <= '1';
        wait for period;
            
        assert ((sum = '0') and (carry = '1'))
        report "tes gagal pada testcase ke-4" severity error;
        
        wait; -- wait until the end of time
    end process; 
```

The following is the output produced by the testbench:
![TB Result](https://learn.digilabdte.com/uploads/images/gallery/2025-09/screenshot-2025-09-16-135117.png)


### 4.2 Severity Level

The `severity` level tells the simulator **how serious** the failed assertion is. There are four standard levels:

| Level | Description | Simulator Action |
| :---: | :---------: | :--------------: |
| `NOTE` | **Informational message**. Used for debugging, tracing, or indicating progress. | Prints the message and continues simulation. |
| `WARNING` | **Non-critical issue**. Something is unexpected or out of spec, but the design might still function. | Prints a warning and continues simulation. Increments a warning counter. |
| `ERROR` | **Functional failure**. The design's output is incorrect. This is the standard for a failed test. | Prints an error and continues simulation. Increments an error counter. |
| `FAILURE` | **Catastrophic/Fatal error**. Something is fundamentally broken, making further simulation pointless. | Prints a failure message and immediately halts the simulation. |

Note that if we don't explicitly specify which `severity` level used in a `report` statement, it automatically defaults to `note`.

# 5. File I/O in VHDL

In VHDL, we can perform file handling using the TextIO library. This feature is very useful for documenting the results of a program that has been created. To use the TextIO library, we need to add it at the beginning of our program as follows:

```vhdl
use std.textio.all;
use ieee.std_logic_textio.all; -- For std_logic types
```

### 5.1 Read File

When repeatedly simulating a design, changing the value of each input one by one can be time-consuming and inefficient. Therefore, we can use a feature from the TextIO library that can **read inputs from a file** to be used in the design simulation.

Here is how to read input from a file in VHDL. First, we can define the file to be read within a process statement and open it in `read_mode`:

```vhdl
process
    -- Define the file and its open mode
    file text_file   : text open read_mode is "filename.txt";

    -- Variable to receive data from the file
    variable fileinp : integer;

    -- Line type variable to hold a row from the text file
    variable row     : line;

    -- Variale to store file reading status
    variable ok      : boolean
```

Then, we can read the file using the `readline` and `read` procedures from the TextIO library as follows:

```vhdl
begin
    while not endfile(text_file) loop --loop until the end of the text file
        readline(text_file, row); --reads the line from the file
        
        -- Skip empty lines or comments that start with '#'
        if row.all'length = 0 or row.all(1) = '#' then
            next;
        end if;

        -- Reads a variable from the line and puts it into fileinp
        read(row, fileinp, ok); 
        
        assert ok
        report "Read 'sel' failed for line: " & row.all
        severity failure;  --report if the file read fails
         
        wait for delay; --delay for the read iteration
    end loop;
end process;
```
  
### 5.2 Write File

Besides reading a set of inputs to be used in a testbench, we can also use the TextIO library to **save the results of our testbench to a file**. This allows us to analyze the results without having to use a simulator like ModelSim or Vivado.

Here are the steps required to write the results of a testbench to a file. First, we can define the file to be created and open it in `write_mode`:

```vhdl
process
    -- Open "filename.txt" in write mode
    file text_file      : text open write_mode is "filename.txt";

    -- Line type variable to build a row for the text file
    variable row        : line;

    -- Variable holding the data to be written to the file
    variable data_write : integer;
```

Then, we can use the `write` and `writeline` procedures rom the TextIO library to write data to the file as follows:

```vhdl
begin
    -- writes data to the file, left-justified, 15 characters
    write(row, data_write, left, 15); 

    writeline(text_file, row); -- writes the line to the file
end process;
```

# Extra: Array in VHDL

### 6.1 Array

In VHDL, an `array` is a **collection of elements** that share the same data type. You can think of an `array` as a variable that holds many elements of the same type, and these elements are indexed to be accessed. The index can be a number or another indexable type, such as `integer`, `natural`, or `std_logic_vector`. Arrays can have one dimension (one-dimensional array) or more (two-dimensional, three-dimensional, and so on). Two-dimensional arrays are often used to represent tables or matrices.

### 6.2 Type

A `type` is a definition used to **declare a new data type** in VHDL. A `type` can be used to define complex data types, such as arrays or records, or as a type used to declare variables, ports, or signals. Types can also be used to describe the properties and structure of data. 

VHDL has predefined data types, such as `std_logic`, `std_logic_vector`, `integer`, and others, but we can also create our own **custom data types**. Types that are predefined or embedded in VHDL libraries are called "built-in types," while types that we define ourselves are called "user-defined types."

Here is an example of using type and array to create a bank of 8-bit registers:

```chdl
type RegisterArray is array (0 to 7) of std_logic_vector(7 downto 0);
signal registers : RegisterArray := (others => (others => '0'));
```

In the example above, we are defining a structure that could be used in an entity like a RegisterBank which has eight 8-bit registers. These registers are represented by the registers array, which has 8 elements, each with a length of 8 bits.

# Module 5 - Structural Programming

# 1. Structural Programming in VHDL

## 1.1 Structural Style
Structural Style Programming is an approach in VHDL that allows designers to create digital circuits by using basic components connected to each other to form a more complex system. In this approach, a circuit is represented as a collection of entities linked in a specific way to achieve the desired function.

---

## 1.2 Port Mapping
Port mapping is the process of associating (mapping) the ports of a component (entity) in VHDL with the signals present in the architecture. This allows us to connect a defined entity to the actual circuit in the design.

Some important points:
-   **Entity Definition**: An entity must be defined before port mapping.
-   **Port-Map List**: A list that maps ports to signals.
-   **Port Mapping Order**: The order must match the entity's port definition.
-   **Signal Declaration**: Signals must be declared beforehand.

### Port Mapping Example
```vhdl
-- Entity definition
entity AND2 is
  port (
    A, B: in std_logic;
    Y: out std_logic
  );
end entity;

-- Port mapping in architecture (this is actually the entity's behavior)
architecture RTL of AND2 is
begin
  Y <= A and B;
end architecture;

-- Using the entity with port mapping in a higher-level design
D1: AND2 port map (
  A => input_signal_A,
  B => input_signal_B,
  Y => output_signal
);

# 2. Generic Map

## 2.1 Generic Map Explanation
A generic map is the process of associating a generic value in an entity with a value in the architecture. Generics are parameters used to configure a component.

Some important points:
-   **Generic**: A parameter to change the characteristics of an entity.
-   **Generic Map**: Sets the value of the generic during instantiation.
-   **Default Value**: Used if a value is not explicitly set.

### 2.2 Generic Map Example
```vhdl
entity Counter is
  generic (
    WIDTH: positive := 8;
    ENABLED: boolean := true
  );
  port (
    clk: in std_logic;
    reset: in std_logic;
    count: out std_logic_vector(WIDTH-1 downto 0)
  );
end entity;

architecture RTL of MyDesign is
  signal my_counter_output: std_logic_vector(7 downto 0);
begin
  my_counter_inst: Counter
    generic map (
      WIDTH => 8,
      ENABLED => true
    )
    port map (
      clk => system_clock,
      reset => reset_signal,
      count => my_counter_output
    );
end architecture;

# 3. VHDL Modularity

## 3.1 VHDL Modularity Explanation
Example: **4-bit Ripple Carry Adder** using 4 Full Adders.
A Ripple Carry Adder adds binary numbers with a chained carry.

### 3.1.1 Stage 1: Full Adder
```vhdl
entity Full_Adder is
  port (
    A, B, Cin: in std_logic;
    Sum, Cout: out std_logic
  );
end entity Full_Adder;

architecture RTL of Full_Adder is
begin
  Sum <= (A xor B) xor Cin;
  Cout <= (A and B) or ((A xor B) and Cin);
end architecture;
```

### 3.1.2 Stage 2: 4-bit Ripple Carry Adder
```vhdl
entity Four_Bit_RCA is
  port (
    A, B: in std_logic_vector(3 downto 0);
    Sum: out std_logic_vector(3 downto 0);
    Cout: out std_logic
  );
end entity Four_Bit_RCA;

architecture RTL of Four_Bit_RCA is
  signal Carry: std_logic_vector(3 downto 0);
begin
  FA0: Full_Adder port map (A(0), B(0), '0', Sum(0), Carry(0));
  FA1: Full_Adder port map (A(1), B(1), Carry(0), Sum(1), Carry(1));
  FA2: Full_Adder port map (A(2), B(2), Carry(1), Sum(2), Carry(2));
  FA3: Full_Adder port map (A(3), B(3), Carry(2), Sum(3), Cout);
end architecture;
```

### 3.1.3 Stage 3: Testbench

```vhdl
entity RCA_tb is
end entity RCA_tb;

architecture RTL of RCA_tb is
  signal A, B, Sum: std_logic_vector(3 downto 0);
  signal Cout: std_logic;
  signal Clock: std_logic := '0';
  constant Clock_Period: time := 10 ns;

  component Four_Bit_RCA
    port (
      A, B: in std_logic_vector(3 downto 0);
      Sum: out std_logic_vector(3 downto 0);
      Cout: out std_logic
    );
  end component;
begin
  Clock_Process: process
  begin
    while now < 1000 ns loop
      Clock <= not Clock;
      wait for Clock_Period / 2;
    end loop;
    wait;
  end process;

  A <= "1101";
  B <= "0011";

  RCA: Four_Bit_RCA port map (A, B, Sum, Cout);

  process
  begin
    wait until rising_edge(Clock);
    if Cout = '1' then
      report "The addition result is overflowing";
    end if;
    report "Addition result: " & to_string(Sum);
    wait;
  end process;
end architecture RTL; -- Corrected from Main_Architecture to RTL
```

# 4. Array and Type

## 4.1 Array and Type in VHDL

### 4.1.1 Array
An array is a collection of elements of the same data type. It can be one-dimensional or multi-dimensional.

### 4.1.2 Type
A type is a new data definition. It can be a built-in type (`std_logic`, `integer`) or a user-derived type.

## 4.2 Type and Array Example
```vhdl
type RegisterArray is array (0 to 7) of std_logic_vector(7 downto 0);
signal registers: RegisterArray := (others => (others => '0'));

# Module 6 - Looping Construct

loop

# Introduction to Looping Constructs

### **Introduction to Looping Constructs**

Looping constructs are VHDL instructions that allow a program to repeat the same block of code, a process known as **iteration** (similar to C).

Loops in VHDL are divided into two main categories based on how they function and what they create.

---

#### **1. Sequential Loops (Inside a `process`)**

These loops are similar to those in traditional programming languages. They describe an **algorithm** or a sequence of operations that execute step-by-step.

- They can **only** be used inside a `process`, `function`, or `procedure`.
- They are synthesized into hardware that performs an operation over multiple clock cycles or as a large block of combinational logic.

There are two main types of sequential loops:

- **`for` loop** -> Repeats for a specific number of times. Use this when you know the exact number of iterations.

- **`while` loop** -> Repeats as long as a certain condition is `true`. Use this when the number of iterations is unknown.

---

#### **2. Concurrent Loops (Outside a `process`)**

This type of loop is unique to hardware description languages. It doesn't describe an algorithm but instead describes the **replication of hardware structures**. It acts like copy and pasting ICs on a breadboard. 

![image](https://hackmd.io/_uploads/SJRoqOThgx.png)

(well, that's the gist of it)

- It creates multiple instances of concurrent statements (like component instantiations).
- It is **only** used **outside** of a `process`, in the main architectural body.

- The main type is the **`for-generate` loop**.

# For Loop

### **For Loop**


The **`for` loop** is the most common type of sequential loop in VHDL. It is used to repeat a block of code a specific, pre-determined number of times. This makes it ideal for tasks where you know exactly how many iterations you need, such as processing the bits of a vector.

-----

#### **Syntax**

The basic structure of a `for` loop is:

```vhdl
loop_label: for <index_variable> in <range> loop
    -- code code code...
end loop loop_label;
```
Example:
```vhdl
testloop: for i in 0 to 2 loop
    -- code cedo deco ... .. . .
end loop
```

  - **`loop_label`** is the name of the loop (**recommended!**). It's not mandatory, but helps a lot in coding. 
  - **`<index_variable>`** is a temporary var that holds the value of the current iteration.
  - **`<range>`**, the sequence of values the index will take. This is defined with `to` for an ascending range (`0 to 7`) or `downto` for a descending range (`7 downto 0`).

-----

#### **Notes**

When using a `for` loop, there are a few important rules to remember:

  - The index variable (`i`) is created automatically by the loop and does **not** need to be declared in the process.
  - You can read the value of `i` within the loop, but you **cannot** manually assign a new value to it.
  - The loop index will always increment or decrement by one in each iteration. You cannot specify a different step value.

-----

#### **Example: Init Memory**

The code below shows the `for` loop iterating through every address of the memory to write a '0' value to it, like setting a RAM's content to 0.

```vhdl
type t_memory is array(0 to 63) of std_logic_vector(7 downto 0);

-- Inside your architecture...
p_Memory_Reset: process (i_Clock, i_Reset)
    variable v_ram : t_memory;
begin
    if (i_Reset = '1') then
        -- Initialize all 64 locations of the RAM to zero.
        -- We use a label "RAM_INIT_LOOP" for clarity.
        RAM_INIT_LOOP: for i in v_ram'range loop
        -- RAM_INIT_LOOP: for i in 0 to 63 loop <- would work too. variable`range is more flexible
            v_ram(i) := (others => '0');
        end loop RAM_INIT_LOOP;

    elsif rising_edge(i_Clock) then
        -- ... normal memory operation code ...
    end if;
end process p_Memory_Reset;
```

This loop is much cleaner than writing 64 individual lines of code. It uses `v_ram'range` (variable'range) to automatically loop through the entire size of the array.

# While Loop

### **While Loop**

The **`while` loop** is used where the number of repetitions is not known from the start. A `while` loop continues to execute as long as a specified condition is `true`.

-----

#### **Syntax**

The basic structure of a `while` loop is:

```vhdl
loop_label: while <condition> loop
    -- Going through...
    -- IMPORTANT: Must include logic to eventually make the condition false!
end loop loop_label;
```

  - **`<condition>`** is a **boolean expression** that is checked before each iteration. If it's `true`, the loop body executes. If it's `false`, the loop terminates.

-----

#### **Warning: Infinite Loops**

A `while` loop can create an **infinite loop**. This occurs if the loop's condition never becomes `false`. Unlike in C, Java, Python etc. where infinite loops are harmless, VHDL **shouldn't have these**.

  - In a simulation, an infinite loop will cause the simulator to hang and never finish.
  - In synthesis, it can be interpreted as a feedback path that creates a latch, or the synthesizer might fail with an error.

To avoid this, ensure that the logic inside the loop will eventually cause the condition to become `false`.

-----

#### **Example: Finding the First '1'**

In this example, we'll search a vector from left to right (from the most significant bit) to find the position of the first bit that is a '1'.

Notice that unlike a `for` loop, we must **manually declare and update** our index variable (`i`).

```vhdl
-- Inside a process...
p_Find_First_One: process(a_vector)
    -- We must declare our own index variable for a while loop
    variable i : integer := a_vector'left; -- Start at the leftmost bit
    variable i_position : integer := -1; -- -1 if no 1 is found
begin
    -- Reset variables for each run of the process
    i_position := -1;
    i := a_vector'left;

    FIRST_ONE: while (i >= a_vector'right) loop

        if a_vector(i) = '1' then
            i_position := i;
            exit FIRST_ONE; -- Quit if found
        end if;

        -- Manually decrement the index to check the next bit
        i := i - 1;

    end loop SEARCH_LOOP;

    -- Assign the result to a signal
    found_position_signal <= i_position;

end process p_Find_First_One;
```

In this code, the loop continues as long as `i` is within the vector's bounds. If a '1' is found, the `exit` statement terminates the loop early. If no '1' is found, the loop completes naturally when `i` goes out of bounds.

# Loop Control - Next & Exit

### **Loop Control - Next & Exit**

The two control statements, `next` and `exit`, allow you to skip an iteration or terminate the loop entirely, giving you more precise control over your sequential code.

These statements can be used in both `for` and `while` loops.

-----

### One, **The `next` Statement**

The **`next`** statement immediately stops the **current** loop iteration and jumps to the beginning of the **next** one. Any code that comes after the `next` statement within the loop body is skipped for that specific iteration (same thing as in C!).

The syntax can be written in two ways:

```vhdl
-- Form 1: Using an if-statement
if <condition> then
    next;
end if;

-- Form 2: Using the 'when' keyword
next when <condition>;
```

#### **Example: Summing Only Odd Numbers**

To skip even numbers, the `next` statement is perfect!

```vhdl
-- Inside a process...
variable sum : integer := 0;
...
SUM_ODD_LOOP: for i in 1 to 10 loop
    -- If the number is even, skip to the next iteration
    next when (i mod 2 = 0);

    -- This line is only reached for odd numbers
    sum := sum + i;
end loop SUM_ODD_LOOP;
-- At the end, sum will be 1+3+5+7+9 = 25
```

-----

### Two, **The `exit` Statement**

The **`exit`** statement terminates the loop **entirely**. As soon as `exit` is executed, the program moves to the line after `end loop`.

The syntax is similar to `next`:

```vhdl
-- Form 1: Using an if-statement
if <condition> then
    exit;
end if;

-- Form 2: Using the 'when' keyword
exit when <condition>;
```

##### **Example: Finding a Value in Memory**

Say, we want to search through a memory to find an address that holds a specific value. Once we find it, there is no reason to continue searching.

```vhdl
-- Inside a process...
constant SEARCH_VALUE : std_logic_vector(7 downto 0) := x"A5";
variable found_addr : integer := -1; -- Default value
...
SEARCH_MEM_LOOP: for i in ram'range loop
    -- If the value at the current address matches,
    -- store the address and exit the loop immediately.
    if ram(i) = SEARCH_VALUE then
        found_addr := i;
        exit SEARCH_MEM_LOOP;
    end if;
end loop SEARCH_MEM_LOOP;

-- The code goes on...
```

Using `exit` makes the search efficient. It prevents the loop from doing unnecessary work after the item has been found.

# For-Generate Loop

### **The Concurrent 'for-generate' Loop**

We now switch from sequential loops to a **concurrent** one. 

The **`for-generate`** statement is **not** a loop that executes over time inside a process. Instead, it's a command that tells the synthesizer to create multiple copies of hardware structures.

It's good for repetitive structures like registers and chains of components. A key rule is that `for-generate` is used **outside** of a `process`, in the main architectural body.

-----

#### **Main Differentiators**

  - This loop is **Concurrent**, not **Sequential**. All the hardware instances created by the loop exist simultaneously and operate in parallel. The synthesizer "unrolls" the loop, creating all the copies before the design is even simulated or synthesized.
  - This is most often used to create multiple instances of a `component`, but **can also be used for concurrent signal assignments or even entire `process` blocks**.

-----

#### **Syntax**

The basic structure of a `for-generate` statement is:

```vhdl
generate_label: for <identifier> in <range> generate
    -- Concurrent statements to be replicated go here...
    -- (e.g., component instantiations)
end generate generate_label;
```

  - Label, identifier, and range, all follow the same rules as the other loops (`for` and `while`).

-----

#### **Example: 4-Bit Adder**

We start with a **1-bit full adder** and use `for-generate` to create and connect four of them in a chain to make a **4-bit adder**.

**1. Replication Target**
First, we need the definition of the 1-bit full adder that we want to copy.

```vhdl
component full_adder is
    port (
        A, B, Cin  : in  std_logic;
        S, Cout    : out std_logic
    );
end component;
```

**2. 4-Bit Adder Architecture**
Next, we use `for-generate` to create four instances of the `full_adder` and wire them together.

```vhdl
entity four_bit_adder is
    port (
        A, B    : in  std_logic_vector(3 downto 0);
        Cin     : in  std_logic;
        S       : out std_logic_vector(3 downto 0);
        Cout    : out std_logic
    );
end entity;

architecture structural of four_bit_adder is
    -- Internal signal to wire the carry chain between the adders.
    -- It needs 5 bits to include the first Cin and final Cout.
    signal C : std_logic_vector(4 downto 0);
begin

    -- The first carry wire is connected to the adder's carry-in pin
    C(0) <= Cin;

    -- Generate the chain of 4 full adders
    ADDER_CHAIN: for i in 0 to 3 generate
        -- Create one instance of the full_adder in each "iteration"
        FA_INSTANCE: full_adder
            port map (
                A    => A(i),       -- Connect to the i-th bit of input A
                B    => B(i),       -- Connect to the i-th bit of input B
                Cin  => C(i),       -- The carry-in for this bit
                S    => S(i),       -- The sum output for this bit
                Cout => C(i+1)      -- The carry-out for this bit
            );
    end generate ADDER_CHAIN;

    -- The final carry-out of the chain is the adder's carry-out pin
    Cout <= C(4);

end architecture structural;
```

Physically (RTL wise), this is the exact same as copy-pasting the 4 adders manually. So, this approach is much cleaner and more organized.

# When & Which

### **When & Which?**

#### **Comparison**

| Feature | `for` Loop | `while` Loop | `for-generate` Statement |
| :--- | :--- | :--- | :--- |
| **Execution** | **Sequential** | **Sequential** | **Concurrent** |
| **Usage Location** | Inside a `process` | Inside a `process` | **Outside** a `process` |
| **Iteration** | Fixed number of times | Repeats while a condition is `true` | Creates N physical copies |
| **Purpose** | Algorithmic tasks | Searching or Polling | Hardware Replication |

---
#### **Quick Guide**

- Use a **`for` loop** when...
    - You need to repeat an action a **specific number of times**.
    - *Iterating through all the bits of a vector, initializing every address in a memory.*

- Use a **`while` loop** when...
    - You need to repeat an action **until a condition changes**, and you don't know how long that will take.
    - *Searching for the first occurrence of a value, waiting for a status flag to be set in a testbench.*

- Use a **`for-generate` statement** when...
    - You need to create **multiple, regular instances of hardware components** or concurrent statements.
    - *Building an N-bit register from N flip-flops, creating a chain of adders, connecting several identical modules to a bus.*

Remember, all loops have the same syntax rules, so you just need to remember the structure. Good luck! :+1:

# Module 7 - Procedure, Function, and Impure Function

# Procedure

In VHDL, a *procedure* is a type of language construct used to group several statements and specific tasks into a single block of code. Procedures help in organizing and simplifying the understanding of complex VHDL designs.

A procedure in VHDL is similar to a void function in languages like C. It performs a specific task but does not return a value. Instead, it may modify signals or variables passed to it as parameters, allowing designers to reuse code and improve readability.

### Procedure Declaration

A procedure is defined using a procedure declaration. This declaration specifies the procedure name, any required parameters (if any), and the data type that is returned (if applicable). Below is an example of a procedure declaration in VHDL:

```vhdl
procedure Nama_Procedure(parameter1: tipe_data; parameter2: tipe_data) return tipe_data
  is
  begin
  -- Blok kode procedure
end Nama_Procedure;
```

**Parameters in Procedure**  
A procedure can accept parameters as arguments. These parameters are used to pass data into the procedure so that it can be processed. The required parameters are defined in the procedure declaration and can be of different modes such as in, out, or inout, depending on whether the data is being read, written, or both.

**Procedure Body (Code Block)**  
The body of a procedure is the section where the tasks to be performed by the procedure are written. Within this block, you can write statements to perform various operations such as calculations, condition checks, data manipulation, and more. This is where the logic of the procedure is implemented, similar to the body of a function in other programming languages.

## Procedure Call

To use a procedure, it can be called from the main part of the design or from within another procedure. A procedure is invoked by providing arguments that match the parameters defined in its declaration. Below is an example of how a procedure is used in VHDL.

```vhdl
variable1 := Nama_Procedure(nilai_parameter1, nilai_parameter2);
```

## Example Code

```vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity Procedure_Example is
  port(
    clk : in std_logic;         -- Clock input
    result_out : out integer    -- Output to observe the result
  );
end Procedure_Example;

architecture Behavioral of Procedure_Example is

  -- Signal declarations
  signal sigA : integer := 5;
  signal sigB : integer := 7;
  signal adder_result : integer;

  procedure adder(
    A, B : in integer;       -- Input parameters
    Hasil : out integer      -- Output parameter (will write to adder_result)
  ) is
  begin
    Hasil := A + B;
  end procedure;

begin

  process(clk)
  begin
    if rising_edge(clk) then
      adder(sigA, sigB, adder_result);  -- Call the procedure
    end if;
  end process;

  -- Output assignment
  result_out <= adder_result;

end Behavioral;
```

# Function

In VHDL, a **function** is a subprogram used to perform calculations or data processing that **returns a single value as a result**. Functions in VHDL are similar to functions in traditional programming languages such as C or Java, where the main purpose is to compute a value based on the input arguments. Unlike procedures, functions **must return exactly one value** and **cannot modify signals or variables outside the function directly**. They are typically used for operations like arithmetic, logical computations, or data conversion.

Functions improve code readability, reusability, and modularity in complex VHDL designs.

### Function Declaration

A function is defined using a function declaration. This declaration specifies:
- The function name
- Input parameters (if any)
- The return data type

Below is an example of a function declaration in VHDL:

```vhdl
function Function_Name(parameter1: data_type; parameter2: data_type) return return_type is
begin
  -- Function code block
  return value;
end Function_Name;
```

#### Parameters in Function

A function can accept input parameters only. These parameters are used to pass data into the function to be processed. Unlike procedures, functions cannot have out or inout parameters. All data returned from a function must be provided through the return statement.

#### Function Body (Code Block)

The body of a function contains the logic used to compute and return a value. This may include arithmetic operations, conditions, or other expressions. The function must include a return statement that provides the final result.

### Function Call

A function is called by using its name and passing the required arguments. The returned value can be directly assigned to a signal or variable.

```vhdl
variable1 := Function_Name(input_value1, input_value2);
```

### Example Code

```vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity Function_Example is
  port(
    clk : in std_logic;         -- Clock input
    result_out : out integer    -- Output to observe the result
  );
end Function_Example;

architecture Behavioral of Function_Example is

  -- Signal declarations
  signal sigA : integer := 10;
  signal sigB : integer := 20;
  signal function_result : integer;

  -- Function Declaration
  function adder(
    A: integer;
    B: integer
  ) return integer is
  begin
    return A + B;
  end function;

begin

  process(clk)
  begin
    if rising_edge(clk) then
      function_result <= adder(sigA, sigB);  -- Call the function
    end if;
  end process;

  -- Output assignment
  result_out <= function_result;

end Behavioral;
```

# Impure Function

In VHDL, an **impure function** is a special type of function that is allowed to **read or modify signals, variables, or states outside its local scope**. Unlike a pure function, which always produces the same output for the same input (no side effects), an impure function **can interact with external data** and may produce different results each time it is called.

Impure functions are useful when you need to:
- Access or modify global variables or signals
- Read the current value of a signal that may change over time
- Implement functions with memory or state (like random number generators, counters, etc.)

To define an impure function, you must use the keyword `impure`.

---

### Impure Function Declaration

```vhdl
impure function Function_Name(parameter1: data_type) return return_type is
begin
  -- Function code block with external side effects
  return value;
end Function_Name
```

### Code Example

```vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity Impure_Function_Example is
  port(
    clk : in std_logic;
    result_out : out integer
  );
end Impure_Function_Example;

architecture Behavioral of Impure_Function_Example is

  -- Signal declaration
  signal counter : integer := 0;
  signal function_result : integer;

  -- Impure Function Declaration
  impure function read_and_increment return integer is
  begin
    counter <= counter + 1;          -- Modifies an external signal
    return counter;                  -- Returns updated value
  end function;

begin

  process(clk)
  begin
    if rising_edge(clk) then
      function_result <= read_and_increment;  -- Call the impure function
    end if;
  end process;

  -- Output assignment
  result_out <= function_result;

end Behavioral;
```

### When to Use Impure or Pure Functions

#### Code 1

```vhdl
function multiply(a, b: integer) return integer is
begin
  return a * b;
end function;
```
This is **pure** because it depends only on the inputs a and b.

#### Code 2

```vhdl
signal counter : integer := 0;

impure function increment_counter return integer is
begin
  counter <= counter + 1;  -- Modifies external signal
  return counter;
end function;
```
This function must be **impure** because it changes an external signal (counter).

#### Code 3

```vhdl
signal total_sum : integer := 0;

impure function add_and_accumulate(a, b : integer) return integer is
begin
  total_sum <= total_sum + (a + b); -- Modify external signal
  return total_sum;                 -- Return updated accumulated value
end function;
```
This function must be **impure** because it changes an external signal (total_sum).

#### Code 4

```vhdl
signal current_status : integer := 3;

impure function read_status return integer is
begin
  return current_status; -- Reads external signal, so must be impure
end function;
```

This function mus be **impure** because it reads external signal that may change over time.


#### Code 5

```vhdl
impure function random_generator return integer is
  variable seed : integer := 1; -- Static variable retains value between calls
begin
  seed := (seed * 1103515245 + 12345) mod 256; -- Simple random algorithm
  return seed;
end function;
```
This function must be impure because it maintains internal state (seed) that changes on each call.

# Procedure, Function and Impure Function Synthesis

In VHDL, both "function" and "procedure" can be used in hardware descriptions, but it should be understood that hardware synthesis is usually more suitable for implementations based on deterministic and synchronous behavior. Therefore, there are several limitations on the use of functions and procedures in the context of synthesis:

* **Procedure**: Procedures in VHDL are used to perform tasks without returning a value. They can be used in hardware descriptions to manage operations and organize code. Hardware synthesis will usually replace procedure calls with the corresponding physical actions in the target hardware. Therefore, deterministic procedures can be synthesized. However, there are some limitations on the use of procedures that depend on time flow or behavior that is difficult to predict. Some VHDL compilers may not support the synthesis of such procedures.
* **Function**: VHDL functions that do not have impure properties (e.g., they produce a deterministic value based on input arguments only) can usually be synthesized well.
* **Impure Function**: Impure functions, which produce unpredictable results or depend on external factors, are usually not suitable for deterministic hardware synthesis. Impure functions that depend on random or non-deterministic behavior will not be synthesizable, as the resulting hardware must be deterministic and predictable.

So, while functions and procedures can be used in hardware descriptions and can be synthesized if they meet certain requirements, impure functions are usually not suitable for VHDL synthesis.

# Difference between Procedure, Function and Impure Function

| Criteria | Procedure | Function | Impure Function |
| --------- | :--- | :--- | :--- |
| **Purpose** | Perform tasks without returning a value. | Return a value from a calculation. | Produce an unpredictable value or one that depends on external factors. |
| **Arguments** | Can have input and/or output arguments. | Can have input arguments only. | Can have input arguments only. |
| **Return Value** | Does not return a value (void). | Returns a value from a calculation. | Returns a value from a calculation. |
| **Usage** | Used to organize tasks or operations. | Used for calculations or data processing. | Used when the function's result depends on external factors. |
| **Example** | `vhdl procedure SetFlag(flag: out boolean);` | `vhdl function Add(a, b: integer) return integer;` | `vhdl function RandomNumber return integer;` |
| **Synthesis** | Can be synthesized if it is deterministic and synchronous. | Can be synthesized if it is deterministic and synchronous. | Not suitable for synthesis because the result is unpredictable or depends on external factors. |

# Module 9 - Microprogramming

# 1. Introduction: The Role of the Control Unit

* **1.1 Definition:** The Control Unit (CU) is the core component of a computer's Central Processing Unit (CPU) that directs its operation. Often compared to the "brain" or "central nervous system" of the computer, the CU does not execute program instructions itself; rather, it manages and coordinates the activities of all other components, such as the Arithmetic Logic Unit (ALU) and the registers, to ensure instructions are performed correctly. It is the logical hub that orchestrates the complex sequence of events necessary for processing.

  * **1.2 Function:** The primary function of the Control Unit is to manage the fundamental operation of the CPU: the fetch-decode-execute cycle.

      * **Fetch:** The CU generates the signals to read the memory address from the Program Counter (PC), send it to the Memory Address Register (MAR), and then read the instruction from RAM into the Instruction Register (IR).
      * **Decode:** The CU interprets the binary opcode of the instruction that has been fetched into the IR.
      * **Execute:** Based on the decoded instruction, the CU issues a precise sequence of control signals to the datapath (e.g., enabling registers, setting the ALU's operation, and managing memory access) to carry out the command.

  * **1.3 The Problem:** The mechanism for decoding instructions and generating these precise sequences of control signals presents a fundamental design choice. For a CPU to function, it must have a logic system capable of producing the correct set of outputs (control signals) for every possible input (opcode and status flags). The core design problem, therefore, is *how* to implement this complex logic. This challenge leads to two primary design philosophies: a fixed, high-speed logic circuit known as **Hardwired Control**, or a more flexible, memory-based approach known as **Microprogrammed Control**.

# 2. The Control Unit Dilemma: Hardwired vs. Microprogrammed

The fundamental problem of generating control signals, introduced in Section 1.0, is solved by two distinct design philosophies. This choice between a "hardwired" and a "microprogrammed" control unit represents a classic engineering trade-off between speed and flexibility.

* **2.1 Hardwired Control**

  * **Implementation:** A hardwired control unit is a fixed, sequential logic circuit. Its logic is built directly from gates (AND, OR, NOT) and flip-flops, which together form a complex Finite State Machine (FSM). The 4-bit `opcode` from the instruction, along with status flags and the current state, are fed into this combinatorial logic, which in turn generates the specific output signals (`RAI`, `PCO`, `SUB`, etc.) for that clock cycle.

  * **Analogy:** This design is analogous to a custom-built, high-speed machine designed for one specific task, like a specialized factory robot. It is built from the ground up to perform its one job as fast as possible.

  * **Pros:** Its primary advantage is speed. Because the control signals are generated directly by logic gates, the propagation delay is minimal, allowing for a very high clock speed.

  * **Cons:** The design is extremely inflexible. If a bug is found or a new instruction needs to be added (e.g., adding a `SUB` instruction to a CPU that only has `ADD`), the entire logic circuit must be redesigned, re-manufactured, and replaced. This makes it complex to design and nearly impossible to modify or upgrade.

* **2.2 Microprogrammed Control**

  * **Implementation:** This is the alternative, flexible, memory-based approach. In this design, the Control Unit is not a complex web of gates but rather a small, simple "computer-within-a-computer." This internal computer has its own simple program (a **microprogram**) stored in a special, high-speed memory called a **Control Store**.

  * **Analogy:** Instead of a custom-built robot, this is like a general-purpose, programmable robot. To execute a command like "ADD," it runs a small, internal program (a "micro-routine") that tells it, step-by-step, how to activate the necessary hardware components to perform the addition.

  * **Pros:** The primary advantage is flexibility. To add a new instruction, one simply adds a new micro-routine to the Control Store's memory (firmware). This makes the design process systematic and far easier to debug and upgrade.

  * **Cons:** Its main disadvantage is speed. It is inherently slower than a hardwired unit because it must perform an extra memory access (fetching the micro-instruction from the Control Store) for every clock cycle.

* **2.3 Comparison Table**

| **Feature** | **Hardwired Control (FSM)** | **Microprogrammed Control** | 
|--|---|---|
| **Implementation** | Sequential logic circuit (gates, flip-flops) | Control Store (ROM) & Sequencer | 
| **Speed** | **Very Fast** (low propagation delay) | **Slower** (extra memory access) | 
| **Flexibility** | **Very Low.** Difficult to modify. | **Very High.** Can be updated (firmware). | 
| **Design Complexity** | High, error-prone, and complex to manage. | Systematic, orderly, and easier to debug. | 
| **Best For** | RISC (Reduced Instruction Set Computers) | CISC (Complex Instruction Set Computers) |

# 3. Principles of Microprogrammed Control

This section details the core theory of the microprogrammed control unit, the flexible alternative to the hardwired FSM. This approach fundamentally changes the design from a complex, fixed logic circuit to a simple, programmable one.

* **3.1 Core Concept: The "Computer-within-a-Computer"**
    * The most effective analogy for a microprogrammed control unit is that it is a "computer-within-a-computer." The main CPU (which has assembly instructions like `ADD`, `LDA`, `JMP`) is the "outer" computer. The Control Unit itself is a tiny, hidden, "inner" computer.
    * This inner computer has its own simple program, called a **microprogram**. The microprogram is composed of a sequence of instructions called **micro-instructions**.
    * The key relationship is this: **One assembly instruction** (like `LDA`) is interpreted by the CU as a command to run a specific **micro-routine** (a "subroutine" of micro-instructions). The micro-routine is the step-by-step recipe that defines *how* to execute that single assembly instructior.

* **3.2 Architectural Components**
    * To function as a simple computer, the microprogrammed CU has its own set of internal hardware components, separate from the main datapath.

    * **3.2.1 Control Store (or Control Memory):**
        * This is a small, high-speed Read-Only Memory (ROM) that is located *inside* the Control Unit.
        * Its sole purpose is to store the entire microprogram—that is, all the micro-routines for every assembly instruction in the CPU's instruction set (e.g., the routines for `LDA`, `STA`, `ADD`, `JMP`, etc.).

    * **3.2.2 Micro-Sequencer (Next Address Logic):**
        * This is the "Program Counter" for the Control Unit (often called a `uPC` or "micro-Program Counter").
        * Its job is to determine the address of the *next* micro-instruction to be fetched from the Control Store.
        * It decides this address based on several inputs: the main instruction's `opcode` (for the initial "decode" jump), CPU status flags (for conditional branches like `JEQ`), and sequencing fields from the current micro-instruction (e.g., `SEQ_NEXT`, `SEQ_FETCH`).

    * **3.2.3 Micro-instruction Register (uIR):**
        * This is a register that holds the *current* micro-instruction that was just fetched from the Control Store.
        * This is the most critical component for execution: the output bits of this register **are** the actual control signals that are sent to the datapath.
        * For example, a 16-bit `uIR` might directly output 16 control signals (like `Register_Enable`, `ALU_Subtract`, `Memory_Write`, etc.) to the rest of the CPU.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762884193402.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762884193402.png)

# 4. The Micro-instruction

If the Control Store is the "recipe book" for the CPU, then a micro-instruction is a single "line" in that recipe. It is the most fundamental unit of control in a microprogrammed CPU, defining the exact set of hardware operations that will occur in a single clock cycle.

* **4.1 Definition**
    A micro-instruction is a single word fetched from the Control Store. Its primary function is to specify the control signals to be generated for one clock tick. Each micro-instruction is held in the Micro-instruction Register (uIR), and the bits of this register are used—either directly or after decoding—to activate or deactivate every control line in the CPU's datapath [1].

* **4.2 Format and Fields**
    While the exact layout varies, a micro-instruction is typically divided into two primary types of fields [2].

    * **4.2.1 Control Field:**
        This is the main part of the micro-instruction. It contains the bits that directly control the datapath components. For example, a "1" in a specific bit position might enable a register to output its value to the main bus (like `RAO`), while a "0" keeps it disabled. Another set of bits might specify the operation for the ALU (e.g., `SUB=1`).

    * **4.2.2 Sequencing Field (Next Address Field):**
        This field doesn't control the datapath; it controls the Micro-Sequencer itself. These bits provide the sequencer with information to determine the address of the *next* micro-instruction. This field might contain:
        * A specific "next address" to jump to.
        * A "branch condition" code (like `SEQ_DECODE` or `SEQ_JUMP_ON_ZERO`) that tells the sequencer *how* to find the next address by checking opcodes or status flags [3].

* **4.3 Horizontal vs. Vertical Microprogramming**
    The "Control Field" can be designed in two primary ways, which presents a classic trade-off between speed and memory efficiency [4].

    

    * **4.3.1 Horizontal Microprogramming:**
        This is a "decoded" or "unencoded" approach. The micro-instruction is very wide, and each bit in the control field corresponds directly to a single control line. If the CPU has 60 control signals, the control field is 60 bits wide.
        * **Pros:** It is extremely fast. No additional decoding logic is needed; the bits from the uIR can be used directly. It also allows for high parallelism, as many signals can be activated in the same cycle.
        * **Cons:** It is very inefficient. A micro-instruction that only activates 2 signals (e.g., `RAO` and `RBI`) still requires the full 60 bits, wasting space in the Control Store.

    * **4.3.2 Vertical Microprogramming:**
        This is an "encoded" approach. Instead of one bit per signal, groups of signals are encoded into fields. For example, if an ALU has 8 possible operations (ADD, SUB, AND, OR, etc.), a 3-bit field (`2^3 = 8`) can be used to select which operation to perform.
        * **Pros:** It is highly efficient and saves a significant amount of space in the Control Store. The micro-instructions are much narrower.
        * **Cons:** It is slower. The encoded fields (like the 3-bit ALU field) must be passed through an external decoder circuit to generate the final control signals, adding a layer of gate delay.

    In practice, most modern designs are a hybrid, using vertical encoding for mutually exclusive signals (like ALU operations) and horizontal bits for independent signals (like `Memory_Write`).

# 5. Execution Flow and Sequencing

This section connects all the previous concepts to illustrate how the microprogrammed control unit *runs* a program. The core of this operation is the mapping of high-level assembly instructions to low-level micro-routines, all managed by the micro-sequencer.

* **5.1 Mapping Assembly to Micro-routines**
    * The most important relationship in this design is the one-to-many mapping between an assembly instruction and its micro-routine. The main CPU's instruction set (the "assembly language") is not executed directly by the hardware; rather, each assembly instruction's opcode is a *command* that tells the micro-sequencer to find and run a specific "subroutine" of micro-instructions stored in the Control Store.

    * **Hypothetical Example:**
        * Consider a CPU with registers `R0`, `R1`, a `PC`, `MAR`, `IR`, and `RAM`.
        * A programmer writes the assembly instruction: `LOAD R0, [0x30]`
        * This translates to the 8-bit machine code `0001 0011` (Opcode `0001` for `LOAD R0`, and Address `0011` for `0x30`).
        * The Control Unit is designed to interpret this `0001` opcode. This single assembly instruction triggers a multi-step micro-routine:

| **Phase** | **Micro-operation** | **Keterangan** |
| :--- | :--- | :--- |
| **Fetch** | `uAddr 0:` `PC_out, MAR_in` | (Fetch CC1) Send PC to memory. |
| | `uAddr 1:` `RAM_out, IR_in` | (Fetch CC2) Get instruction `00010011` into IR. |
| **Decode** | `uAddr 1:` (Sequencer Logic) | Sequencer sees `0001`, maps it to `uAddr 16`. |
| **Execute** | `uAddr 16:` `IR_addr_out, MAR_in` | (Execute CC1) Get address `0011` from IR. |
| | `uAddr 17:` `RAM_out, R0_in, PC_inc` | (Execute CC2) Get data from `RAM[0x30]` into `R0`. Increment PC. |
| | `uAddr 17:` (Sequencer Logic) | Sequencer sees `SEQ_FETCH`, sets `next_uPC = 0`. |

* **5.2 The Fetch-Decode-Execute Cycle (Microprogram View)**
    * From the micro-sequencer's perspective, the fetch-decode-execute cycle is a continuous loop of branching within its own Control Store [2].

    * **1. Fetch:** The micro-sequencer is hardwired to always start at a fixed address (e.g., `uAddr 0`) when a new instruction is needed. It then executes the `Fetch` micro-routine (e.g., at `uAddr 0` and `uAddr 1`).

    * **2. Decode:** The final micro-instruction of the `Fetch` routine (at `uAddr 1` in our example) contains a special `SEQ_DECODE` sequencing command. This command tells the micro-sequencer to *stop* incrementing and instead use the `opcode` from the `IR` as an input to its logic. This logic acts as a "mapping ROM," translating the opcode (e.g., `0001`) into the starting address of its corresponding micro-routine (e.g., `uAddr 16`).

    * **3. Execute:** The micro-sequencer *jumps* to that new address (e.g., `uAddr 16`) and executes the micro-routine for the instruction. The final micro-instruction of *that* routine (e.g., at `uAddr 17`) then issues a `SEQ_FETCH` command, which forces the micro-sequencer to jump back to `uAddr 0` and begin the entire process again for the next assembly instruction.

* **5.3 Conditional Branching (Sequencing with Flags)**
    * The true power of a micro-sequencer is revealed in how it handles conditional branching (e.g., `JUMP_IF_ZERO`). This is not implemented with a new micro-instruction, but by adding logic to the **sequencer's `Decode` step**.

    * The `SEQ_DECODE` command is enhanced to not only look at the `opcode` but also at the CPU's `Status Flags` (like `Zero_Flag` and `Carry_Flag`).

    * **Example (Hypothetical `JUMP_ZERO [ADDR]`, Opcode `1010`):**
        * The programmer writes `CMP R0, R1` (which sets the `Zero_Flag`) followed by `JUMP_ZERO 0x05`.
        * When the `JUMP_ZERO` instruction (`1010 0101`) is fetched, the micro-sequencer's `DECODE` logic executes the following:
        
        ```
        if (opcode == "1010") then
            -- This is a JUMP_ZERO instruction
            if (Zero_Flag == '1') then
                next_uPC = uAddr_JUMP_ROUTINE;  -- Jump to the JUMP micro-routine
            else
                next_uPC = uAddr_NOP_ROUTINE;   -- Jump to the NOP micro-routine (to just inc PC)
            end if;
        else
            -- (handle other opcodes...)
        end if;
        ```
    * In this way, the micro-sequencer dynamically selects the correct micro-routine—either `JMP` or `NOP`—based on the *current state of the CPU flags*, effectively executing the conditional branch.

* **5.4 VHDL Code Example: A Hypothetical Microprogrammed Control Unit**

The following VHDL code is a complete, behavioral model of a hypothetical microprogrammed control unit. This example is simplified to clearly demonstrate the core concepts of sequencing, mapping, and conditional branching.

This code directly illustrates the concepts from sections 5.1, 5.2, and 5.3.

* **Mapping (5.1):** The `init_rom` function shows how opcodes (like `"0001"`) are "mapped" to the starting addresses of micro-routines (like `uAddr 20`).
* **Fetch/Decode/Execute (5.2):** The `next_uPC` process (the Micro-Sequencer) implements this flow.
    * `SEQ_FETCH` (at `uAddr 0, 1`) is the `Fetch` phase.
    * `when SEQ_DECODE =>` is the `Decode` phase, which reads the `OPCODE_IN`.
    * The resulting jump (e.g., `next_uPC <= to_unsigned(20, 8)`) is the start of the `Execute` phase.
* **Conditional Branching (5.3):** The `SEQ_DECODE` block contains the most important logic. It explicitly checks the `Z_FLAG` *in addition* to the `OPCODE_IN` to implement a `JUMP_IF_ZERO` instruction.

```vhdl
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity Hypothetical_Micro_CU is
    port (
        CLK         : in  std_logic;
        RST         : in  std_logic;
        
        -- Inputs from Datapath
        OPCODE_IN   : in  std_logic_vector(3 downto 0);
        Z_FLAG      : in  std_logic; -- (for conditional jumps)
        
        -- Outputs to a hypothetical 10-signal datapath
        PC_OUT      : out std_logic := '0';
        PC_INC      : out std_logic := '0';
        MAR_IN      : out std_logic := '0';
        RAM_OUT     : out std_logic := '0';
        RAM_IN      : out std_logic := '0';
        IR_IN       : out std_logic := '0';
        ACC_IN      : out std_logic := '0';
        ACC_OUT     : out std_logic := '0';
        TEMP_IN     : out std_logic := '0';
        ALU_OUT     : out std_logic := '0'
    );
end entity;

architecture Behavioral of Hypothetical_Micro_CU is

    -- 3.2.2 Micro-Sequencer
    -- This is the "micro-Program Counter" (uPC)
    signal uPC      : unsigned(7 downto 0) := (others => '0');
    signal next_uPC : unsigned(7 downto 0);
    
    -- 3.2.3 Micro-instruction Register (uIR)
    -- 10 control bits + 2 sequencing bits
    signal uIR : std_logic_vector(11 downto 0); 
    
    -- 4.2.2 Sequencing Field Constants
    constant SEQ_NEXT   : std_logic_vector(1 downto 0) := "00";
    constant SEQ_DECODE : std_logic_vector(1 downto 0) := "01";
    constant SEQ_FETCH  : std_logic_vector(1 downto 0) := "10";
    constant SEQ_HLT    : std_logic_vector(1 downto 0) := "11";
    
    -- 4.2 Format and Fields
    constant C_SEQ_BITS  : integer := 2;
    constant C_CTRL_BITS : integer := 10;
    constant C_UCODE_WIDTH : integer := C_SEQ_BITS + C_CTRL_BITS; -- 12 bits
    
    -- 3.2.1 Control Store (ROM)
    type t_control_store is array(0 to 255) of std_logic_vector(C_UCODE_WIDTH - 1 downto 0);

    -- Helper function to build a micro-instruction
    function to_ucode(
        ctrl : std_logic_vector(C_CTRL_BITS - 1 downto 0);
        seq  : std_logic_vector(C_SEQ_BITS - 1 downto 0)
    ) return std_logic_vector is
    begin
        return seq & ctrl; -- [Seq(1:0), Ctrl(9:0)]
    end function;

    -- Initialize the Control Store (ROM) with micro-routines
    function init_rom return t_control_store is
        variable rom : t_control_store := (others => (others => '0'));
        
        -- Control Field constants (10 bits)
        -- [PC_OUT, PC_INC, MAR_IN, RAM_OUT, RAM_IN, IR_IN, ACC_IN, ACC_OUT, TEMP_IN, ALU_OUT]
        constant C_FETCH1 : std_logic_vector(9 downto 0) := "1010000000"; -- PC_OUT, MAR_IN
        constant C_FETCH2 : std_logic_vector(9 downto 0) := "0001010000"; -- RAM_OUT, IR_IN
        constant C_LDA1   : std_logic_vector(9 downto 0) := "0010000000"; -- MAR_IN (assume from IR)
        constant C_LDA2   : std_logic_vector(9 downto 0) := "0101001000"; -- PC_INC, RAM_OUT, ACC_IN
        constant C_JUMP1  : std_logic_vector(9 downto 0) := "0000000000"; -- (Assume PCI/IRO logic)
        constant C_NOP1   : std_logic_vector(9 downto 0) := "0100000000"; -- PC_INC

    begin
        -- 5.2 Fetch Cycle
        rom(0) := to_ucode(C_FETCH1, SEQ_NEXT);   -- uAddr 0
        rom(1) := to_ucode(C_FETCH2, SEQ_DECODE); -- uAddr 1

        -- 5.1 Mapping Assembly to Micro-routines
        -- These are the "Execute" routines
        
        -- Map Opcode "0001" (LOAD_ACC) to uAddr 20
        rom(20) := to_ucode(C_LDA1, SEQ_NEXT);
        rom(21) := to_ucode(C_LDA2, SEQ_FETCH);

        -- Define targets for conditional branches
        rom(16) := to_ucode(C_NOP1, SEQ_FETCH);  -- uAddr 16 (NOP routine)
        rom(30) := to_ucode(C_JUMP1, SEQ_FETCH); -- uAddr 30 (JUMP routine)

        -- ... the rest of the ROM for other opcodes (ADD, STA, etc.) ...
        
        return rom;
    end function;

    -- Create the constant ROM
    constant Control_Store : t_control_store := init_rom;
    
    -- Define the jump targets for conditional branches
    constant uAddr_NOP_ROUTINE : unsigned(7 downto 0) := to_unsigned(16, 8);
    constant uAddr_JMP_ROUTINE : unsigned(7 downto 0) := to_unsigned(30, 8);

begin

    -- 1. Micro-Program Counter (uPC) Register (The "State")
    process(CLK)
    begin
        if rising_edge(CLK) then
            if RST = '1' then
                uPC <= (others => '0'); -- On reset, go to Fetch (uAddr 0)
            else
                uPC <= next_uPC;
            end if;
        end if;
    end process;

    -- 2. ROM Read (Concurrent)
    uIR <= Control_Store(to_integer(uPC));

    -- 3. Micro-Sequencer (Next uPC Logic)
    -- This process is the "brain" of the Control Unit
    process(uPC, uIR, OPCODE_IN, Z_FLAG)
        variable seq_control : std_logic_vector(1 downto 0);
    begin
        seq_control := uIR(C_UCODE_WIDTH - 1 downto C_CTRL_BITS);
        
        -- Default: always increment
        next_uPC <= uPC + 1;

        case seq_control is
            when SEQ_NEXT =>
                next_uPC <= uPC + 1;
                
            when SEQ_FETCH =>
                next_uPC <= (others => '0'); -- Go back to Fetch (uAddr 0)

            when SEQ_HLT =>
                next_uPC <= uPC; -- Halt by looping on this address

            when SEQ_DECODE =>
                -- This is the 5.2 Decode step
                -- It maps the opcode to a micro-routine address
                case OPCODE_IN is
                    when "0001" => -- LOAD_ACC
                        next_uPC <= to_unsigned(20, 8);
                    
                    -- ... other non-branching opcodes (ADD, STA, etc.) ...
                    
                    -- 5.3 Conditional Branching Logic
                    when "1010" => -- JUMP_IF_ZERO
                        if Z_FLAG = '1' then
                            next_uPC <= uAddr_JMP_ROUTINE;
                        else
                            next_uPC <= uAddr_NOP_ROUTINE;
                        end if;
                        
                    -- ... other conditional branches (JNE, JLT, etc.) ...

                    when others => 
                        next_uPC <= uAddr_NOP_ROUTINE; -- Invalid opcode
                end case;

            when others =>
                next_uPC <= (others => '0'); -- Safe state
        end case;
    end process;

    -- 4. Output Logic (Concurrent)
    -- Map the 10 control bits from the uIR to the output ports
    PC_OUT  <= uIR(9);
    PC_INC  <= uIR(8);
    MAR_IN  <= uIR(7);
    RAM_OUT <= uIR(6);
    RAM_IN  <= uIR(5);
    IR_IN   <= uIR(4);
    ACC_IN  <= uIR(3);
    ACC_OUT <= uIR(2);
    TEMP_IN <= uIR(1);
    ALU_OUT <= uIR(0);

end Behavioral;

# Module 10 - Final Project

# Final Project Guide

## Congratulations! 🥳

In this final module, you are given the opportunity to create a project with your group members according to the following provisions:

### Final Project Timeline

* **Github Link Deadline:** **Sunday, November 23, 2025 23.59 WIB**
* **Project Title:** Discuss with the supervising assistant (Must be decided by **Sunday, November 30, 2023**)
* **Project Submission Deadline:** **Sunday, December 7, 2025, 23:59 WIB**
* **Presentation Week:** **December 8-12, 2025** (discuss with the supervising assistant)

---

### Final Project Criteria

* The final project must cover atleast 6 of the practicum modules. The program and testbench **MUST** use the VHDL language, including code explanations using comments.
* Create a **public GitHub repository** for project submission. Each individual must **commit regularly** so their individual contributions are visible in the final project (there will also be a contribution assessment form).
* You **must invite the supervising assistant** as a collaborator to the repo and to the LINE group.
* **Force pushing** to the repo is prohibited as it can delete commit history.
* You must create a **README.md** in the final project repository containing an explanation of your project. The sections can be the same as those in the report file, with the addition of code snippet explanations.
    * Tutorial: Markdown Crash Course.
    * Template: [GitHub - matiassingers/awesome-readme](https://github.com/matiassingers/awesome-readme)
* **Program complexity** will affect the final project grade. If the project you create is limited to fulfilling module requirements and is less suitable for FPGA implementation, the grade will be lower than for one that is more suitable.
* **Example of a less suitable** (though not prohibited) project: Creating a Vending Machine can be implemented on an FPGA, or a DSD project you've done before can also be implemented with FPGA (VHDL). However, no one would implement something that simple on an FPGA because it's more practical to use an Arduino or another microcontroller.
* **Example of a more suitable** project: Creating a hardware accelerator for a specific, frequently used algorithm.
* Create a final project report based on the provided template.
* Create a presentation PowerPoint.

---

### Final Project Grading Weight

* Report (PDF & MD): 15%
* Presentation (PPT, Delivery, & Q&A): 20%
* Complexity (including Understanding): 25%
* Idea Creativity: 10%
* Success/Functionality: 30%

---

### Files and Submission Location

* **EMAS3**
    * GitHub Link (submitted on EMAS)
* **GitHub Repository**
    * PDF Report file
    * PPT Presentation file
    * Source code + Testbench
    * Quartus Synthesis
    * Modelsim waveform simulation
    * README.md

---

### Example Final Project Ideas\*

* **VHDL Image Processing:** Modify an image (e.g., Rotation, Brightness, Flip).
    * Source: https://github.com/juanjonathan67/Simple-Image-Augmenter
* **VHDL Image Upscaler:** Upscale an image.
    * Source: https://github.com/Jordinia/Bicubic-Interpolation
* **VHDL Enigma Encryption:** Encrypt an input message.
    * Source: https://github.com/ArmondHarer/Proyek-Akhir-PSD-B2

\*Only use these three examples as a reference or for idea inspiration. **Plagiarism is prohibited**.