As already mentioned in The Digilent Arty S7: An Unexpected journey - Part 1 - The Board, the heart of the Arty S7 is the Spartan-7 FPGA. In this article, I will explore the tools available to work with the FPGA. As my focus is the Arty S7 board, it goes without saying that when I talk of FPGAs, the reference is to the Xilinx’s Spartan-7 (although many considerations might apply to FPGAs in general).


If you are approaching those devices for the first time, there is a nice short introduction to the Xilinx’s FPGAs on Element14 essentials. Also, to get an idea of the world of FPGAs and the alternatives available, there are some interesting discussions in the FPGA Group.

While FPGAs, in the past, have been predominantly used in industrial and commercial designs, lately there has been a significant push for this technology to make its way into the makers’ space.

But to be successful in the makers’ space, any device needs to be accompanied by:

  • a good tool set, to help streamline the development of a solution based on device;
  • a wide selection of learning resources & projects;
  • an active community of adopters.


I purposely avoided to mention time and money in the list: money is typically the most limited resource (sigh!) for many makers and hobbyists, with time very close second. While all of the above doesn’t necessarily need to be freely accessible, it does help if they are open-standard based and free/open license based.

For the Arty S7 board, the choice of development tools is limited to Xilinx’s Integrated Development Environments, specifically the Vivado® HLx Design Suite WebPack™, a development environment for the FPGA hardware design, which is free for the Spartan-7 FPGA family. If the hardware design includes also the MicroBlaze soft core microprocessor, you can also take advantage of the Xilinx Software Development Kit for the development of the software side of your project. One important note: in order to be able to download and install any of the Xilinx tools, you are required to create an account on their website first.


For my project, I am using a Linux Debian 9 distribution as OS for my development, and the Vivado Design Suite 2017.4. Before installing Vivado, you need to make sure you read the list of requirements for the system, as the tool is quite demanding in terms of resources. In particular, check the recommendations for the minimum amount of RAM required. The Spartan-7 are not listed there, but probably you can apply the same recommendations as for the Artix-7/Zynq-7000. I have tested Vivado on Intel i7 machines, one equipped with 8GB RAM and 1TB HD (normal), and the other with 16GB of RAM and 500GB HD (SSD), and the suite had good performances when running the full build (synthesis-implemetation-bitstream generation). If you use less RAM (4GB), be prepared to hit problems and make sure you got plenty of coffee at hand before you start…


As for disk space requirement, it can vary, depending on what you choose to install. The screenshot shows my choices for the install, and it resulted on an installed size on disk of roughly 24GB (to reduce the install size, I removed the support for Zynq and the UltraScale platforms, as I’m using it only for the Spartan-7 FPGAs. This way I managed to save about 8GB).


Vivado Install Choices



Although not strictly necessary, I recommend installing the Documentation Navigator. Xilinx produces an incredible amount of documentation (Guides, Application Notes), and navigating through all this stuff can be daunting, especially when you have to search it on the internet. I found the Documentation Navigator extremely helpful, offering a clean and friendly way of accessing the documents (see the screenshot below), although at expense of some disk space.


Documentation Navigator



The installation will take some time, especially if you don’t have a fast internet connection available. Once Vivado is installed, the next step is to add the support for the Arty S7-50 board. Such support provides Vivado with the system-level information about the board (Spartan-7 pin mapping to the board interfaces), allowing the board to be selected as target when creating a new project.


Digilent has published several guides about Arty S7 on their website: the one we are interested here is “Installing Vivado and Digilent Board Files”, which goes through the installation and configuration process. The board files can be found on Digilent's GitHub.


Vivado is now ready to be used for the first project. Being new to the tool, using the graphical user interface (GUI) is definitely the easiest way to work with it, but this is not the only way. Vivado supports the tcl scripting language, which means that, investing some time learning the language and the commands provided by the suite, a project can be created and built just using a script, without the need to launch the GUI at all. This allows automation of the build, and also contributes to save some precious RAM, better utilised for the synthesis of the hardware rather than showing the GUI.


To get started, there are couple of demo projects available on Digilent’s website. Lets load one, to get acquainted with the typical flow used to go from VHDL/Verilog modules and IP cores to the downloading the bitstream onto the board.

The demo used is the Arty S7 XADC. Basically, this project uses the 1MSPS analog-to-digital converter that comes with the Spartan-7 FPGA to sample the voltage applied to 2 pins of the Arduino compatible header (the voltage needs to be limited to 0V-3.3V, to avoid damaging the FPGA pins). The LEDs on the boards will act as rudimental meter (0V – all LEDs off, with the LEDs turning on one at time when the voltage increases, to reach the 3.3V – all LEDs on). The selection of which pins of the header to use as ADC input can be made by using a specific combination for the 4 switches on the board.


The project main file is the Verilog top module XADCdemo.v, where all the logic is defined. Looking at the code below, you can see the module uses also an IP Core (xadc_wiz_0) for the ADC, created using the XADC Wizard. This is one of the great features of Vivado: the IP Integrator with its IP Library. IP cores are the “CoTS” of the digital world: they are ready packaged modules, which offer a specific function that users can customise to their needs. Many of them are free to use in your design, while some need to be purchased.




