This is part 3 from a series of tutorials (full list is given at the bottom) describing a fast inter-arrival time pulse counter implemented in FPGA. Using the previously designed and packaged IPs, we are going to build a full hardware block design capable of taking inputs from physical board pins, processing them, and using the DMA engine to transfer the data to the device memory.
The entire design will use a single clock source running at 100 MHz. It is possible to provide a separate, faster clock to the counter but this improvement is beyond the scope of this tutorial. I'd like to point out though that if you plan to use an external clock source, you need to use the Z-turn with Zynq-7020 chip due to availability of clock capable pins.
As implemented here our counter will have 10 ns time resolution and two input channels. We also need to specify the data width for the counter, and 16-bit seems like a good choice. The two high bits will be used for reporting channel information leaving 14 bits for the time stamp. In the absence of arriving pulses our counter will generate an event every 160.384 microseconds (or more frequently if we set a lower maximum count value as explained in the counter design tutorial). If you are interested in implementing this project with more channels or with a faster clock it may make more sense to go with a 32-bit counter.
2. Prerequisites- installed Vivado 2022.2 (an earlier version is also fine)
As a reminder, at the end of the last tutorial we packaged three IPs. It is now time to use them in a block design. You can start by creating a blank project in Vivado (which we covered in the first tutorial). Then click on the left to Create Block Design:
I named my design design_iat2ch:
We are going to add block IPs in the diagram window:
Click on the blue cross and select ZYNQ7 Processing System in the pop-up window.
This is the heart of our design, and we'll start by setting it up for our purposes. Double click on the component to enter the customization window. Then click on Presets and select Apply Configuration...
I like to start with hello_world.tcl as the preset which you can find among the files that came with your board (this simple operation is also covered in the tutorials that are provided my the manufacturer of the board, MYIR).
Click on the next item in the Page navigator: PS-PL Configuration. Under General > Enable Clock Resets select the first checkbox (this will allow us to reset AXI interfaces):
Under AXI Non Secure Establishment > GP Master AXI Interface select GP0 (which is needed to control behavior of our IPs through their Slave AXI Lite interfaces)
Under HP Slave AXI Interface select HP0 (this is how we receive the data streamed by our counter):
Then proceed to the next item in the Page navigator: PeripheralI/O Pins. In addition to SD 0 and UART 1 (which should be already checked) select USB 0. (We will use the SD card for booting the board, UART for debugging, and the USB2 port for transferring counter data to the PC).
We skip MIO configuration and proceed to Page Navigator: Clock Configuration. Under PL Fabric Clocks select CLK0 and change Requested Frequency to 100 MHz (this is the clock that will drive everything in our design, including the counter itself, resulting in 10 ns time resolution for acquired data).
Finally, we proceed straight to Page Navigator: Interrupts and select PL-PS interrupt ports > IRQ_F2P (these are needed to keep track of DMA transactions and the overflow status of the counter):
This completes the setup of ZYNQ IP; click OK to save your settings and exit. Finally, right-click on the DDR port and Make External, then repeat for Fixed I/O. Your block design should now look like this:
Now we are ready to add the block design for our counter. If you successfully completed tutorial part 2, you should see iatcollector2chSmart_axis among available designs; double click on the name to add it. Then double click on the component to enter the setup and change Tdata width to 16.
The data from the counter should be fed to a buffer (FIFO) so that it can be transferred more efficiently and doesn't get lost if there are delays in receiving it somewhere along the way. Fortunately, such buffer IP called AXI4-Stream Data FIFO is available in Vivado. I kept all settings at their default values but increased the buffer size (FIFO depth = 2048) and changed TDATA width to 2 bytes to match the width of our data.
The data will be transferred in packets, and therefore we need to generate the end of packet signal, Tlast. This can be done with AXI4-Stream Subset Converter. Again, most settings remain at their default values but I did change the data width to 2 bytes on both master and slave interfaces and enabled Tlast choosing to Generate TLAST every 128 transfers since we have 2 bytes per transfer.
Next, we add AXI Direct Memory Access which will allow us to transfer data into memory. Since we are interested in writing data only, we can disable Read Channel; I also disabled Control/Status Stream. For the Write Channel, we change the Stream Data Width to 16 and set Max Burst Size to 256.
Finally, we add the other two IPs that we've designed, cascade3 and eventSimSmart. There is no setup needed for these blocks.
Now we are going to making connections between our IPs following the flow of the signal from our counter.
We start with channel1 input to our counter which comes from an external pin through the cascade of flip-flops into our counter. If you right click on a port and choose 'Make Connection' you'll be able to connect cascade3_0/output_casc to iatCollector2chSmart_0/channel1. Another option after right clicking is to make the port external, this is what we want for input_casc, the input port of cascade3_0. Here is the result of these operations where I also renamed the external port channel1_0.
Channel2 port is a bit trickier as we want to be able to detect external pulses as well as the event simulator signal (when this component is enabled). This can be done by combining the signal from the external port with the output of the simulator using an OR gate, an element that is available in the IP library under the name Utility Vector Logic (it does require some minimal setup shown below).
Now we are ready to connect all blocks related to channel2: cascade3_1, eventSimSmart_0, util_vector_logic_0, and iatcollector2chSmart_0:
Next we connect the AXI Stream ports, from the counter to the fifo, to the subset converter and then to the dma:
Let's now Run Connection Automation at the top of the window which will greatly simplify finishing the setup. You can accept the defaults for all ports on the left (there should be little confusion since there is really only one way to connect everything), just make sure that all check boxes are clicked. Then, run connection automation again, this time accept the default for M_AXI_SG. You should now see something that looks like this.
You can use the Regenerate Layout and Optimize Routing buttons at the top and allow Vivado to make a better looking picture.
There is only one thing missing: we need to connect the interrupts. There are two sources of interrupts, the DMA (which will signal to the processors when a transfer is complete) and our counter. You'll need to add the Concat block IP from the catalog and connect axi_dma_0/s2mm_introut to xconcat_0/In0 and iatcollector2chSmart_0/fifo_overflow to xconcat_0/I1 while the output xlconcat_0/dout should be connected to processing_system7_0/IRQ_F2P.
Click on the Validate Design button at the top. There should be no errors. Our hardware design is now complete. There is a high resolution picture of the final block diagram as an attachment to this tutorial.
6. ConstraintsFinally, we need to specify which hardware pins will be used for channel1_0 and channel2_0. The information on pin names is available in the technical documentation provided with Z-turn and its cape. I am going to use pins 1 and 3 of the cape connector J5, and we can see from the picture below that they are connected to IO_B34LP1 and IO_LP34_LN1 which are named T11 and T10, respectively.
Armed with this knowledge, we proceed to add a design source (as we did before to add VHDL files) but now select Add or create constraints.
You then create a file (I named mine constr1) and accept all default settings. Now double click on constr1.xdc file in the design sources window and enter the code provided below.
# J5
# J5 pin 1
set_property PACKAGE_PIN T11 [get_ports channel1_0]
# J5 pin 3
set_property PACKAGE_PIN T10 [get_ports channel2_0]
set_property IOSTANDARD LVCMOS33 [get_ports channel1_0]
set_property IOSTANDARD LVCMOS33 [get_ports channel2_0]
7. Synthesis and implementationWe now switch to the Sources window, right click on our design and Generate Output Products, you can accept the default settings shown below. This step will take a few minutes to complete.
Next, we create HDL wrapper. You get to this option by right clicking again on the design file and selecting Create HDL Wrapper..., then Let Vivado manage wrapper and auto-update option. When complete, you should see the wrapper appear among the design files.
Finally, we click on Generate Bitstream in the Flow Navigator on the left.
This will launch synthesis and implementation which again will take a few minutes. When done, make sure to open the implemented design:
Then, select File > Export > Export Hardware..., and select the option to include bitstream:
The resulting file, design_iat2ch_wrapper.xsa, will be used in the next tutorial.
8. ConclusionWe have created a hardware design that is capable of receiving event in two channels, recording the time difference between arrivals, streaming this data to the device memory and then transferring it to a host via the USB2 port. The remaining tutorials in this series will be dedicated to writing software that runs on this hardware platform and receives data from it.
9. 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. Pulse counter on Zynq: complete hardware design.
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.
Comments
Please log in or sign up to comment.