The Eclypse Z7 recently caught my attention for its high-speed modular peripheral system with its available ADC and DAC ZMOD peripherals making it reading for analog signal processing and generation right out of the box.
This board is based on Xilinx's Zynq-7000 SoC with pins connected straight to the SYZYGY interface connectors for Digilent's ZMOD ADC and DAC peripherals. For this Hello World bringup project on the Eclypse, the hardware needs to be laid out to connect these interfaces directly to the Eclypse's DDR through the Zynq processing system's DMA (direct memory access) interface.
If this is the first time you're using a board from Digilent, you'll need to install their board preset files in your Vivado installation directory. These board preset files allow for you to automatically generate the basic aspects/peripherals of a Digilent FPGA development board within your design in Vivado so you can spend more time focusing on your own part of a custom design on the board. I'm using Vivado/Vitis version 2019.2 for this project.
Download the ZIP file of the latest version of the board files from Digilent's Github repository and extract them into your Vivado's installation directory under <install path>/Xilinx/Vivado/2019.2/data/boards/board_files
Once the board files are placed Vivado's install directory, launch Vivado and create a new project targeting the Eclypse board with your desired project name and directory.
On top of the board preset files, there is also some custom IP from Digilent for configuring the Eclypse's PMOD and ZMOD connectors. To make this custom IP available to use in the Vivado project, the directory containing this IP needs to be added as a repository in Vivado's project settings.
Download the IP from Digilent's github here and then open the Settings menu from the Flow Navigator window. Navigate to IP > Repository, and press the '+' to specify the path to the folder containing the IP from Digilent. Click 'Apply' then 'OK' to close the Settings window.
With the Digilent IP now specified to the Vivado project, it's time to start the actual hardware design by creating the Block Diagram using the 'Create Block Diagram' option from the Flow Navigator window:
As with any new block design, the first thing to add is the processor for the design. Since this is a Zynq-7000 series FPGA on the Eclypse, the Zynq PS IP block is the processor to add to the block design.
Click the '+' in the center of the empty block design or from the menu bar at the top of the block design window and type 'zynq' into the search bar of the box that appears.
Double-click on ZYNQ7 Processing System to add it to the design.
After a few moments, a green banner will appear at the top of the block design window with the option to 'Run Block Automation'. This will configure the Zynq PS IP block to match the custom configuration of the Zynq pinout on the Ecypse board.
With the Zynq PS in place, the rest of the IP for peripherals on the Eclypse can be added to the block design. For the system clock, LEDs, buttons, and PMOD connectors there is the option to do what I like to call a "quick add" for each of their corresponding IPs.
In the sources window, switch to the 'Board' tab and it'll show all of the peripherals available for this "quick add" feature, this is another artifact of the board preset files being utilized from specifying the Eclypse as the target board back when the Vivado project was created. (Side note: the ZMODs will not appear here as they need a bit more detailed configuration covered in later steps.)
Starting with the system clock, right-click and select the option for 'Auto-Connect'. A clock wizard IP block will appear in the block design with a port on its clock input pin for sys_clock from package pin E3 that the board oscillator is physically connected to. The green banner will appear again with the option for 'Run Connection Automation', this will connect the reset pin to the reset push button on the Eclypse board.
There are two RGB LEDs and two push buttons on the Eclypse board that can be made available to any software design via GPIO. To connect them via AXI GPIO to the Zynq PS, right click on them from the Board options tab and again select the 'Auto Connect' option. Run the option for Connection Automation that may pop up after the AXI GPIO block is added to the design.
The last two peripherals available for the quick add option are the two PMOD connectors on the bottom side of the Eclypse board. The auto connection option however isn't available for the PMODs since you have to select the appropriate IP for the the PMOD connector based upon the PMOD peripheral you will be using on the port. The IP blocks for the PMOD peripherals are part of what's in the Digilent IP repo added to the project in the first steps of this project.
I'm a fan of OLED displays for troubleshooting and general status outputs, and Digilent has one available in their catalog of PMOD peripheral boards. I chose the SSD1306 based PMOD OLED.
Right-click on PMOD Connector JA and select the 'Connect Board Component' option. In the search menu type "OLED" and select the Pmod_OLED IP block.
Again, run the connection automation option that appears after the PMOD IP is added to the block design.
Since I haven't chosen a specific PMOD peripheral for the second PMOD connector, I just added a general purpose IO IP block to it. Again, right-click on PMOD Connector JB and select the 'Connect Board Component' option. Then in the search menu, just search "gpio" and select the Pmod_out IP block.
Run the connection automation option that appears after the PMOD IP is added to the block design.
Before adding the ZMOD IP to the block design, the Zynq PS configuration needs a few tweaks, the first of which being to enable interrupt inputs from the programmable logic to the Zynq PS.
Double-click on the Zynq IP block to open its configuration window. Select the Interrupts tab and check the box for 'Fabric Interrupts', which will then allow you to select the option for IRQ_F2P[15:0] under PL to PS interrupts.
A second clock output from the Zynq PS also needs to be added since there will be too many peripherals for a single clock source to support.
Under the Clock Configuration tab, select the PL Fabric Clocks menu and check the box for FCLK_CLK1 and set its value to 100 MHz.
The ZMODs will need a high performance (high speed) AXI port for the DMA to read and write data from/to the DDR. To enable one of the four available, navigate to PS-PL configuration then check the box next to S AXI HP0 interface under the HP Slave AXI Interface menu.
After doing so, save this new configuration and close the window by clicking 'OK' on the bottom right side of the window.
The ZMODs will also require some clocking configuration tweaks to the Clocking Wizard IP block. Again, double-click on the Clocking Wizard IP block to open its configuration window.
Under the Output Clocks tab, enable clk_out1. Then set clk_out1 to 400MHz. Unselect the 'locked' option and check the reset type to active low:
If the connection automation added a reset port for the reset of the clock wizard, delete it and instead connect it to FCLK_RESET0 on the Zynq PS:
Finally, the design is ready to start adding the IP for the ADC and DAC ZMOD peripherals. Starting with the ADC ZMOD, there are two available IP blocks to be added to the block design for the ZMOD ADC: the ZmodADC1410_Controller and AXI_ZmodADC1410_v1.0 IP blocks.
Add the AXI_ZmodADC1410_v1.0 IP block first:
The green banner for connection automation will appear and when you select to run it, first change the clock source for the slave interface to FCLK_CLK1, as this will be the clock source dedicated to supporting the ZMOD interface parts of the design.
Next add the ZmodADC1410_Controller IP block:
The connection automation for it initially will just be for the ADC reference clock. This is will be the 400 MHz clock we configured in the clocking wizard IP. Select it as the clock source from the drop down menu then click OK to run the connection automation.
Double-click on the ZmodADC1410_Controller IP block to open its configuration window and enable the options for ExtRelayConfigEn, ExtCalibEn, and ExtCmdInterfaceEn:
Click 'OK' to save and close the configuration window and make the following connections manually between the ZmodADC1410_Controller IP and the AXI_ZmodADC1410_v1.0 IP blocks:
A majority of the output ports on the ZmodADC1410_Controller are what connect directly to the pins on the FPGA that are routed to the SYZYGY connector. To achieve this, they need to be connected to external ports in the block design.
To achieve this, you can right-click on a pin name and select the option to "Make External" or single click on a pin name to select it the use the keyboard shortcut cntrl+T.
Make the following output ports on the ZmodADC1410_Controller IP external:
Make the following input ports on the ZmodADC1410_Controller IP external:
The option for connection automation will have appeared again by this point. This time, for the SysClk clock ports on the ADC ZMOD IPs. Ensure the clock source for each is set to FCLK_CLK1:
For some reason the data port widths between the two ADC ZMOD IPs don't match. These ports are how the controller IP outputs the digital data to send to the interface IP from the ADC's output. To fix this, add two xlslice IP blocks to the design to convert the data width outputs.
Double-click on the xlslice IPs to open their configuration window and configure each to the following:
Then manually connect as the highlighted wires below show:
Add an AXI Direct Memory Access IP block for the AXI_ZmodADC1410_v1.0 to write the incoming digital data from the ADC to the DDR. Configure it as a write only DMA:
Again, when the connection automation appears, ensure the clock source for the slave interface on the AXI Lite port is set to FCLK_CLK1:
And all of the AXI HP0 clock sources are also set to FCLK_CLK1:
Then manually connect the AXIS_S2MM port from the AXI_ZmodADC1410_v1.0 IP to the S_AXIS_S2MM port of the write-only DMA IP block:
Run the available connection automation for the AxiStreamClk on the AXI_ZmodADC1410_v1.0 IP and set it to FCLK_CLK1:
This covers the ADC ZMOD part of the block design for now, so we can move on to the DAC ZMOD IP blocks. There are also two available IP blocks to be added to the block design for the ZMOD DAC: the ZmodDAC1411_Controller_v1_0 and AXI_ZmodDAC1411_v1_0_v1_0 IP blocks.
Add the AXI_ZmodDAC1411_v1_0_v1_0 IP block first:
Run the first connection automation for the AXI Lite interface, setting its clock source to FCLK_CLK1:
Then manually connect the IRst_n port to the same reset line and the IRst_n port on the ADC ZMOD IP:
Add the DAC ZMOD controller IP block to the design, then double-click to open its configuration menu and select each of the check boxes:
Manually connect the following pins between the two DAC ZMOD IPs as shown below:
Again, the option for connection automation will have appeared again for the SysClk on the DAC interface IP. Ensure it is set to FCLK_CLK1:
Manually connect DacClk to SysClk and make the appropriate output signals external:
Add an AXI Direct Memory Access IP block to the design and configure it as a read only for the IP blocks to pull the digital data from to send to the DAC:
Run the connection automation for the DMA ports, ensuring the clock sources for everything are set to FCLK_CLK1:
Then manually connect the M_AXIS_MM2S port from the read-only DMA to the S_AXIS_MM2S port on the ZMOD DAC interface IP:
Run the connection automation for its AxiStreamClk and set the clock source to FCLK_CLK1:
At this point, all of the IP blocks with interrupt outputs have been added to the design, and to connect them all to the Zynq PS, they need to be concatenated. So add a Concat IP block and configure it for 6 input ports:
Connect each of the interrupt outputs on the various IP blocks to the inputs of the concat IP and the output of the concat IP to the interrupt input of the Zynq PS:
With the block design completed at this point, run validation to check it for any errors or critical warnings (regular level warnings can be ignored). The button for validation the design is at the top of the block design window and looks like a box with a check mark in it.
After a successful validation, save the block design and create an HDL wrapper for it to instantiate it in the project:
Select the option to allow Vivado to auto manage and update the HDL wrapper.
With the block design and its top level wrapper completed, the constraints need to be added to the design to map the pin names to the appropriate package pins of the FPGA chip on the Eclypse board, as well as specify the clocks and timing for the design.
From the Flow Navigator window, select the option to 'Add Sources' then select 'Add or create constraints':
Click 'Create File' and give it the desired name. After you click ok, you'll see it in the list with its directory path. Click 'Finish' to generate the new file:
The master constraints file for the Eclypse board can be found in Digilent's github repository here. This is what I used, along with the schematic for the Eclypse board, to derive my own constraints for the Zmod project. Open the new constraints file generated from the last step and copy+paste the following constraints into it:
# 125MHz Clock from Ethernet PHY
set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { sys_clock }]; #IO_L12P_T1_MRCC Sch=sysclk
create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports { sys_clock }];
# Syzygy Port A
set_property IOSTANDARD DIFF_SSTL18_I [get_ports -filter { name =~ adcClkIn* }]
set_property -dict { PACKAGE_PIN N20 } [get_ports { adcClkIn_n_0 }]; #IO_L14N_T2_SRCC Sch=syzygy_a_c2p_clk_n
set_property -dict { PACKAGE_PIN N19 } [get_ports { adcClkIn_p_0 }]; #IO_L14P_T2_SRCC Sch=syzygy_a_c2p_clk_p
set_property SLEW SLOW [get_ports -filter { name =~ adcClkIn* }]
set_property -dict { PACKAGE_PIN T17 IOSTANDARD LVCMOS18 } [get_ports { sCh1CouplingL_0 }]; #IO_L21N_T3_DQS Sch=syzygy_a_d_n[0]
set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS18 } [get_ports { sCh1CouplingH_0 }]; #IO_L21P_T3_DQS Sch=syzygy_a_d_p[0]
set_property -dict { PACKAGE_PIN T19 IOSTANDARD LVCMOS18 } [get_ports { sCh2CouplingL_0 }]; #IO_L22N_T3 Sch=syzygy_a_d_n[1]
set_property -dict { PACKAGE_PIN R19 IOSTANDARD LVCMOS18 } [get_ports { sCh2CouplingH_0 }]; #IO_L22P_T3 Sch=syzygy_a_d_p[1]
set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS18 } [get_ports { sCh2GainL_0 }]; #IO_L20N_T3 Sch=syzygy_a_d_n[3]
set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS18 } [get_ports { sCh2GainH_0 }]; #IO_L20P_T3 Sch=syzygy_a_d_p[3]
set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS18 } [get_ports { sCh1GainL_0 }]; #IO_L19N_T3_VREF Sch=syzygy_a_d_n[5]
set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS18 } [get_ports { sCh1GainH_0 }]; #IO_L19P_T3 Sch=syzygy_a_d_p[5]
set_property -dict { PACKAGE_PIN K21 IOSTANDARD LVCMOS18 } [get_ports { sRelayComL_0 }]; #IO_L9N_T1_DQS Sch=syzygy_a_d_n[7]
set_property -dict { PACKAGE_PIN J20 IOSTANDARD LVCMOS18 } [get_ports { sRelayComH_0 }]; #IO_L9P_T1_DQS Sch=syzygy_a_d_p[7]
set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS18 } [get_ports { sADC_Sclk_0 }]; #IO_L23N_T3 Sch=syzygy_a_d_n[2]
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS18 } [get_ports { sADC_SDIO_0 }]; #IO_L23P_T3 Sch=syzygy_a_d_p[2]
set_property -dict { PACKAGE_PIN M21 IOSTANDARD LVCMOS18 } [get_ports { sADC_CS_0 }]; #IO_L15P_T2_DQS Sch=syzygy_a_s[26]
set_property -dict { PACKAGE_PIN M22 } [get_ports { adcSync_0 }]; #IO_L15N_T2_DQS Sch=syzygy_a_s[27]
set_property IOSTANDARD LVCMOS18 [get_ports -filter { name =~ adcSync* }]
set_property DRIVE 4 [get_ports -filter { name =~ adcSync* }]
set_property SLEW SLOW [get_ports -filter { name =~ adcSync* }]
set_property -dict { PACKAGE_PIN M19 } [get_ports { DcoClk_0 }]; #IO_L13P_T2_MRCC Sch=syzygy_a_p2c_clk_p
set_property IOSTANDARD LVCMOS18 [get_ports -filter { name =~ DcoClk* }]
set_property -dict { PACKAGE_PIN N22 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[0] }]; #IO_L16P_T2 Sch=syzygy_a_s[24]
set_property -dict { PACKAGE_PIN L21 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[1] }]; #IO_L10P_T1 Sch=syzygy_a_s[22]
set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[2] }]; #IO_L24N_T3 Sch=syzygy_a_d_n[4]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[3] }]; #IO_L7P_T1 Sch=syzygy_a_d_p[6]
set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[4] }]; #IO_L7N_T1 Sch=syzygy_a_d_n[6]
set_property -dict { PACKAGE_PIN L19 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[5] }]; #IO_L12N_T1_MRCC Sch=syzygy_a_s[16]
set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[6] }]; #IO_L12P_T1_MRCC Sch=syzygy_a_s[18]
set_property -dict { PACKAGE_PIN L22 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[7] }]; #IO_L10N_T1 Sch=syzygy_a_s[20]
set_property -dict { PACKAGE_PIN K20 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[8] }]; #IO_L11N_T1_SRCC Sch=syzygy_a_s[17]
set_property -dict { PACKAGE_PIN P16 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[9] }]; #IO_L24P_T3 Sch=syzygy_a_d_p[4]
set_property -dict { PACKAGE_PIN K19 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[10] }]; #IO_L11P_T1_SRCC Sch=syzygy_a_s[19]
set_property -dict { PACKAGE_PIN J22 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[11] }]; #IO_L8N_T1 Sch=syzygy_a_s[21]
set_property -dict { PACKAGE_PIN J21 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[12] }]; #IO_L8P_T1 Sch=syzygy_a_s[23]
set_property -dict { PACKAGE_PIN P22 IOSTANDARD LVCMOS18 } [get_ports { dADC_Data_0[13] }]; #IO_L16N_T2 Sch=syzygy_a_s[25]
#set_property -dict { PACKAGE_PIN M20 } [get_ports { syzygy_a_p2c_clk_n }]; #IO_L13N_T2_MRCC Sch=syzygy_a_p2c_clk_n
create_clock -period 10.000 -name DcoClk_0 -waveform {0.000 5.000} [get_ports DcoClk_0]
create_generated_clock -name adcClkIn_p_0 -source [get_pins design_1_i/ZmodADC1410_Controll_0/U0/InstADC_ClkODDR/C] -divide_by 1 [get_ports adcClkIn_p_0]
set_input_delay -clock [get_clocks DcoClk_0] -clock_fall -min -add_delay 3.240 [get_ports {dADC_Data_0[*]}]
set_input_delay -clock [get_clocks DcoClk_0] -clock_fall -max -add_delay 5.440 [get_ports {dADC_Data_0[*]}]
set_input_delay -clock [get_clocks DcoClk_0] -min -add_delay 3.240 [get_ports {dADC_Data_0[*]}]
set_input_delay -clock [get_clocks DcoClk_0] -max -add_delay 5.440 [get_ports {dADC_Data_0[*]}]
# Syzygy Port B
set_property -dict { PACKAGE_PIN Y19 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[0] }]; #IO_L11P_T1_SRCC Sch=syzygy_b_s[19]
set_property -dict { PACKAGE_PIN Y18 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[1] }]; #IO_L12P_T1_MRCC Sch=syzygy_b_s[18]
set_property -dict { PACKAGE_PIN AB22 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[2] }]; #IO_L7N_T1 Sch=syzygy_b_d_n[6]
set_property -dict { PACKAGE_PIN AB20 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[3] }]; #IO_L10N_T1 Sch=syzygy_b_s[20]
set_property -dict { PACKAGE_PIN AA18 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[4] }]; #IO_L12N_T1_MRCC Sch=syzygy_b_s[16]
set_property -dict { PACKAGE_PIN AA19 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[5] }]; #IO_L11N_T1_SRCC Sch=syzygy_b_s[17]
set_property -dict { PACKAGE_PIN Y21 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[6] }]; #IO_L9N_T1_DQS Sch=syzygy_b_d_n[7]
set_property -dict { PACKAGE_PIN Y20 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[7] }]; #IO_L9P_T1_DQS Sch=syzygy_b_d_p[7]
set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[8] }]; #IO_L19N_T3_VREF Sch=syzygy_b_d_n[5]
set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[9] }]; #IO_L19P_T3 Sch=syzygy_b_d_p[5]
set_property -dict { PACKAGE_PIN AB15 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[10] }]; #IO_L24N_T3 Sch=syzygy_b_d_n[3]
set_property -dict { PACKAGE_PIN AB14 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[11] }]; #IO_L24P_T3 Sch=syzygy_b_d_p[3]
set_property -dict { PACKAGE_PIN W13 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[12] }]; #IO_L20N_T3 Sch=syzygy_b_d_n[1]
set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Data_0[13] }]; #IO_L20P_T3 Sch=syzygy_b_d_p[1]
set_property -dict { PACKAGE_PIN W16 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Clkin_0 }]; #IO_L14P_T2_SRCC Sch=syzygy_b_c2p_clk_p
set_property -dict { PACKAGE_PIN W17 IOSTANDARD LVCMOS18 } [get_ports { sDAC_ClkIO_0 }]; #IO_L13P_T2_MRCC Sch=syzygy_b_p2c_clk_p
set_property -dict { PACKAGE_PIN Y14 IOSTANDARD LVCMOS18 } [get_ports { sDAC_SDIO_0 }]; #IO_L22P_T3 Sch=syzygy_b_d_p[4]
set_property DRIVE 4 [get_ports sDAC_SDIO_0]
set_property -dict { PACKAGE_PIN AA14 IOSTANDARD LVCMOS18 } [get_ports { sDAC_CS_0 }]; #IO_L22N_T3 Sch=syzygy_b_d_n[4]
set_property DRIVE 4 [get_ports sDAC_CS_0]
set_property -dict { PACKAGE_PIN AA13 IOSTANDARD LVCMOS18 } [get_ports { sDAC_SCLK_0 }]; #IO_L23N_T3 Sch=syzygy_b_d_n[2]
set_property DRIVE 4 [get_ports sDAC_SCLK_0]
set_property -dict { PACKAGE_PIN W15 IOSTANDARD LVCMOS18 } [get_ports { sDAC_SetFS1_0 }]; #IO_L21P_T3_DQS Sch=syzygy_b_d_p[0]
set_property -dict { PACKAGE_PIN Y15 IOSTANDARD LVCMOS18 } [get_ports { sDAC_SetFS2_0 }]; #IO_L21N_T3_DQS Sch=syzygy_b_d_n[0]
set_property -dict { PACKAGE_PIN Y13 IOSTANDARD LVCMOS18 } [get_ports { sDAC_Reset_0 }]; #IO_L23P_T3 Sch=syzygy_b_d_p[2]
set_property -dict { PACKAGE_PIN AA22 IOSTANDARD LVCMOS18 } [get_ports { sDAC_EnOut_0 }]; #IO_L7P_T1 Sch=syzygy_b_d_p[6]
create_generated_clock -name sDAC_Clkin_0 -source [get_pins design_1_i/ZmodDAC1411_Controll_0/U0/InstDAC_ClkinODDR/C] -divide_by 1 [get_ports sDAC_Clkin_0]
create_generated_clock -name sDAC_ClkIO_0 -source [get_pins design_1_i/ZmodDAC1411_Controll_0/U0/InstDAC_ClkIO_ODDR/C] -divide_by 1 [get_ports sDAC_ClkIO_0]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -clock_fall -min -add_delay -0.100 [get_ports {sDAC_Data_0[*]}]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -clock_fall -max -add_delay 0.250 [get_ports {sDAC_Data_0[*]}]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -min -add_delay -0.100 [get_ports {sDAC_Data_0[*]}]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -max -add_delay 0.150 [get_ports {sDAC_Data_0[*]}]
Once the modifications to the constraints file have been saved, the design is ready to be synthesized then place and routed.
Run synthesis by selecting the option from the Flow Navigator or pressing F11. Once successfully completed, open the Synthesized design to verify there are no critical warnings.
Close the synthesized design and select the option to run implementation from the Flow Navigator.
This design had a couple of timing failures on the data lines going to the DAC in that their hold times weren't long enough. Adding some delay in the constraints file to the lines (which is what the last four lines in my constraints file are doing) helped but couldn't fully solve the issue.
My next solution would be to add some extra gating for the signals to pass through to extend their hold times, but since I will be adding on to this design in my next project and that will change the routing/timing again I'm going to hold off on fully fixing this timing issue for now. The worst case scenario I see this causing at the moment is some of the test data going to the DAC having a few bits corrupted here and there, which is acceptable for a test project such as this.
Finally generate the bitstream for the design using the "generate bitstream" option in the Flow Navigator. Once successfully generated, export the the hardware design using the Export option under the File menu:
Be sure to check the box to include the bitstream in the hardware and specify your desired output location. I like to leave it in the default location which is the location project directory for the Vivado project.
This step exports the entire hardware design we just created as Xilinx's archive file format, XSA. This is what Xilinx's software tools need to import the hardware design and ultimate configure the corresponding software on top of. Since this is a first bring up project for the Eclypse board, I'm using Vitis to create a standalone (bare-metal) Hello World application.
Vitis can be launched directly from Vivado under the Tools menu:
The launch will ask for a workspace directory to be specified:
Click browse and select your desired workspace directory. I like to create a new folder in the Vivado project directory titled 'vitis_workspace' to use:
after navigating to the directory, click OK to return to the launcher:
Finally, click Launch:
Vitis does not automatically generate a hardware platform like its predecessor XSDK did, so the first step in any new Vitis project is to create a new Platform Project:
Specify a name for the Platform Project and click Next:
Select the option to create from a hardware specification XSA file and click Next:
Specify the location of the XSA file exported from Vivado in earlier steps, then select standalone for the operation system and the ARM core 0 of the Zynq chip. Also ensure the generate boot components option is selected before clicking Finish:
With the hardware platform in place, use the New menu and select 'New Application Project...':
Specify a name for the application project and click Next:
Select the custom XSA hardware exported from Vivado as the platform and click Next:
Leave the domain set to standalone_domain and select C++ for the programming language. I normally prefer C over C++ for my embedded projects like this but I noticed that the ZMOD software library was written in C++.
Finally, select the Empty Application template from the list of application templates and click Finish.
After everything has generated, build the project by right clicking on the application project in the Explorer window and selecting 'Build' or use the keyboard shortcut ctrl+B:
With the base level application framework in place, the software library for the ZMODs needs to be imported to allow for the application to ultilize the ZMOD IP blocks in the hardware block design. Clone or download the master branch of the ZMOD library from Digilent's GitHub here.
Right-click on the src folder in the application file structure in the Explorer window and select the option to 'Import Sources...':
Browse to the location of the downloaded ZMOD library and select the folders to import into the application project.
With the ZMOD software library imported, the main file needs to be added. Create a new file in the application project's src folder:
Name the file main.c:
Build the application project again to check that everything has been imported properly.
I initially got a few build errors about "unknown type bool" on a few of the ZMOD library source files. To correct this, I simply added the standard include file for boolean to the beginning of the files that threw the error:
#include <stdbool.h>
I added this include to reg.c, intc.c, flash.c, and dma.c in the baremetal folder of the zmodlib directory to achieve a successful application project build.
Open main.c and add the following code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "./zmodlib/Zmod/zmod.h"
#include "./zmodlib/ZmodADC1410/zmodadc1410.h"
#include "./zmodlib/ZmodDAC1411/zmoddac1411.h"
#define TRANSFER_LEN 0x400
// ZMOD ADC parameters
#define ZMOD_ADC_BASE_ADDR XPAR_AXI_ZMODADC1410_0_S00_AXI_BASEADDR
#define DMA_ADC_BASE_ADDR XPAR_AXI_DMA_ADC_BASEADDR
#define IIC_BASE_ADDR XPAR_PS7_I2C_1_BASEADDR
#define FLASH_ADDR_ADC 0x30
#define ZMOD_ADC_IRQ XPAR_FABRIC_AXI_ZMODADC1410_0_LIRQOUT_INTR
#define DMA_ADC_IRQ XPAR_FABRIC_AXI_DMA_ADC_S2MM_INTROUT_INTR
//ZMOD DAC parameters
#define ZMOD_DAC_BASE_ADDR XPAR_AXI_ZMODDAC1411_V1_0_0_BASEADDR
#define DMA_DAC_BASE_ADDR XPAR_AXI_DMA_DAC_BASEADDR
#define FLASH_ADDR_DAC 0x31
#define DMA_DAC_IRQ XPAR_FABRIC_AXI_DMA_DAC_MM2S_INTROUT_INTR
#define IIC_BASE_ADDR XPAR_PS7_I2C_1_BASEADDR
/*
* Simple ADC test, puts the ADC in the test mode (ramp),
* performs an acquisition under specific trigger conditions
* and verifies the acquired data to be consistent with these conditions.
*/
void testZMODADC1410Ramp_Auto()
{
ZMODADC1410 adcZmod(ZMOD_ADC_BASE_ADDR, DMA_ADC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_ADC,
ZMOD_ADC_IRQ, DMA_ADC_IRQ);
if(adcZmod.autoTestRamp(1, 0, 0, 4, TRANSFER_LEN) == ERR_SUCCESS)
{
xil_printf("Success autotest ADC ramp\r\n");
}
else
{
xil_printf("Error autotest ADC ramp\r\n");
}
}
/*
* Format data contained in the buffer and sends it over UART.
* It displays the acquired value (in mV), raw value (as 14 bits hexadecimal value)
* and time stamp within the buffer (in time units).
* @param padcZmod - pointer to the ZMODADC1410 object
* @param acqBuffer - the buffer containing acquired data
* @param channel - the channel where samples were acquired
* @param gain - the gain for the channel
* @param length - the buffer length to be used
*/
void formatADCDataOverUART(ZMODADC1410 *padcZmod, uint32_t *acqBuffer, uint8_t channel, uint8_t gain, size_t length)
{
char val_formatted[15];
char time_formatted[15];
uint32_t valBuf;
int16_t valCh;
float val;
xil_printf("New acquisition ------------------------\r\n");
xil_printf("Ch1\tRaw\tTime\t\r\n");
for (size_t i = 0; i < length; i++)
{
valBuf = acqBuffer[i];
valCh = padcZmod->signedChannelData(channel, valBuf);
val = padcZmod->getVoltFromSignedRaw(valCh, gain);
padcZmod->formatValue(val_formatted, 1000.0*val, "mV");
if (i < 100)
{
padcZmod->formatValue(time_formatted, i*10, "ns");
}
else
{
padcZmod->formatValue(time_formatted, (float)(i)/100.0, "us");
}
xil_printf("%s\t%X\t%s\r\n", val_formatted, (uint32_t)(valCh&0x3FFF), time_formatted);
}
}
/*
* Simple ADC test, acquires data and sends it over UART.
* @param channel - the channel where samples will be acquired
* @param gain - the gain for the channel
* @param length - the buffer length to be used
*/
void adcDemo(uint8_t channel, uint8_t gain, size_t length) {
ZMODADC1410 adcZmod(ZMOD_ADC_BASE_ADDR, DMA_ADC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_ADC,
ZMOD_ADC_IRQ, DMA_ADC_IRQ);
uint32_t *acqBuffer;
adcZmod.setGain(channel, gain);
while(1)
{
acqBuffer = adcZmod.allocChannelsBuffer(length);
adcZmod.acquireImmediatePolling(acqBuffer, length);
formatADCDataOverUART(&adcZmod, acqBuffer, channel, gain, length);
adcZmod.freeChannelsBuffer(acqBuffer, length);
sleep(2);
}
}
/*
* Simple DAC test, using simple ramp values populated in the buffer.
* @param offset - the voltage offset for the generated ramp
* @param amplitude - the amplitude for the generated ramp
* @param step - the step between two generated samples
* @param channel - the channel where samples will be generated
* @param frequencyDivider - the output frequency divider
* @param gain - the gain for the channel
*/
void dacRampDemo(float offset, float amplitude, float step, uint8_t channel, uint8_t frequencyDivider, uint8_t gain)
{
ZMODDAC1411 dacZmod(ZMOD_DAC_BASE_ADDR, DMA_DAC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_DAC, DMA_DAC_IRQ);
uint32_t *buf;
float val;
uint32_t valBuf;
int16_t valRaw;
size_t length = (int)(amplitude/step) << 2;
int i;
if (length > ((1<<14) - 1))
{
// limit the length to maximum buffer size (1<<14 - 1)
length = ((1<<14) - 1);
// adjust step
step = amplitude/(length>>2);
}
buf = dacZmod.allocChannelsBuffer(length);
dacZmod.setOutputSampleFrequencyDivider(frequencyDivider);
dacZmod.setGain(channel, gain);
i = 0;
// ramp up
for(val = -amplitude; val < amplitude; val += step)
{
valRaw = dacZmod.getSignedRawFromVolt(val + offset, gain);
valBuf = dacZmod.arrangeChannelData(channel, valRaw);
buf[i++] = valBuf;
}
// ramp down
for(val = amplitude; val > -amplitude; val -= step)
{
valRaw = dacZmod.getSignedRawFromVolt(val + offset, gain);
valBuf = dacZmod.arrangeChannelData(channel, valRaw);
buf[i++] = valBuf;
}
// send data to DAC and start the instrument
dacZmod.setData(buf, length);
dacZmod.start();
dacZmod.freeChannelsBuffer(buf, length);
}
int main()
{
init_platform();
xil_printf("Hello Eclypse Z7!\n\r");
xil_printf("Testing ADC ZMOD...\n\r");
adcDemo(0, 0, TRANSFER_LEN);
xil_printf("Testing DAC ZMOD...\n\r");
dacRampDemo(2, 3, 0.01, 0, 2, 1);
cleanup_platform();
return 0;
}
Save and close the main file then create the boot binary file for the application. Right-click on the application project name in the Explorer window and select the option to 'Create Boot Image'.
Verify the first stage bootloader elf file, hardware bitstream, and application elf file are included in the boot image partitions then click 'Create Image'.
Once the boot image (BOOT.BIN) has been generated, program it into the flash of the Eclypse. Again right-click on the application project name in the Explorer window and select the option to 'Program Flash'.
Verify the settings below and check the option to verify after flash then click 'Program'.
Vitis has a built in serial terminal that can be brought up from the Window menu under 'Show View..'.
Search for serial and select 'Vitis Serial Terminal' then click 'Open'.
Connect a USB cable to J6 port on the Eclypse board then connect to its serial port from the Vitis serial terminal window, using the green plus button.
Select the serial port number of the Eclypse board and set the baud rate to 115200.
Use SMA cables to connect channel one of the ADC ZMOD to channel one of the ZMOD DAC and channel two to channel two:
Watch the serial terminal as the application sends data to the DAC to output a ramp wave and its read in by the ADC, the result of which is being output via the serial terminal.
This project serves as a base starting point on the Eclypse with the ADC and DAC ZMODs that can be added on for future projects.
Comments