`timescale 1ns / 1ps 
// Company: Digilent inc. 
// Engineer: Samuel Lowe 
// Engineer Email: 
// Create Date: 05/28/2015 03:26:51 PM 
// Design Name: XADCdemo 
// Module Name: XADCdemo:  
// Target Devices: ARTY 
// Tool Versions: Vivado 15.1 
// Description: A top level design for reading values off of the XADC Pmod port of the ARTY FPGA 
// Dependencies:  
// Revision: 3/3/2015 
// Revision 0.01 - File Created 
// Additional Comments: 
module XADCdemo( 
   input CLK100MHZ, 
   input vp_in, 
   input vn_in, 
   input vauxp0, 
   input vauxn0, 
   input vauxp1, 
   input vauxn1, 
   input vauxp2, 
   input vauxn2, 
   input vauxp3, 
   input vauxn3, 
   input vauxp8, 
   input vauxn8, 
   input vauxp9, 
   input vauxn9, 
   input vauxp10, 
   input vauxn10, 
   input vauxp11, 
   input vauxn11, 
   input [3:0] sw, 
   output reg [5:0] LED 
   wire enable;   
   wire ready; 
   reg ready_d1; 
   wire ready_rising; 
   wire ready_falling; 
   wire [15:0] data;    
   reg [6:0] Address_in;      
   //xadc instantiation connect the eoc_out .den_in to get continuous conversion 
    xadc_wiz_0 xadc 
        .daddr_in(Address_in),            // Address bus for the dynamic reconfiguration port 
        .dclk_in(CLK100MHZ),             // Clock input for the dynamic reconfiguration port 
        .den_in(enable),              // Enable Signal for the dynamic reconfiguration port 
        .di_in(0),               // Input data bus for the dynamic reconfiguration port 
        .dwe_in(0),              // Write Enable for the dynamic reconfiguration port 
        .reset_in(0),            // Reset signal for the System Monitor control logic 
        .busy_out(),            // ADC Busy signal 
        .channel_out(),         // Channel Selection Outputs 
        .do_out(data),              // Output data bus for dynamic reconfiguration port 
        .eoc_out(enable),             // End of Conversion Signal 
        .eos_out(),             // End of Sequence Signal 
        .alarm_out(),           // OR'ed output of all the Alarms   
        .drdy_out(ready),            // Data ready signal for the dynamic reconfiguration port 
        .vp_in(vp_in),                        // input wire vp_in 
        .vn_in(vn_in),                        // input wire vn_in 
        .vauxp0(vauxp0),                      // input wire vauxp0 
        .vauxn0(vauxn0),                      // input wire vauxn0 
        .vauxp1(vauxp1),                      // input wire vauxp1 
        .vauxn1(vauxn1),                      // input wire vauxn1 
        .vauxp2(vauxp2),                      // input wire vauxp2 
        .vauxn2(vauxn2),                      // input wire vauxn2 
        .vauxp3(vauxp3),                      // input wire vauxp3 
        .vauxn3(vauxn3),                      // input wire vauxn3 
        .vauxp8(vauxp8),                      // input wire vauxp8 
        .vauxn8(vauxn8),                      // input wire vauxn8 
        .vauxp9(vauxp9),                      // input wire vauxp9 
        .vauxn9(vauxn9),                      // input wire vauxn9 
        .vauxp10(vauxp10),                    // input wire vauxp10 
        .vauxn10(vauxn10),                    // input wire vauxn10 
        .vauxp11(vauxp11),                    // input wire vauxp11 
        .vauxn11(vauxn11)                     // input wire vauxn11 
      always @(posedge CLK100MHZ) 
          ready_d1 <= ready; 
      assign ready_rising = ready && !ready_d1 ? 1'b1 : 1'b0; 
      assign ready_falling = !ready && ready_d1 ? 1'b1 : 1'b0; 
      //led visual dmm               
      always @(posedge CLK100MHZ) 
          if (ready_rising == 1) 
              case (data[15:13]) 
                2:  LED <= 6'b000001; 
                3:  LED <= 6'b000011; 
                4:  LED <= 6'b000111;  
                5:  LED <= 6'b001111; 
                6:  LED <= 6'b011111; 
                7:  LED <= 6'b111111; 
                default: LED <= 6'b0;  
              LED <= LED; 
      //switch driver to choose channel 
      always @(posedge CLK100MHZ) 
          if (ready_rising == 1) 
            0: Address_in <= 8'h10; //A0 
            1: Address_in <= 8'h11; //A1 
            2: Address_in <= 8'h19; //A2 
            3: Address_in <= 8'h12; //A3 
            4: Address_in <= 8'h1A; //A4 
            5: Address_in <= 8'h1B; //A5 
            6: Address_in <= 8'h18; //A6 - A7 differential 
            7: Address_in <= 8'h13; //A8 - A9 differential 
            8: Address_in <= 8'h03; //Dedicated V diferential 
            default: Address_in <= 8'h10; //A0 
            Address_in <= Address_in; 




The other fundamental file of the project is the constraint file: this file contains the mapping between the board hardware, the FPGA’s pins and the I/O port names used in the VHDL/Verilog code.


## This file is a general .xdc for the Arty S7-50 Rev. B 
## To use it in a project: 
## - uncomment the lines corresponding to used pins 
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project 
## Clock signal 
#set_property -dict { PACKAGE_PIN F14   IOSTANDARD LVCMOS33 } [get_ports { CLK12MHZ }]; #IO_L13P_T2_MRCC_15 Sch=uclk 
#create_clock -add -name sys_clk_pin -period 83.333 -waveform {0 41.667} [get_ports { CLK12MHZ }]; 
set_property -dict { PACKAGE_PIN R2    IOSTANDARD SSTL135 } [get_ports { CLK100MHZ }]; #IO_L12P_T1_MRCC_34 Sch=ddr3_clk[200] 
create_clock -add -name sys_clk_pin -period 10.000 -waveform {0 5.000}  [get_ports { CLK100MHZ }]; 
## Switches 
set_property -dict { PACKAGE_PIN H14   IOSTANDARD LVCMOS33 } [get_ports { sw[0] }]; #IO_L20N_T3_A19_15 Sch=sw[0] 
set_property -dict { PACKAGE_PIN H18   IOSTANDARD LVCMOS33 } [get_ports { sw[1] }]; #IO_L21P_T3_DQS_15 Sch=sw[1] 
set_property -dict { PACKAGE_PIN G18   IOSTANDARD LVCMOS33 } [get_ports { sw[2] }]; #IO_L21N_T3_DQS_A18_15 Sch=sw[2] 
set_property -dict { PACKAGE_PIN M5    IOSTANDARD SSTL135 } [get_ports { sw[3] }]; #IO_L6N_T0_VREF_34 Sch=sw[3] 
## RGB LEDs 
#set_property -dict { PACKAGE_PIN J15   IOSTANDARD LVCMOS33 } [get_ports { led0_r }]; #IO_L23N_T3_FWE_B_15 Sch=led0_r 
set_property -dict { PACKAGE_PIN G17   IOSTANDARD LVCMOS33 } [get_ports { LED[4] }]; #IO_L14N_T2_SRCC_15 Sch=led0_g 
#set_property -dict { PACKAGE_PIN F15   IOSTANDARD LVCMOS33 } [get_ports { led0_b }]; #IO_L13N_T2_MRCC_15 Sch=led0_b 
#set_property -dict { PACKAGE_PIN E15   IOSTANDARD LVCMOS33 } [get_ports { led1_r }]; #IO_L15N_T2_DQS_ADV_B_15 Sch=led1_r 
set_property -dict { PACKAGE_PIN F18   IOSTANDARD LVCMOS33 } [get_ports { LED[5] }]; #IO_L16P_T2_A28_15 Sch=led1_g 
#set_property -dict { PACKAGE_PIN E14   IOSTANDARD LVCMOS33 } [get_ports { led1_b }]; #IO_L15P_T2_DQS_15 Sch=led1_b 
## LEDs 
set_property -dict { PACKAGE_PIN E18   IOSTANDARD LVCMOS33 } [get_ports { LED[0] }]; #IO_L16N_T2_A27_15 Sch=led[2] 
set_property -dict { PACKAGE_PIN F13   IOSTANDARD LVCMOS33 } [get_ports { LED[1] }]; #IO_L17P_T2_A26_15 Sch=led[3] 
set_property -dict { PACKAGE_PIN E13   IOSTANDARD LVCMOS33 } [get_ports { LED[2] }]; #IO_L17N_T2_A25_15 Sch=led[4] 
set_property -dict { PACKAGE_PIN H15   IOSTANDARD LVCMOS33 } [get_ports { LED[3] }]; #IO_L18P_T2_A24_15 Sch=led[5] 
## Buttons 
#set_property -dict { PACKAGE_PIN G15   IOSTANDARD LVCMOS33 } [get_ports { btn[0] }]; #IO_L18N_T2_A23_15 Sch=btn[0] 
#set_property -dict { PACKAGE_PIN K16   IOSTANDARD LVCMOS33 } [get_ports { btn[1] }]; #IO_L19P_T3_A22_15 Sch=btn[1] 
#set_property -dict { PACKAGE_PIN J16   IOSTANDARD LVCMOS33 } [get_ports { btn[2] }]; #IO_L19N_T3_A21_VREF_15 Sch=btn[2] 
#set_property -dict { PACKAGE_PIN H13   IOSTANDARD LVCMOS33 } [get_ports { btn[3] }]; #IO_L20P_T3_A20_15 Sch=btn[3] 
## PMOD Header JA 
#set_property -dict { PACKAGE_PIN L17   IOSTANDARD LVCMOS33 } [get_ports { ja[0] }]; #IO_L4P_T0_D04_14 Sch=ja_p[1] 
#set_property -dict { PACKAGE_PIN L18   IOSTANDARD LVCMOS33 } [get_ports { ja[1] }]; #IO_L4N_T0_D05_14 Sch=ja_n[1] 
#set_property -dict { PACKAGE_PIN M14   IOSTANDARD LVCMOS33 } [get_ports { ja[2] }]; #IO_L5P_T0_D06_14 Sch=ja_p[2] 
#set_property -dict { PACKAGE_PIN N14   IOSTANDARD LVCMOS33 } [get_ports { ja[3] }]; #IO_L5N_T0_D07_14 Sch=ja_n[2] 
#set_property -dict { PACKAGE_PIN M16   IOSTANDARD LVCMOS33 } [get_ports { ja[4] }]; #IO_L7P_T1_D09_14 Sch=ja_p[3] 
#set_property -dict { PACKAGE_PIN M17   IOSTANDARD LVCMOS33 } [get_ports { ja[5] }]; #IO_L7N_T1_D10_14 Sch=ja_n[3] 
#set_property -dict { PACKAGE_PIN M18   IOSTANDARD LVCMOS33 } [get_ports { ja[6] }]; #IO_L8P_T1_D11_14 Sch=ja_p[4] 
#set_property -dict { PACKAGE_PIN N18   IOSTANDARD LVCMOS33 } [get_ports { ja[7] }]; #IO_L8N_T1_D12_14 Sch=ja_n[4] 
## PMOD Header JB 
#set_property -dict { PACKAGE_PIN P17   IOSTANDARD LVCMOS33 } [get_ports { jb[0] }]; #IO_L9P_T1_DQS_14 Sch=jb_p[1] 
#set_property -dict { PACKAGE_PIN P18   IOSTANDARD LVCMOS33 } [get_ports { jb[1] }]; #IO_L9N_T1_DQS_D13_14 Sch=jb_n[1] 
#set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { jb[2] }]; #IO_L10P_T1_D14_14 Sch=jb_p[2] 
#set_property -dict { PACKAGE_PIN T18   IOSTANDARD LVCMOS33 } [get_ports { jb[3] }]; #IO_L10N_T1_D15_14 Sch=jb_n[2] 
#set_property -dict { PACKAGE_PIN P14   IOSTANDARD LVCMOS33 } [get_ports { jb[4] }]; #IO_L11P_T1_SRCC_14 Sch=jb_p[3] 
#set_property -dict { PACKAGE_PIN P15   IOSTANDARD LVCMOS33 } [get_ports { jb[5] }]; #IO_L11N_T1_SRCC_14 Sch=jb_n[3] 
#set_property -dict { PACKAGE_PIN N15   IOSTANDARD LVCMOS33 } [get_ports { jb[6] }]; #IO_L12P_T1_MRCC_14 Sch=jb_p[4] 
#set_property -dict { PACKAGE_PIN P16   IOSTANDARD LVCMOS33 } [get_ports { jb[7] }]; #IO_L12N_T1_MRCC_14 Sch=jb_n[4] 
## PMOD Header JC 
#set_property -dict { PACKAGE_PIN U15   IOSTANDARD LVCMOS33 } [get_ports { jc[0] }]; #IO_L18P_T2_A12_D28_14 Sch=jc1/ck_io[41] 
#set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { jc[1] }]; #IO_L18N_T2_A11_D27_14 Sch=jc2/ck_io[40] 
#set_property -dict { PACKAGE_PIN U17   IOSTANDARD LVCMOS33 } [get_ports { jc[2] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=jc3/ck_io[39] 
#set_property -dict { PACKAGE_PIN U18   IOSTANDARD LVCMOS33 } [get_ports { jc[3] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=jc4/ck_io[38] 
#set_property -dict { PACKAGE_PIN U16   IOSTANDARD LVCMOS33 } [get_ports { jc[4] }]; #IO_L16P_T2_CSI_B_14 Sch=jc7/ck_io[37] 
#set_property -dict { PACKAGE_PIN P13   IOSTANDARD LVCMOS33 } [get_ports { jc[5] }]; #IO_L19P_T3_A10_D26_14 Sch=jc8/ck_io[36] 
#set_property -dict { PACKAGE_PIN R13   IOSTANDARD LVCMOS33 } [get_ports { jc[6] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=jc9/ck_io[35] 
#set_property -dict { PACKAGE_PIN V14   IOSTANDARD LVCMOS33 } [get_ports { jc[7] }]; #IO_L20P_T3_A08_D24_14 Sch=jc10/ck_io[34] 
## PMOD Header JD 
#set_property -dict { PACKAGE_PIN V15   IOSTANDARD LVCMOS33 } [get_ports { jd[0] }]; #IO_L20N_T3_A07_D23_14 Sch=jd1/ck_io[33] 
#set_property -dict { PACKAGE_PIN U12   IOSTANDARD LVCMOS33 } [get_ports { jd[1] }]; #IO_L21P_T3_DQS_14 Sch=jd2/ck_io[32] 
#set_property -dict { PACKAGE_PIN V13   IOSTANDARD LVCMOS33 } [get_ports { jd[2] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=jd3/ck_io[31] 
#set_property -dict { PACKAGE_PIN T12   IOSTANDARD LVCMOS33 } [get_ports { jd[3] }]; #IO_L22P_T3_A05_D21_14 Sch=jd4/ck_io[30] 
#set_property -dict { PACKAGE_PIN T13   IOSTANDARD LVCMOS33 } [get_ports { jd[4] }]; #IO_L22N_T3_A04_D20_14 Sch=jd7/ck_io[29] 
#set_property -dict { PACKAGE_PIN R11   IOSTANDARD LVCMOS33 } [get_ports { jd[5] }]; #IO_L23P_T3_A03_D19_14 Sch=jd8/ck_io[28] 
#set_property -dict { PACKAGE_PIN T11   IOSTANDARD LVCMOS33 } [get_ports { jd[6] }]; #IO_L23N_T3_A02_D18_14 Sch=jd9/ck_io[27] 
#set_property -dict { PACKAGE_PIN U11   IOSTANDARD LVCMOS33 } [get_ports { jd[7] }]; #IO_L24P_T3_A01_D17_14 Sch=jd10/ck_io[26] 
## USB-UART Interface 
#set_property -dict { PACKAGE_PIN R12   IOSTANDARD LVCMOS33 } [get_ports { uart_rxd_out }]; #IO_25_14 Sch=uart_rxd_out 
#set_property -dict { PACKAGE_PIN V12   IOSTANDARD LVCMOS33 } [get_ports { uart_txd_in }]; #IO_L24N_T3_A00_D16_14 Sch=uart_txd_in 
## ChipKit Single Ended Analog Inputs 
## NOTE: The ck_an_p pins can be used as single ended analog inputs with voltages from 0-3.3V (Chipkit Analog pins A0-A5).  
## These signals should only be connected to the XADC core. When using these pins as digital I/O, use pins ck_io[14-19]. 
set_property -dict { PACKAGE_PIN B13   IOSTANDARD LVCMOS33 } [get_ports { vauxp0 }]; #IO_L1P_T0_AD0P_15 Sch=ck_an_p[0] 
set_property -dict { PACKAGE_PIN A13   IOSTANDARD LVCMOS33 } [get_ports { vauxn0 }]; #IO_L1N_T0_AD0N_15 Sch=ck_an_n[0] 
set_property -dict { PACKAGE_PIN B15   IOSTANDARD LVCMOS33 } [get_ports { vauxp1 }]; #IO_L3P_T0_DQS_AD1P_15 Sch=ck_an_p[1] 
set_property -dict { PACKAGE_PIN A15   IOSTANDARD LVCMOS33 } [get_ports { vauxn1 }]; #IO_L3N_T0_DQS_AD1N_15 Sch=ck_an_n[1] 
set_property -dict { PACKAGE_PIN E12   IOSTANDARD LVCMOS33 } [get_ports { vauxp9 }]; #IO_L5P_T0_AD9P_15 Sch=ck_an_p[2] 
set_property -dict { PACKAGE_PIN D12   IOSTANDARD LVCMOS33 } [get_ports { vauxn9 }]; #IO_L5N_T0_AD9N_15 Sch=ck_an_n[2] 
set_property -dict { PACKAGE_PIN B17   IOSTANDARD LVCMOS33 } [get_ports { vauxp2 }]; #IO_L7P_T1_AD2P_15 Sch=ck_an_p[3] 
set_property -dict { PACKAGE_PIN A17   IOSTANDARD LVCMOS33 } [get_ports { vauxn2 }]; #IO_L7N_T1_AD2N_15 Sch=ck_an_n[3] 
set_property -dict { PACKAGE_PIN C17   IOSTANDARD LVCMOS33 } [get_ports { vauxp10 }]; #IO_L8P_T1_AD10P_15 Sch=ck_an_p[4] 
set_property -dict { PACKAGE_PIN B18   IOSTANDARD LVCMOS33 } [get_ports { vauxn10 }]; #IO_L8N_T1_AD10N_15 Sch=ck_an_n[4] 
set_property -dict { PACKAGE_PIN E16   IOSTANDARD LVCMOS33 } [get_ports { vauxp11 }]; #IO_L10P_T1_AD11P_15 Sch=ck_an_p[5] 
set_property -dict { PACKAGE_PIN E17   IOSTANDARD LVCMOS33 } [get_ports { vauxn11 }]; #IO_L10N_T1_AD11N_15 Sch=ck_an_n[5] 
## Dedicated Analog Inputs 
set_property -dict { PACKAGE_PIN J10   } [get_ports { vp_in }]; #IO_L1P_T0_AD4P_35 Sch=v_p 
set_property -dict { PACKAGE_PIN K9    } [get_ports { vn_in }]; #IO_L1N_T0_AD4N_35 Sch=v_n 
## ChipKit Digital I/O Low 
#set_property -dict { PACKAGE_PIN L13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[0] }]; #IO_0_14 Sch=ck_io[0] 
#set_property -dict { PACKAGE_PIN N13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[1] }]; #IO_L6N_T0_D08_VREF_14 Sch=ck_io[1] 
#set_property -dict { PACKAGE_PIN L16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[2] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=ck_io[2] 
#set_property -dict { PACKAGE_PIN R14   IOSTANDARD LVCMOS33 } [get_ports { ck_io[3] }]; #IO_L13P_T2_MRCC_14 Sch=ck_io[3] 
#set_property -dict { PACKAGE_PIN T14   IOSTANDARD LVCMOS33 } [get_ports { ck_io[4] }]; #IO_L13N_T2_MRCC_14 Sch=ck_io[4] 
#set_property -dict { PACKAGE_PIN R16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[5] }]; #IO_L14P_T2_SRCC_14 Sch=ck_io[5] 
#set_property -dict { PACKAGE_PIN R17   IOSTANDARD LVCMOS33 } [get_ports { ck_io[6] }]; #IO_L14N_T2_SRCC_14 Sch=ck_io[6] 
#set_property -dict { PACKAGE_PIN V17   IOSTANDARD LVCMOS33 } [get_ports { ck_io[7] }]; #IO_L16N_T2_A15_D31_14 Sch=ck_io[7] 
#set_property -dict { PACKAGE_PIN R15   IOSTANDARD LVCMOS33 } [get_ports { ck_io[8] }]; #IO_L17P_T2_A14_D30_14 Sch=ck_io[8] 
#set_property -dict { PACKAGE_PIN T15   IOSTANDARD LVCMOS33 } [get_ports { ck_io[9] }]; #IO_L17N_T2_A13_D29_14 Sch=ck_io[9] 
#set_property -dict { PACKAGE_PIN H16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[10] }]; #IO_L22P_T3_A17_15 Sch=ck_io10_ss 
#set_property -dict { PACKAGE_PIN H17   IOSTANDARD LVCMOS33 } [get_ports { ck_io[11] }]; #IO_L22N_T3_A16_15 Sch=ck_io11_mosi 
#set_property -dict { PACKAGE_PIN K14   IOSTANDARD LVCMOS33 } [get_ports { ck_io[12] }]; #IO_L23P_T3_FOE_B_15 Sch=ck_io12_miso 
#set_property -dict { PACKAGE_PIN G16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[13] }]; #IO_L14P_T2_SRCC_15 Sch=ck_io13_sck 
## ChipKit Digital I/O On Outer Analog Header 
## NOTE: These pins should be used when using the analog header signals A0-A5 as digital I/O (Chipkit digital pins 14-19) 
#set_property -dict { PACKAGE_PIN G13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[14] }]; #IO_0_15 Sch=ck_a[0] 
#set_property -dict { PACKAGE_PIN B16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[15] }]; #IO_L4P_T0_15 Sch=ck_a[1] 
#set_property -dict { PACKAGE_PIN A16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[16] }]; #IO_L4N_T0_15 Sch=ck_a[2] 
#set_property -dict { PACKAGE_PIN C13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[17] }]; #IO_L6P_T0_15 Sch=ck_a[3] 
#set_property -dict { PACKAGE_PIN C14   IOSTANDARD LVCMOS33 } [get_ports { ck_io[18] }]; #IO_L6N_T0_VREF_15 Sch=ck_a[4] 
#set_property -dict { PACKAGE_PIN D18   IOSTANDARD LVCMOS33 } [get_ports { ck_io[19] }]; #IO_L11P_T1_SRCC_15 Sch=ck_a[5] 
## ChipKit Digital I/O On Inner Analog Header 
## NOTE: These pins will need to be connected to the XADC core when used as differential analog inputs (Chipkit analog pins A6-A11) 
set_property -dict { PACKAGE_PIN B14   IOSTANDARD LVCMOS33 } [get_ports { vauxp8 }]; #IO_L2P_T0_AD8P_15 Sch=ad_p[8] 
set_property -dict { PACKAGE_PIN A14   IOSTANDARD LVCMOS33 } [get_ports { vauxn8 }]; #IO_L2N_T0_AD8N_15 Sch=ad_n[8] 
set_property -dict { PACKAGE_PIN D16   IOSTANDARD LVCMOS33 } [get_ports { vauxp3 }]; #IO_L9P_T1_DQS_AD3P_15 Sch=ad_p[3] 
set_property -dict { PACKAGE_PIN D17   IOSTANDARD LVCMOS33 } [get_ports { vauxn3 }]; #IO_L9N_T1_DQS_AD3N_15 Sch=ad_n[3] 
#set_property -dict { PACKAGE_PIN D14   IOSTANDARD LVCMOS33 } [get_ports { ck_io[24] }]; #IO_L12P_T1_MRCC_15 Sch=ck_a10_r 
#set_property -dict { PACKAGE_PIN D15   IOSTANDARD LVCMOS33 } [get_ports { ck_io[25] }]; #IO_L12N_T1_MRCC_15 Sch=ck_a11_r 
## ChipKit Digital I/O High 
## Note: these pins are shared with PMOD Headers JC and JD and cannot be used at the same time as the applicable PMOD interface(s) 
#set_property -dict { PACKAGE_PIN U11   IOSTANDARD LVCMOS33 } [get_ports { ck_io[26] }]; #IO_L24P_T3_A01_D17_14 Sch=jd10/ck_io[26] 
#set_property -dict { PACKAGE_PIN T11   IOSTANDARD LVCMOS33 } [get_ports { ck_io[27] }]; #IO_L23N_T3_A02_D18_14 Sch=jd9/ck_io[27] 
#set_property -dict { PACKAGE_PIN R11   IOSTANDARD LVCMOS33 } [get_ports { ck_io[28] }]; #IO_L23P_T3_A03_D19_14 Sch=jd8/ck_io[28] 
#set_property -dict { PACKAGE_PIN T13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[29] }]; #IO_L22N_T3_A04_D20_14 Sch=jd7/ck_io[29] 
#set_property -dict { PACKAGE_PIN T12   IOSTANDARD LVCMOS33 } [get_ports { ck_io[30] }]; #IO_L22P_T3_A05_D21_14 Sch=jd4/ck_io[30] 
#set_property -dict { PACKAGE_PIN V13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[31] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=jd3/ck_io[31] 
#set_property -dict { PACKAGE_PIN U12   IOSTANDARD LVCMOS33 } [get_ports { ck_io[32] }]; #IO_L21P_T3_DQS_14 Sch=jd2/ck_io[32] 
#set_property -dict { PACKAGE_PIN V15   IOSTANDARD LVCMOS33 } [get_ports { ck_io[33] }]; #IO_L20N_T3_A07_D23_14 Sch=jd1/ck_io[33] 
#set_property -dict { PACKAGE_PIN V14   IOSTANDARD LVCMOS33 } [get_ports { ck_io[34] }]; #IO_L20P_T3_A08_D24_14 Sch=jc10/ck_io[34] 
#set_property -dict { PACKAGE_PIN R13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[35] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=jc9/ck_io[35] 
#set_property -dict { PACKAGE_PIN P13   IOSTANDARD LVCMOS33 } [get_ports { ck_io[36] }]; #IO_L19P_T3_A10_D26_14 Sch=jc8/ck_io[36] 
#set_property -dict { PACKAGE_PIN U16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[37] }]; #IO_L16P_T2_CSI_B_14 Sch=jc7/ck_io[37] 
#set_property -dict { PACKAGE_PIN U18   IOSTANDARD LVCMOS33 } [get_ports { ck_io[38] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=jc4/ck_io[38] 
#set_property -dict { PACKAGE_PIN U17   IOSTANDARD LVCMOS33 } [get_ports { ck_io[39] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=jc3/ck_io[39] 
#set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { ck_io[40] }]; #IO_L18N_T2_A11_D27_14 Sch=jc2/ck_io[40] 
#set_property -dict { PACKAGE_PIN U15   IOSTANDARD LVCMOS33 } [get_ports { ck_io[41] }]; #IO_L18P_T2_A12_D28_14 Sch=jc1/ck_io[41] 
## ChipKit SPI 
## Note: these are shared with the ChipKit IOL pins and should not be used at the same time as the pins. 
#set_property -dict { PACKAGE_PIN H16   IOSTANDARD LVCMOS33 } [get_ports { ck_ss }]; #IO_L22P_T3_A17_15 Sch=ck_io10_ss 
#set_property -dict { PACKAGE_PIN H17   IOSTANDARD LVCMOS33 } [get_ports { ck_mosi }]; #IO_L22N_T3_A16_15 Sch=ck_io11_mosi 
#set_property -dict { PACKAGE_PIN K14   IOSTANDARD LVCMOS33 } [get_ports { ck_miso }]; #IO_L23P_T3_FOE_B_15 Sch=ck_io12_miso 
#set_property -dict { PACKAGE_PIN G16   IOSTANDARD LVCMOS33 } [get_ports { ck_sck }]; #IO_L14P_T2_SRCC_15 Sch=ck_io13_sck 
## CihpKit I2C 
#set_property -dict { PACKAGE_PIN J14   IOSTANDARD LVCMOS33 } [get_ports { ck_scl }]; #IO_L24N_T3_RS0_15 Sch=ck_scl 
#set_property -dict { PACKAGE_PIN J13   IOSTANDARD LVCMOS33 } [get_ports { ck_sda }]; #IO_L24P_T3_RS1_15 Sch=ck_sda 
## Misc. ChipKit signals 
#set_property -dict { PACKAGE_PIN K13   IOSTANDARD LVCMOS33 } [get_ports { ck_ioa }]; #IO_25_15 Sch=ck_ioa 
#set_property -dict { PACKAGE_PIN C18   IOSTANDARD LVCMOS33 } [get_ports { ck_rst }]; #IO_L11N_T1_SRCC_15 
## Quad SPI Flash 
## Note: the SCK clock signal can be driven using the STARTUPE2 primitive 
#set_property -dict { PACKAGE_PIN M13   IOSTANDARD LVCMOS33 } [get_ports { qspi_cs }]; #IO_L6P_T0_FCS_B_14 Sch=qspi_cs 
#set_property -dict { PACKAGE_PIN K17   IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[0] }]; #IO_L1P_T0_D00_MOSI_14 Sch=qspi_dq[0] 
#set_property -dict { PACKAGE_PIN K18   IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[1] }]; #IO_L1N_T0_D01_DIN_14 Sch=qspi_dq[1] 
#set_property -dict { PACKAGE_PIN L14   IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[2] }]; #IO_L2P_T0_D02_14 Sch=qspi_dq[2] 
#set_property -dict { PACKAGE_PIN M15   IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[3] }]; #IO_L2N_T0_D03_14 Sch=qspi_dq[3] 
## Configuration options, can be used for all designs 
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] 
set_property CONFIG_VOLTAGE 3.3 [current_design] 
set_property CFGBVS VCCO [current_design] 
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] 
set_property CONFIG_MODE SPIx4 [current_design] 
## SW3 is assigned to a pin M5 in the 1.35v bank. This pin can also be used as 
## the VREF for BANK 34. To ensure that SW3 does not define the reference voltage 
## and to be able to use this pin as an ordinary I/O the following property must 
## be set to enable an internal VREF for BANK 34. Since a 1.35v supply is being 
## used the internal reference is set to half that value (i.e. 0.675v). Note that 
## this property must be set even if SW3 is not used in the design. 
set_property INTERNAL_VREF 0.675 [get_iobanks 34] 



Once all the source modules and IPs are created, and the constraint file is populated, we can start the process of generating the bitstream for our board. The typical flow consists of the following steps:


  • Simulation
  • Synthesis
  • Implementation
  • Bitstream generation


Those step are easily found in the Vivado GUI, as you can see from the screenshot below:


Vivado View



The simulation step is used to test the modules, and consists in generating all the signals according to the code in the module, that you can later inspect using the logic analyzer tool that Vivado provides. Normally, to test your design, you would create a “test bench” module for each of the modules in your project (or at least for the most important ones). The test bench is basically a sort of wrapper module, which instantiates the module under test, and exercises its I/O by generating patterns of signals. For this demo, I will skip the simulation.


The Synthesis step is the first phase of the mapping of the code onto the FPGA hardware. The focus of this phase is mostly to operate the translation of the code into blocks that can be realised on hardware, to make sure it can be done, and as such it is device-independent (i.e. it won’t need the constraint file). If the synthesis step completes successfully, it still doesn’t mean that our design is actually mappable onto the hardware we are using.


The Implementation step is the one that will map the design onto our actual FPGA hardware. During this phase, the logic components will be placed and routed within the FPGA fabric, and a series of tests will be run, to make sure the mapping is within the specification. Once this phase is completed, Vivado gives access to a series of reports, where you can find all the information you need about the mapping. Particularly important is the Timing report, which uses accurate simulation techniques to provide information on delays affecting signals travelling on different paths (nets) within the FPGA hardware. Fine-tuning the timing specification will be needed if failures are highlighted by the timing report.



Vivado Implementation View



Once the Implementation step is successfully completed, you can start the bitstream generation step. This will create the binary stream of data needed to program the FPGA. On completion of this step, all is left to do is to download the bitstream to the board, using the Hardware Manager, and you are ready to go. Below there is the link to the video of the actual demo.


Digilent Arty S7-50 XADC Demo






For this demo project, there is no MicroBlaze processor soft core used, so there is no need to write any code to program the processor. In the next blog article, I will create a project which uses a MicroBlaze core to analyse the thermal data coming from the GridEye sensor, and will show how you can seamlessly integrate the SDK with Vivado.


Before ending this article, I would like to go back to what I said about the push of FPGAs into the makers and hobbyist space, and add my considerations regarding the tool.


In general, my feeling is that Vivado is a great tool: once you get around its layout and familiarise with the design flow, it is very powerful and quite intuitive. But it is not the kind of tool you can learn to use just by playing with it when you have some spare time: to be productive, you need to spend some “quality time” with the tool. There are plenty of books, tutorials and videos that can introduce you to Vivado, and I found that using a combination of all them I was up to speed in a pretty decent time.


Most definitely, there is no shortage of documentation available! This is both good and bad news, at the same time. The good news is that, for virtually any aspect of design with Xilinx’s FPGAs, you can find a reference guide to get information from. The bad news is that, once you find the guides you need, you got to read them! For a design involving just an handful of IP cores, the reading can easily mount up to thousand pages, which inevitably takes time.


Does this mean this board is not for makers? No, in my opinion it doesn’t. It only means that, before you start this journey, you need to prepare yourself: it will take perhaps longer, but it is a lot of fun, and definitely more rewarding! :-)


The Digilent Arty S7 Series
The Digilent Arty S7: An Unexpected journey - Part 1 - The Board
The Digilent Arty S7: An Unexpected Journey - Part 2 - The Tools
The Digilent Arty S7: An Unexpected Journey - Part 3 - To the drawing board
The Digilent Arty S7: An Unexpected Journey - Part 4 - The Code