November 14, 2024

Load Data from Files into Verilog and Vivado Simulations – FPGA Tutorial

What is covered in this tutorial: In this FPGA, Verilog, and Vivado tutorial, we explain how to load data from files into a Verilog and Vivado simulation.

Motivation: The standard practice when developing and implementing FPGA algorithms is to first implement and test algorithms in MATLAB and Python. Some people also use C/C++ programming languages for this purpose. This is done since it is significantly easier to test algorithms in MATLAB or Python than in FPGAs. After MATLAB or Python simulations convince us that algorithms work as expected, the next stage is to implement them in Verilog and simulate them in the Vivado development environment. The final stage is the FPGA implementation. During testing and simulation in Verilog and Vivado, it is often necessary to load a test data sequence that is identical to the test data sequence which is used for algorithm simulation in Python and MATLAB. Consequently, we need to know how to load data from the external files into a Verilog simulation.

The YouTube tutorial is given below.

How to Load Load from Files into Verilog and Vivado FPGA Simulations

As a test case, we will create a simple module that will perform bit-wise complement of the input sequence. The input sequence consists of 10 elements, where every element is a 2-bit binary number. For tests, create a file and save it as “input.data”. The content of the file is given below

00
01
10
11
01
01
10
11
00
11

We will load this file in our Verilog simulation. The module is given below.

`timescale 1ns / 1ps
module test_module(inputData, outputData);

input [1:0] inputData;
output [1:0] outputData;

assign outputData=~inputData;

endmodule

This module contains two ports “inputData” and “outputData”. Both of these ports are two-bit long. The outputData is a bit-wise complement of inputData. Save this file as “test_module.v”

The binary numbers from the created file called “input.data” will be sent sample-by-sample to the Verilog code. For that purpose, we define a testbench file given below.

`timescale 1ns / 1ps

module simulation;

integer k;
reg [1:0] inputData;
wire [1:0] outputData;

reg CLK;

always #10 CLK=~CLK;

reg [1:0] data[9:0];


test_module UUT(.inputData(inputData),
                .outputData(outputData)
                );
initial 
begin 
CLK=0;
k=0;
// read the data from the file 
$readmemb("input.data",data);
#20
for (k=0;k<10;k=k+1)
begin
 @(posedge CLK);
    inputData<=data[k];
end
end
endmodule

Let us first explain the declaration block:

integer k;
reg [1:0] inputData;
wire [1:0] outputData;

reg CLK;

The integer variable k is used to iterate through the rows of the data file. The register inputData and the wire variable outputDat awill be connected to the corresponding variables in the original module. The register variable CLK is used to define the clock for running the simulation. The clock is defined like this

always #10 CLK=~CLK;

This defines a clock with a period of 20 nanoseconds (pulse width of 10 nanoseconds). The memory (register array) for storing the elements of the data file is declared like this:

reg [1:0] data[9:0];

This declares a register array with 10 entries, where every entry is 2-bit wide. This memory array will be used to load and store the data from the file.

We connect our simulation testbench with the original module as follows

test_module UUT(.inputData(inputData),
                .outputData(outputData)
                );

This statement tells to Verilog and Vivado to connect test_module ports with the variables in our simulation module. Namely, “.inputData” and “.outputData” are the ports from “test_module”, and they are connected with the variables that have the same name in our simulation file. For that purpose, we use parenthesis:

.inputData(inputData),
.outputData(outputData)

Next, we define our simulation

initial 
begin 
CLK=0;
k=0;
// read the data from the file 
$readmemb("input.data",data);
#20
for (k=0;k<10;k=k+1)
begin
 @(posedge CLK);
    inputData<=data[k];
end
end
endmodule

First, we initialize the CLK and k variables. Then, we use the following statement:

$readmemb("input.data",data);

to read data from the data file called “input.data” and to store it in the previously declared memory array called “data”. This statement will read all the entries (rows) from the data file and fill in the corresponding entries in the memory array. Then, after 20 nanosecond delay, we start the for loop and simulation:

for (k=0;k<10;k=k+1)
begin
 @(posedge CLK);
    inputData<=data[k];
end

In every iteration of the for loop, we will wait until the rising edge of the clock variable CLK, and then we will load the entry from the memory array and feed it into the input port of the module. The simulation diagram is shown below.