This is part 1 from a series of tutorials (full list is given at the bottom) describing a fast inter-arrival time pulse counter implemented in FPGA. The series covers all aspects of the design:
- counter hardware;
- streaming;
- packaging the design;
- DMA transfers;
- USB2 transfers by the device;
- receiving USB transfers by the host (a PC application)
Everything here is based on the popular Zynq hardware platform which makes all parts of the project easily interchangeable.
2. Prerequisites- basic knowledge of VHDL (you should be familiar with VHDL syntax)
- installed Vivado 2022.2 (an earlier version is also fine)
The first piece of hardware we design is the detector of pulses with the following schematic diagram
The purpose of this device is to take asynchronous (not aligned with the clock of the board) input pulses that vary in length and turn them into synchronous output pulses with duration equal to the period of the clock.
We'll go through the step-by-step instructions for designing and simulating this piece of hardware.
Launch Vivado and create a new project: File → Project → New…, then click Next to create a new project.
Choose your project name and directory, then click Next.
Keep the default settings (we are going to specify sources in the next step).
Choose the part for the Zynq board you are going to use. I'm using Z-turn Zynq 7010 board but you can easily substitute your own board for just about every step in these tutorials. Click Finish, now we are ready to add the first source file for our design.
Click the blue cross button in the Sources window, then choose the second option:
In the next window, click on 'Create file' button and type detector in the File name field. I chose VHDL as File type; this is the hardware description language for all code examples in these tutorials.
Click 'OK', then 'Finish'. You'll be prompted to make changes to the module definition. Just accept the defaults and confirm that you want to use these values (we are about to write our own VHDL code anyway).
You should now see a new design source file named 'detector' in the Sources window. If you double click on the file name, the contents will be displayed in the window on the right. Replace the file content on the right with the code provided below.
Here is the code we are going to use for detector.vhdl
-- detector.vhdl
-- detector takes an asychronous input and makes it synchronous:
-- following a rising edge of INPUT,
-- OUTPUT will be high for the next clock cycle
library ieee;
use ieee.std_logic_1164.all;
entity detector is
port(
clk : in std_logic;
input : in std_logic;
output : out std_logic
);
end detector;
architecture behavior of detector is
signal input_prev : std_logic;
begin
prDetectClk : process (clk,input)
begin
if (rising_edge(clk)) then
output <= (not input_prev) and input;
input_prev <= input;
end if; --rising_edge(clk)
end process;
end behavior;
It is now time to test out hardware design. We are going to add another VHDL file that references our detector and tests it by looking at outputs from known inputs. Click on the "Add sources" cross button again and repeat the process for adding a file named tb_detector.vhdl (tb stands for testbench). Here is the code:
-- tb_detector.vhdl
-- testbench for detector design
library ieee;
use ieee.std_logic_1164.all;
-- entity declaration for your testbench.
entity tb_detector is
end tb_detector;
architecture behavior of tb_detector is
-- component declaration
component detector is
port(
clk : in std_logic;
input : in std_logic;
output : out std_logic
);
end component;
-- definitions of constants
constant CLK_PERIOD : time := 10 ns;
constant SIGNAL_DURATION : time := 3*CLK_PERIOD;
constant SIGNAL_DELAY : time := 2 ns;
--declare inputs and initialize them to zero.
signal clk : std_logic := '0';
signal input : std_logic := '0';
--declare outputs
signal output : std_logic;
begin
-- instantiate the component
uut : detector port map (
clk => clk,
input => input,
output => output
);
-- Clock process definitions
Clk_process :process
begin
Clk <= '0';
wait for CLK_PERIOD/2;
Clk <= '1';
wait for CLK_PERIOD/2;
end process;
-- Apply inputs here.
stim_proc: process
begin
wait for CLK_PERIOD*5 + SIGNAL_DELAY; --wait for 5 clock cycles.
input <= '1';
wait for SIGNAL_DURATION;
input <= '0';
wait for CLK_PERIOD*8;
input <= '1';
wait for SIGNAL_DURATION;
input <= '0';
wait;
end process;
end;
Pay attention to what happens to the hierarchy of designs as you update the code for the testbench: you should see that Vivado is able to automatically deduce the relationship between our two hardware designs and Sources window should look like this:
You can now run the simulation from the Flow navigator on the left:
After a few seconds, you should see results of your simulation on the right. With some zooming in/out and resizing, your window should looks like this.
The detector operates as expected: it takes asynchronous input pulses of some length and converts them into clock cycle long synchronized output pulses. Note that only one output pulse is generated per rising edge of the input pulse, a new output pulse is not going to be generated until the input drops back to low.
When you are done with this example, right click on SIMULATION in the Flow Navigator window and choose 'Close Simulation'.
4. Inter-arrival time counterNext, we are going to implement a device that counts the number of clock cycles between two consecutive input pulses. Here is our block diagram
The input pulses may arrive from several sources (detectors), and so we combine them into a vector of stop_flags. Whenever a new input is detected, iatcounter will set its ready flag for one clock cycle and report the internal counter value through data output. The data output also has stop_flags appended to its higher bits so that it can reflect which detectors caused the counter to stop. If the internal counter reaches the user-defined max_count value without being reset by the stop_flag, an output event will be generated with 0s in the upper bits of data.
The operation of iatcounter is perhaps best described with an example. I'm not going to go through every detail of adding the hardware design for this device, you can follow the workflow described above for the detector. The VHDL implementation of this device and the testbench are available below among the attachments (along with the schematic of the state machine description). To carry out the simulation, you'll need to temporarily disable detector.vhdl and tb_detector.vhdl files using the menu available through right-clicking on the name of the file. You also have to ensure that tb_iatcounter file is at the top of the hierarchy under Simulation Sources.
Then run the simulation as before with the following result:
I encourage you to scroll through the testbench file and through the simulation results to see what's going on. Specifically, here we have two events (each with pulses detected in both channels, hence stop_flags = b'11' = 3). Whenever stop_flags != 0, ready signal is generated. data output always consists of the first bits copied from stop_flags followed by the value of the internal counter. Later in the simulation, single channel events are simulated; you can see how they affect the output signal.
Feel free to add your custom simulated events to experiment with the testbench.
5. Inter-arrival time collector.It is time to combine the two devices we've designed so far into a single design named iatcollector2ch (two input channels). Here is the diagram that illustrates how detector and iatcounter are interconnected.
Note that we have two instances of detector the outputs of which are combined to make the input (stop_flags) of iatcounter. Once again, the hardware description files are available in the attachments section; I'll jump straight to the testbench results for this device.
The results here are very similar to the results of the testbench for iatcounter. Notice, however, that the outputs of iatcollector are delayed by one clock cycle compared to the input of the detector. This is not a problem since our device measures inter-arrival times, and these are not affected.
6. ConclusionWe have created a hardware device that is moderately complicated and is rather useful. It can be easily extended to detect signals from more than just two channels. Next, we are going to focus on interfacing our device with the real world and packaging it into a custom IP.
7. Full list of tutorials in this series1. Pulse counter implemented in FPGA: hardware (VHDL) design
2. Pulse counter streaming using AXI interface and packaging the counter as a custom IP.
3. Full hardware design of the streaming pulse counter on Zynq.
4. Setting up DMA transfers to receive data from the streaming pulse counter.
5. USB2 bulk transfers and interrupts for high data transfer rates.
6. Working event counter with USB2 transfers and communications.
7. External (PC) testing software for receiving data from the counter.
8. VHDL resourcesThe best introduction to VHDL, in my opinion, is "Free range VHDL" by Bryan Mealy and Fabrizio Tappero available here
Comments
Please log in or sign up to comment.