I sell on Tindie
Map
Grand Prize Winner
(Dec 2012)
Second Prize Winner
(July 2012)












Arty Z7 Zynq 7020 – ‘Hello World’

Having hardware and software configurable platform gives the ultimate flexibility. Having FPGA fabric and two ARM cores integrated into a single System-On-Chip (SoC) gives benefits of the two (hardware and software) worlds, compact size, advanced optimisation of such integration, reduces the time for development effort, simplifies subsequent testing, verification and validation. Today we are reviewing a great development platform with Zynq 7020 SoC from Xilinx. Let’s implement an equivalent of ‘Hello World’ application, in our case we are going to create 6 independent PWM channels in hardware, connect them to the two RGB LEDs and write a simple demo cycling through the colours.

The board that we are going to use is Arty Z7-20, its picture is given below:

We will also need a Vivado Webpack SDK, we will be using relatively old but stable revision 2017.3 but our readers have a full freedom of using something more recent. At the end of the day, the tool doesn’t matter as much as the actual engineer who uses the tool.

Let’s iterate through the steps of our activities. We are going to create a blank Vivado project, create a board design, design a new AXI IP core which will control our PWM channels through the set of registers, go back to the main project, create a Zynq processing system, enable board’s system clock and RGB LED peripheral and connect it to the processing system through our newly designed PWM IP Core. Then add constraints, generate HDL wrapper, synthesize and run the implementation, generate bitstream and hardware description, export it to SDK, create an empty FreeRTOS application, write our C code for controlling LEDs and then run the demo.

There is not so much of the code writing during this exercise, it is customising VHDL template for the AXI IP Core and C-code. Let’s start by looking at the VHDL code.

First code example is given below, it is autogenerated led_pwm_v1_0.vhd that acts as a wrapper around the AXI Bus Interface. The highlighted lines are our customised additions to the autogenerated code:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity led_pwm_v1_0 is
	generic (
		-- Users to add parameters here
		-- User parameters ends
		-- Do not modify the parameters beyond this line
		-- Parameters of Axi Slave Bus Interface S00_AXI
		C_S00_AXI_DATA_WIDTH	: integer	:= 32;
		C_S00_AXI_ADDR_WIDTH	: integer	:= 5
	);
	port (
		-- Users to add ports here
		gpio_io_o : out std_logic_vector(5 downto 0);
		-- User ports ends
		-- Do not modify the ports beyond this line
		-- Ports of Axi Slave Bus Interface S00_AXI
		s00_axi_aclk	: in std_logic;
		s00_axi_aresetn	: in std_logic;
		s00_axi_awaddr	: in std_logic_vector(C_S00_AXI_ADDR_WIDTH-1 downto 0);
		s00_axi_awprot	: in std_logic_vector(2 downto 0);
		s00_axi_awvalid	: in std_logic;
		s00_axi_awready	: out std_logic;
		s00_axi_wdata	: in std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0);
		s00_axi_wstrb	: in std_logic_vector((C_S00_AXI_DATA_WIDTH/8)-1 downto 0);
		s00_axi_wvalid	: in std_logic;
		s00_axi_wready	: out std_logic;
		s00_axi_bresp	: out std_logic_vector(1 downto 0);
		s00_axi_bvalid	: out std_logic;
		s00_axi_bready	: in std_logic;
		s00_axi_araddr	: in std_logic_vector(C_S00_AXI_ADDR_WIDTH-1 downto 0);
		s00_axi_arprot	: in std_logic_vector(2 downto 0);
		s00_axi_arvalid	: in std_logic;
		s00_axi_arready	: out std_logic;
		s00_axi_rdata	: out std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0);
		s00_axi_rresp	: out std_logic_vector(1 downto 0);
		s00_axi_rvalid	: out std_logic;
		s00_axi_rready	: in std_logic
	);
end led_pwm_v1_0;
architecture arch_imp of led_pwm_v1_0 is
	-- component declaration
	component led_pwm_v1_0_S00_AXI is
		generic (
		C_S_AXI_DATA_WIDTH	: integer	:= 32;
		C_S_AXI_ADDR_WIDTH	: integer	:= 5
		);
		port (
		rgb_led_tri_o : out std_logic_vector(5 downto 0);
		S_AXI_ACLK	: in std_logic;
		S_AXI_ARESETN	: in std_logic;
		S_AXI_AWADDR	: in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
		S_AXI_AWPROT	: in std_logic_vector(2 downto 0);
		S_AXI_AWVALID	: in std_logic;
		S_AXI_AWREADY	: out std_logic;
		S_AXI_WDATA	: in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
		S_AXI_WSTRB	: in std_logic_vector((C_S_AXI_DATA_WIDTH/8)-1 downto 0);
		S_AXI_WVALID	: in std_logic;
		S_AXI_WREADY	: out std_logic;
		S_AXI_BRESP	: out std_logic_vector(1 downto 0);
		S_AXI_BVALID	: out std_logic;
		S_AXI_BREADY	: in std_logic;
		S_AXI_ARADDR	: in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
		S_AXI_ARPROT	: in std_logic_vector(2 downto 0);
		S_AXI_ARVALID	: in std_logic;
		S_AXI_ARREADY	: out std_logic;
		S_AXI_RDATA	: out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
		S_AXI_RRESP	: out std_logic_vector(1 downto 0);
		S_AXI_RVALID	: out std_logic;
		S_AXI_RREADY	: in std_logic
		);
	end component led_pwm_v1_0_S00_AXI;
begin
-- Instantiation of Axi Bus Interface S00_AXI
led_pwm_v1_0_S00_AXI_inst : led_pwm_v1_0_S00_AXI
	generic map (
		C_S_AXI_DATA_WIDTH	=> C_S00_AXI_DATA_WIDTH,
		C_S_AXI_ADDR_WIDTH	=> C_S00_AXI_ADDR_WIDTH
	)
	port map (
		rgb_led_tri_o => gpio_io_o,
		S_AXI_ACLK	=> s00_axi_aclk,
		S_AXI_ARESETN	=> s00_axi_aresetn,
		S_AXI_AWADDR	=> s00_axi_awaddr,
		S_AXI_AWPROT	=> s00_axi_awprot,
		S_AXI_AWVALID	=> s00_axi_awvalid,
		S_AXI_AWREADY	=> s00_axi_awready,
		S_AXI_WDATA	=> s00_axi_wdata,
		S_AXI_WSTRB	=> s00_axi_wstrb,
		S_AXI_WVALID	=> s00_axi_wvalid,
		S_AXI_WREADY	=> s00_axi_wready,
		S_AXI_BRESP	=> s00_axi_bresp,
		S_AXI_BVALID	=> s00_axi_bvalid,
		S_AXI_BREADY	=> s00_axi_bready,
		S_AXI_ARADDR	=> s00_axi_araddr,
		S_AXI_ARPROT	=> s00_axi_arprot,
		S_AXI_ARVALID	=> s00_axi_arvalid,
		S_AXI_ARREADY	=> s00_axi_arready,
		S_AXI_RDATA	=> s00_axi_rdata,
		S_AXI_RRESP	=> s00_axi_rresp,
		S_AXI_RVALID	=> s00_axi_rvalid,
		S_AXI_RREADY	=> s00_axi_rready
	);
	-- Add user logic here
	-- User logic ends
end arch_imp;

Second code example is autogenerated led_pwm_ip_v3_0_S00_AXI.vhd which is the actual the AXI Bus Interface. Once again, the highlighted lines are manual additions to the autogenerated code. The code is modified to give more meaningful names to the four slave registers accessible through the ARM CPU, these are the code and the registers:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity led_pwm_v1_0_S00_AXI is
	generic (
		-- Users to add parameters here
		-- User parameters ends
		-- Do not modify the parameters beyond this line
		-- Width of S_AXI data bus
		C_S_AXI_DATA_WIDTH	: integer	:= 32;
		-- Width of S_AXI address bus
		C_S_AXI_ADDR_WIDTH	: integer	:= 5
	);
	port (
		-- Users to add ports here
		rgb_led_tri_o : out std_logic_vector(5 downto 0);
		-- User ports ends
		-- Do not modify the ports beyond this line
		-- Global Clock Signal
		S_AXI_ACLK	: in std_logic;
		-- Global Reset Signal. This Signal is Active LOW
		S_AXI_ARESETN	: in std_logic;
		-- Write address (issued by master, acceped by Slave)
		S_AXI_AWADDR	: in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
		-- Write channel Protection type. This signal indicates the
    		-- privilege and security level of the transaction, and whether
    		-- the transaction is a data access or an instruction access.
		S_AXI_AWPROT	: in std_logic_vector(2 downto 0);
		-- Write address valid. This signal indicates that the master signaling
    		-- valid write address and control information.
		S_AXI_AWVALID	: in std_logic;
		-- Write address ready. This signal indicates that the slave is ready
    		-- to accept an address and associated control signals.
		S_AXI_AWREADY	: out std_logic;
		-- Write data (issued by master, acceped by Slave) 
		S_AXI_WDATA	: in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
		-- Write strobes. This signal indicates which byte lanes hold
    		-- valid data. There is one write strobe bit for each eight
    		-- bits of the write data bus.    
		S_AXI_WSTRB	: in std_logic_vector((C_S_AXI_DATA_WIDTH/8)-1 downto 0);
		-- Write valid. This signal indicates that valid write
    		-- data and strobes are available.
		S_AXI_WVALID	: in std_logic;
		-- Write ready. This signal indicates that the slave
    		-- can accept the write data.
		S_AXI_WREADY	: out std_logic;
		-- Write response. This signal indicates the status
    		-- of the write transaction.
		S_AXI_BRESP	: out std_logic_vector(1 downto 0);
		-- Write response valid. This signal indicates that the channel
    		-- is signaling a valid write response.
		S_AXI_BVALID	: out std_logic;
		-- Response ready. This signal indicates that the master
    		-- can accept a write response.
		S_AXI_BREADY	: in std_logic;
		-- Read address (issued by master, acceped by Slave)
		S_AXI_ARADDR	: in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
		-- Protection type. This signal indicates the privilege
    		-- and security level of the transaction, and whether the
    		-- transaction is a data access or an instruction access.
		S_AXI_ARPROT	: in std_logic_vector(2 downto 0);
		-- Read address valid. This signal indicates that the channel
    		-- is signaling valid read address and control information.
		S_AXI_ARVALID	: in std_logic;
		-- Read address ready. This signal indicates that the slave is
    		-- ready to accept an address and associated control signals.
		S_AXI_ARREADY	: out std_logic;
		-- Read data (issued by slave)
		S_AXI_RDATA	: out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
		-- Read response. This signal indicates the status of the
    		-- read transfer.
		S_AXI_RRESP	: out std_logic_vector(1 downto 0);
		-- Read valid. This signal indicates that the channel is
    		-- signaling the required read data.
		S_AXI_RVALID	: out std_logic;
		-- Read ready. This signal indicates that the master can
    		-- accept the read data and response information.
		S_AXI_RREADY	: in std_logic
	);
end led_pwm_v1_0_S00_AXI;
architecture arch_imp of led_pwm_v1_0_S00_AXI is
        component pwm is
            generic (
               G_DATA_WIDTH : integer := 32); 
            port (
                f_aclk : in std_logic;
                i_aresetn : in std_logic;
                i_pwm_module : in std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
                i_pwm_width : in std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
                o_pwm : out std_logic);
        end component;
	-- AXI4LITE signals
	signal axi_awaddr	: std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
	signal axi_awready	: std_logic;
	signal axi_wready	: std_logic;
	signal axi_bresp	: std_logic_vector(1 downto 0);
	signal axi_bvalid	: std_logic;
	signal axi_araddr	: std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0);
	signal axi_arready	: std_logic;
	signal axi_rdata	: std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
	signal axi_rresp	: std_logic_vector(1 downto 0);
	signal axi_rvalid	: std_logic;
	-- Example-specific design signals
	-- local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
	-- ADDR_LSB is used for addressing 32/64 bit registers/memories
	-- ADDR_LSB = 2 for 32 bits (n downto 2)
	-- ADDR_LSB = 3 for 64 bits (n downto 3)
	constant ADDR_LSB  : integer := (C_S_AXI_DATA_WIDTH/32)+ 1;
	constant OPT_MEM_ADDR_BITS : integer := 2;
	------------------------------------------------
	---- Signals for user logic register space example
	--------------------------------------------------
	---- Number of Slave Registers 7
        signal i_pwm_module :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
        signal i_pwm_width0 :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
        signal i_pwm_width1 :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
        signal i_pwm_width2 :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
        signal i_pwm_width3 :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
        signal i_pwm_width4 :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
        signal i_pwm_width5 :std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
	signal slv_reg_rden	: std_logic;
	signal slv_reg_wren	: std_logic;
	signal reg_data_out	:std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
	signal byte_index	: integer;
	signal aw_en	: std_logic;
begin
        pwm1: pwm 
            generic map (
                G_DATA_WIDTH => C_S_AXI_DATA_WIDTH
            ) 
            port map (
                f_aclk => S_AXI_ACLK, 
                i_aresetn => S_AXI_ARESETN, 
                i_pwm_module => i_pwm_module, 
                i_pwm_width => i_pwm_width0, 
                o_pwm => rgb_led_tri_o(0)
            );
        pwm2: pwm 
            generic map (
                G_DATA_WIDTH => C_S_AXI_DATA_WIDTH
            ) 
            port map (
                f_aclk => S_AXI_ACLK, 
                i_aresetn => S_AXI_ARESETN, 
                i_pwm_module => i_pwm_module, 
                i_pwm_width => i_pwm_width1,
                o_pwm => rgb_led_tri_o(1)
            );
        pwm3: pwm 
            generic map (
                G_DATA_WIDTH => C_S_AXI_DATA_WIDTH
            ) 
            port map (
                f_aclk => S_AXI_ACLK, 
                i_aresetn => S_AXI_ARESETN, 
                i_pwm_module => i_pwm_module, 
                i_pwm_width => i_pwm_width2, 
                o_pwm => rgb_led_tri_o(2)
            );
        pwm4: pwm 
            generic map (
                G_DATA_WIDTH => C_S_AXI_DATA_WIDTH
            ) 
            port map (
                f_aclk => S_AXI_ACLK, 
                i_aresetn => S_AXI_ARESETN, 
                i_pwm_module => i_pwm_module, 
                i_pwm_width => i_pwm_width3, 
                o_pwm => rgb_led_tri_o(3)
            );
        pwm5: pwm 
            generic map (
                G_DATA_WIDTH => C_S_AXI_DATA_WIDTH
            ) 
            port map (
                f_aclk => S_AXI_ACLK, 
                i_aresetn => S_AXI_ARESETN, 
                i_pwm_module => i_pwm_module, 
                i_pwm_width => i_pwm_width4, 
                o_pwm => rgb_led_tri_o(4)
            );
        pwm6: pwm 
            generic map (
                G_DATA_WIDTH => C_S_AXI_DATA_WIDTH
            ) 
            port map (
                f_aclk => S_AXI_ACLK, 
                i_aresetn => S_AXI_ARESETN, 
                i_pwm_module => i_pwm_module, 
                i_pwm_width => i_pwm_width5, 
                o_pwm => rgb_led_tri_o(5)
            );
	-- I/O Connections assignments
	S_AXI_AWREADY	<= axi_awready;
	S_AXI_WREADY	<= axi_wready;
	S_AXI_BRESP	<= axi_bresp;
	S_AXI_BVALID	<= axi_bvalid;
	S_AXI_ARREADY	<= axi_arready;
	S_AXI_RDATA	<= axi_rdata;
	S_AXI_RRESP	<= axi_rresp;
	S_AXI_RVALID	<= axi_rvalid;
	-- Implement axi_awready generation
	-- axi_awready is asserted for one S_AXI_ACLK clock cycle when both
	-- S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is
	-- de-asserted when reset is low.
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_awready <= '0';
	      aw_en <= '1';
	    else
	      if (axi_awready = '0' and S_AXI_AWVALID = '1' and S_AXI_WVALID = '1' and aw_en = '1') then
	        -- slave is ready to accept write address when
	        -- there is a valid write address and write data
	        -- on the write address and data bus. This design 
	        -- expects no outstanding transactions. 
	        axi_awready <= '1';
	        elsif (S_AXI_BREADY = '1' and axi_bvalid = '1') then
	            aw_en <= '1';
	        	axi_awready <= '0';
	      else
	        axi_awready <= '0';
	      end if;
	    end if;
	  end if;
	end process;
	-- Implement axi_awaddr latching
	-- This process is used to latch the address when both 
	-- S_AXI_AWVALID and S_AXI_WVALID are valid. 
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_awaddr <= (others => '0');
	    else
	      if (axi_awready = '0' and S_AXI_AWVALID = '1' and S_AXI_WVALID = '1' and aw_en = '1') then
	        -- Write Address latching
	        axi_awaddr <= S_AXI_AWADDR;
	      end if;
	    end if;
	  end if;                   
	end process; 
	-- Implement axi_wready generation
	-- axi_wready is asserted for one S_AXI_ACLK clock cycle when both
	-- S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is 
	-- de-asserted when reset is low. 
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_wready <= '0';
	    else
	      if (axi_wready = '0' and S_AXI_WVALID = '1' and S_AXI_AWVALID = '1' and aw_en = '1') then
	          -- slave is ready to accept write data when 
	          -- there is a valid write address and write data
	          -- on the write address and data bus. This design 
	          -- expects no outstanding transactions.           
	          axi_wready <= '1';
	      else
	        axi_wready <= '0';
	      end if;
	    end if;
	  end if;
	end process; 
	-- Implement memory mapped register select and write logic generation
	-- The write data is accepted and written to memory mapped registers when
	-- axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
	-- select byte enables of slave registers while writing.
	-- These registers are cleared when reset (active low) is applied.
	-- Slave register write enable is asserted when valid address and data are available
	-- and the slave is ready to accept the write address and write data.
	slv_reg_wren <= axi_wready and S_AXI_WVALID and axi_awready and S_AXI_AWVALID ;
	process (S_AXI_ACLK)
	variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0); 
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
                i_pwm_module <= (others => '0');
                i_pwm_width0 <= (others => '0');
                i_pwm_width1 <= (others => '0');
                i_pwm_width2 <= (others => '0');
                i_pwm_width3 <= (others => '0');
                i_pwm_width4 <= (others => '0');
                i_pwm_width5 <= (others => '0');
	    else
	      loc_addr := axi_awaddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
	      if (slv_reg_wren = '1') then
	        case loc_addr is
	          when b"000" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 0
	                i_pwm_module(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"001" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 1
	                i_pwm_width0(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"010" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 2
	                i_pwm_width1(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"011" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 3
	                i_pwm_width2(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"100" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 4
	                i_pwm_width3(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"101" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 5
	                i_pwm_width4(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when b"110" =>
	            for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
	              if ( S_AXI_WSTRB(byte_index) = '1' ) then
	                -- Respective byte enables are asserted as per write strobes                   
	                -- slave registor 6
	                i_pwm_width5(byte_index*8+7 downto byte_index*8) <= S_AXI_WDATA(byte_index*8+7 downto byte_index*8);
	              end if;
	            end loop;
	          when others =>
	            i_pwm_module <= i_pwm_module;
	            i_pwm_width0 <= i_pwm_width0;
	            i_pwm_width1 <= i_pwm_width1;
	            i_pwm_width2 <= i_pwm_width2;
	            i_pwm_width3 <= i_pwm_width3;
	            i_pwm_width4 <= i_pwm_width4;
	            i_pwm_width5 <= i_pwm_width5;
	        end case;
	      end if;
	    end if;
	  end if;                   
	end process; 
	-- Implement write response logic generation
	-- The write response and response valid signals are asserted by the slave 
	-- when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted.  
	-- This marks the acceptance of address and indicates the status of 
	-- write transaction.
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_bvalid  <= '0';
	      axi_bresp   <= "00"; --need to work more on the responses
	    else
	      if (axi_awready = '1' and S_AXI_AWVALID = '1' and axi_wready = '1' and S_AXI_WVALID = '1' and axi_bvalid = '0'  ) then
	        axi_bvalid <= '1';
	        axi_bresp  <= "00"; 
	      elsif (S_AXI_BREADY = '1' and axi_bvalid = '1') then   --check if bready is asserted while bvalid is high)
	        axi_bvalid <= '0';                                 -- (there is a possibility that bready is always asserted high)
	      end if;
	    end if;
	  end if;                   
	end process; 
	-- Implement axi_arready generation
	-- axi_arready is asserted for one S_AXI_ACLK clock cycle when
	-- S_AXI_ARVALID is asserted. axi_awready is 
	-- de-asserted when reset (active low) is asserted. 
	-- The read address is also latched when S_AXI_ARVALID is 
	-- asserted. axi_araddr is reset to zero on reset assertion.
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_arready <= '0';
	      axi_araddr  <= (others => '1');
	    else
	      if (axi_arready = '0' and S_AXI_ARVALID = '1') then
	        -- indicates that the slave has acceped the valid read address
	        axi_arready <= '1';
	        -- Read Address latching 
	        axi_araddr  <= S_AXI_ARADDR;           
	      else
	        axi_arready <= '0';
	      end if;
	    end if;
	  end if;                   
	end process; 
	-- Implement axi_arvalid generation
	-- axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both 
	-- S_AXI_ARVALID and axi_arready are asserted. The slave registers 
	-- data are available on the axi_rdata bus at this instance. The 
	-- assertion of axi_rvalid marks the validity of read data on the 
	-- bus and axi_rresp indicates the status of read transaction.axi_rvalid 
	-- is deasserted on reset (active low). axi_rresp and axi_rdata are 
	-- cleared to zero on reset (active low).  
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then
	    if S_AXI_ARESETN = '0' then
	      axi_rvalid <= '0';
	      axi_rresp  <= "00";
	    else
	      if (axi_arready = '1' and S_AXI_ARVALID = '1' and axi_rvalid = '0') then
	        -- Valid read data is available at the read data bus
	        axi_rvalid <= '1';
	        axi_rresp  <= "00"; -- 'OKAY' response
	      elsif (axi_rvalid = '1' and S_AXI_RREADY = '1') then
	        -- Read data is accepted by the master
	        axi_rvalid <= '0';
	      end if;            
	    end if;
	  end if;
	end process;
	-- Implement memory mapped register select and read logic generation
	-- Slave register read enable is asserted when valid address is available
	-- and the slave is ready to accept the read address.
	slv_reg_rden <= axi_arready and S_AXI_ARVALID and (not axi_rvalid) ;
	process (i_pwm_module, i_pwm_width0, i_pwm_width1, i_pwm_width2, i_pwm_width3, i_pwm_width4, i_pwm_width5, axi_araddr, S_AXI_ARESETN, slv_reg_rden)
	variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0);
	begin
	    -- Address decoding for reading registers
	    loc_addr := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
	    case loc_addr is
	      when b"000" =>
	        reg_data_out <= i_pwm_module;
	      when b"001" =>
	        reg_data_out <= i_pwm_width0;
	      when b"010" =>
	        reg_data_out <= i_pwm_width1;
	      when b"011" =>
	        reg_data_out <= i_pwm_width2;
	      when b"100" =>
	        reg_data_out <= i_pwm_width3;
	      when b"101" =>
	        reg_data_out <= i_pwm_width4;
	      when b"110" =>
	        reg_data_out <= i_pwm_width5;
	      when others =>
	        reg_data_out  <= (others => '0');
	    end case;
	end process; 
	-- Output register or memory read data
	process( S_AXI_ACLK ) is
	begin
	  if (rising_edge (S_AXI_ACLK)) then
	    if ( S_AXI_ARESETN = '0' ) then
	      axi_rdata  <= (others => '0');
	    else
	      if (slv_reg_rden = '1') then
	        -- When there is a valid read address (S_AXI_ARVALID) with 
	        -- acceptance of read address by the slave (axi_arready), 
	        -- output the read dada 
	        -- Read address mux
	          axi_rdata <= reg_data_out;     -- register read data
	      end if;   
	    end if;
	  end if;
	end process;
	-- Add user logic here
	-- User logic ends
end arch_imp;

There are seven registers in total which allow controlling the LEDs, the registers are split into three groups: a control register shared across all six channels, three registers to control R, B, B channels for LED1 and three registers for LED2:
Register name Number of dedicated bits Description
i_pwm_module C_S_AXI_DATA_WIDTH (32) maximum value the PWM module counts to
Register name Number of dedicated bits Description
i_pwm_width0 C_S_AXI_DATA_WIDTH (32) maximum value of the counter when the PWM output is on for RED LED1
i_pwm_width1 C_S_AXI_DATA_WIDTH (32) maximum value of the counter when the PWM output is on for GREEN LED1
i_pwm_width2 C_S_AXI_DATA_WIDTH (32) maximum value of the counter when the PWM output is on for BLUE LED1
Register name Number of dedicated bits Description
i_pwm_width3 C_S_AXI_DATA_WIDTH (32) maximum value of the counter when the PWM output is on for RED LED2
i_pwm_width4 C_S_AXI_DATA_WIDTH (32) maximum value of the counter when the PWM output is on for GREEN LED2
i_pwm_width5 C_S_AXI_DATA_WIDTH (32) maximum value of the counter when the PWM output is on for BLUE LED2
Code example: a PWM module pwm.vhd (non-autogenerated):
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity pwm is
  generic (
    G_DATA_WIDTH : integer := 32
  );
  port (
    -- Clock Signal
    f_aclk : in std_logic;
    -- Reset Signal. This Signal is Active LOW
    i_aresetn : in std_logic;
    i_pwm_module : in std_logic_vector(G_DATA_WIDTH - 1 downto 0);
    i_pwm_width : in std_logic_vector(G_DATA_WIDTH - 1 downto 0);
    -- The PWM-ed output
    o_pwm : out std_logic
  );
end pwm;
architecture arch_imp of pwm is
  signal s_max_count : unsigned(G_DATA_WIDTH - 1 downto 0);
  signal s_pwm_counter : unsigned(G_DATA_WIDTH - 1 downto 0);
  signal s_pwm_width : unsigned(G_DATA_WIDTH - 1 downto 0);
  signal s_tc_pwm_counter : std_logic;
begin
  s_tc_pwm_counter  <= '0' when(s_pwm_counter < s_max_count) else '1';  -- use to strobe new word
  p_state_out : process(f_aclk, i_aresetn)
  begin
    if (i_aresetn = '0') then
      s_max_count <= (others=>'0');
      s_pwm_width <= (others=>'0');
      s_pwm_counter <= (others=>'0');
      o_pwm <= '0';
    elsif (rising_edge(f_aclk)) then
      s_max_count <= unsigned(i_pwm_module);
      if (s_pwm_counter = 0) and (s_pwm_width /= s_max_count) then
        o_pwm <= '0';
      elsif (s_pwm_counter <= s_pwm_width) then
        o_pwm <= '1';
      else
        o_pwm <= '0';
      end if;
          
      if (s_tc_pwm_counter='1') then
        s_pwm_width <= unsigned(i_pwm_width);
      end if;
          
      if (s_pwm_counter = s_max_count) then
        s_pwm_counter <= to_unsigned(0, G_DATA_WIDTH);
      else
        s_pwm_counter <= s_pwm_counter + 1;
      end if;
    end if;
  end process p_state_out;
end arch_imp;
    
Code example: a C code to control the LEDs in a predefined sequence main.c (non-autogenerated):
/*
 FreeRTOS based RGB PWM LED Demo for Arty Z7-20 board
 ====================================================
 This demo is a 'Hello World' application type running on
 Zynq FPGA chip from Xilinx and a starting point for 
 doing Block design, IP Core creation, FreeRTOS application
 development, integration of PL and PS sides and packaging
 the whole project into a compact and elegant solution which
 sets a reference point for future projects.
 This project has a custom designed IP Core capable of
 driving 6 independent PWM channels which outputs are 
 directly connected to the two RGB LEDs of the Arty board. 
 
 Copyright (c) 2020 Dmitry Pakhomenko.
 dmitryp@magictale.com
 http://magictale.com
 
 This code is in the public domain.
*/
/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
/* Xilinx includes. */
#include "xil_printf.h"
#include "xparameters.h"
#include "xil_io.h"
#define BTIMER_ID 1
#define GTIMER_ID 2
#define RTIMER_ID 3
#define DELAY_1_SECOND 1000UL
#define DELAY_30_MSECONDS 30UL
#define DELAY_40_MSECONDS 40UL
#define DELAY_50_MSECONDS 50UL
#define RGB_LED_PWM_BASE XPAR_LED_PWM_0_S00_AXI_BASEADDR
#define RGB_LED_PWM_MODULE_OFFSET 0
#define RGB_LED0_PWM_BLUE_WIDTH_OFFSET 1
#define RGB_LED0_PWM_GREEN_WIDTH_OFFSET 2
#define RGB_LED0_PWM_RED_WIDTH_OFFSET 3
#define RGB_LED1_PWM_BLUE_WIDTH_OFFSET 4
#define RGB_LED1_PWM_GREEN_WIDTH_OFFSET 5
#define RGB_LED1_PWM_RED_WIDTH_OFFSET 6
#define RGB_LED_OFFSET_MULTIPLIER 4
#define RGB_LED_MAX_DUTY_CYCLE 0x30
#define RGB_LED_PWM_MODULE 0x80
#define ITEGRATIONS_IN_RGB_MODE 10
typedef enum
{
    RED_MODE = 0,
    GREEN_MODE,
    BLUE_MODE,
    RGB_MODE,
    END_MODE
} Blinking_Mode_Enum;
typedef struct
{
    uint8_t id;
    uint8_t duty_cycle;
    uint8_t intensity_ascending;
    uint8_t register_offset;
    TimerHandle_t xTimer;
} LED_Descriptor_Struct;
typedef struct
{
    LED_Descriptor_Struct leds[3];
    Blinking_Mode_Enum blinking_mode;
    uint8_t iterations_in_rgb_mode;
} RGB_LED_Descriptor_Struct;
static void prvLedCtrlTask(void *pvParameters);
static void vBlue0TimerCallback(TimerHandle_t pxTimer);
static void vGreen0TimerCallback(TimerHandle_t pxTimer);
static void vRed0TimerCallback(TimerHandle_t pxTimer);
static void vBlue1TimerCallback(TimerHandle_t pxTimer);
static void vGreen1TimerCallback(TimerHandle_t pxTimer);
static void vRed1TimerCallback(TimerHandle_t pxTimer);
static void vUpdateDutyCycle(RGB_LED_Descriptor_Struct * p_rgb_led_descriptor,
    LED_Descriptor_Struct * p_led_descriptor, uint8_t progress_mode);
static void progressBlinkingMode(RGB_LED_Descriptor_Struct * p_rgb_led_descriptor);
static TaskHandle_t xLedControlTask;
static RGB_LED_Descriptor_Struct rgb_led0 =
    {.leds =
        {
            {.id = RED_MODE, .duty_cycle = 0, .intensity_ascending = TRUE, .register_offset = RGB_LED0_PWM_RED_WIDTH_OFFSET, .xTimer = NULL},
            {.id = GREEN_MODE, .duty_cycle = 0, .intensity_ascending = TRUE, .register_offset = RGB_LED0_PWM_GREEN_WIDTH_OFFSET, .xTimer = NULL},
            {.id = BLUE_MODE, .duty_cycle = 0, .intensity_ascending = TRUE, .register_offset = RGB_LED0_PWM_BLUE_WIDTH_OFFSET, .xTimer = NULL}
        },
      .blinking_mode = RED_MODE,
      .iterations_in_rgb_mode = ITEGRATIONS_IN_RGB_MODE
    };
static RGB_LED_Descriptor_Struct rgb_led1 =
    {.leds =
        {
            {.id = RED_MODE, .duty_cycle = 0, .intensity_ascending = TRUE, .register_offset = RGB_LED1_PWM_RED_WIDTH_OFFSET, .xTimer = NULL},
            {.id = GREEN_MODE, .duty_cycle = 0, .intensity_ascending = TRUE, .register_offset = RGB_LED1_PWM_GREEN_WIDTH_OFFSET, .xTimer = NULL},
            {.id = BLUE_MODE, .duty_cycle = 0, .intensity_ascending = TRUE, .register_offset = RGB_LED1_PWM_BLUE_WIDTH_OFFSET, .xTimer = NULL}
        },
      .blinking_mode = RED_MODE,
      .iterations_in_rgb_mode = ITEGRATIONS_IN_RGB_MODE / 2
    };
int main(void)
{
    const TickType_t x30mseconds = pdMS_TO_TICKS(DELAY_30_MSECONDS);
    const TickType_t x40mseconds = pdMS_TO_TICKS(DELAY_40_MSECONDS);
    const TickType_t x50mseconds = pdMS_TO_TICKS(DELAY_50_MSECONDS);
    xil_printf("\r\n\r\nFreeRTOS version of RGB LED PWM Demo for Arty Z7-20 board\r\n");
    xTaskCreate(prvLedCtrlTask, /* The function that implements the task. */
                (const char*) "Led", /* Text name for the task, provided to assist debugging only. */
                configMINIMAL_STACK_SIZE, /* The stack allocated to the task. */
                NULL, /* The task parameter is not used, so set to NULL. */
                tskIDLE_PRIORITY, /* The task runs at the idle priority. */
                &xLedControlTask );
    //=== LED 0 ===
    rgb_led0.leds[BLUE_MODE].xTimer = xTimerCreate((const char *)"Blue0Timer",
                x30mseconds,
                pdFALSE,
                (void*)BTIMER_ID,
                vBlue0TimerCallback);
    /* Check the timer was created. */
    configASSERT(rgb_led0.leds[BLUE_MODE].xTimer);
    rgb_led0.leds[GREEN_MODE].xTimer = xTimerCreate((const char *)"Green0Timer",
                x40mseconds,
                pdFALSE,
                (void*)GTIMER_ID,
                vGreen0TimerCallback);
    /* Check the timer was created. */
    configASSERT(rgb_led0.leds[GREEN_MODE].xTimer);
    rgb_led0.leds[RED_MODE].xTimer = xTimerCreate((const char *)"Red0Timer",
                x50mseconds,
                pdFALSE,
                (void*)RTIMER_ID,
                vRed0TimerCallback);
    /* Check the timer was created. */
    configASSERT(rgb_led0.leds[RED_MODE].xTimer);
    //=== LED 1 ===
    rgb_led1.leds[BLUE_MODE].xTimer = xTimerCreate((const char *)"Blue1Timer",
                x30mseconds / 2,
                pdFALSE,
                (void*)BTIMER_ID,
                vBlue1TimerCallback);
    /* Check the timer was created. */
    configASSERT(rgb_led1.leds[BLUE_MODE].xTimer);
    rgb_led1.leds[GREEN_MODE].xTimer = xTimerCreate((const char *)"Green1Timer",
                x40mseconds / 2,
                pdFALSE,
                (void*)GTIMER_ID,
                vGreen1TimerCallback);
    /* Check the timer was created. */
    configASSERT(rgb_led1.leds[GREEN_MODE].xTimer);
    rgb_led1.leds[RED_MODE].xTimer = xTimerCreate((const char *)"Red1Timer",
                x50mseconds / 2,
                pdFALSE,
                (void*)RTIMER_ID,
                vRed1TimerCallback);
    /* Check the timer was created. */
    configASSERT(rgb_led0.leds[RED_MODE].xTimer);
    Xil_Out32(RGB_LED_PWM_BASE + RGB_LED_PWM_MODULE_OFFSET, RGB_LED_PWM_MODULE);
    /* start the timers with a block time of 0 ticks. This means as soon
       as the schedule starts the timers will start running and will expire after
       predefined number of milliseconds */
    xTimerStart(rgb_led0.leds[RED_MODE].xTimer, 0);
    xTimerStart(rgb_led0.leds[GREEN_MODE].xTimer, 0);
    xTimerStart(rgb_led0.leds[BLUE_MODE].xTimer, 0);
    xTimerStart(rgb_led1.leds[RED_MODE].xTimer, 0);
    xTimerStart(rgb_led1.leds[GREEN_MODE].xTimer, 0);
    xTimerStart(rgb_led1.leds[BLUE_MODE].xTimer, 0);
    /* Start the tasks and timer running. */
    vTaskStartScheduler();
    /* If all is well, the scheduler will now be running, and the following line
    will never be reached.  If the following line does execute, then there was
    insufficient FreeRTOS heap memory available for the idle and/or timer tasks
    to be created.  See the memory management section on the FreeRTOS web site
    for more details. */
    for(;;);
}
void progressBlinkingMode(RGB_LED_Descriptor_Struct * p_rgb_led_descriptor)
{
    if (p_rgb_led_descriptor->blinking_mode == RGB_MODE)
    {
        // In RGB mode we don't immediately progress to the next state but rather
        // loop for several cycles defined in iterations_in_rgb_mode
        if (p_rgb_led_descriptor->iterations_in_rgb_mode == 0)
        {
            p_rgb_led_descriptor->blinking_mode++;
        }
        else
        {
            p_rgb_led_descriptor->iterations_in_rgb_mode--;
        }
    }
    else
    {
        p_rgb_led_descriptor->blinking_mode++;
    }
    if (p_rgb_led_descriptor->blinking_mode >= END_MODE)
    {
        p_rgb_led_descriptor->blinking_mode = RED_MODE;
        Xil_Out32(RGB_LED_PWM_BASE + p_rgb_led_descriptor->leds[RED_MODE].register_offset * RGB_LED_OFFSET_MULTIPLIER, 0);
        Xil_Out32(RGB_LED_PWM_BASE + p_rgb_led_descriptor->leds[GREEN_MODE].register_offset * RGB_LED_OFFSET_MULTIPLIER, 0);
        Xil_Out32(RGB_LED_PWM_BASE + p_rgb_led_descriptor->leds[BLUE_MODE].register_offset * RGB_LED_OFFSET_MULTIPLIER, 0);
        p_rgb_led_descriptor->leds[RED_MODE].duty_cycle = 0;
        p_rgb_led_descriptor->leds[GREEN_MODE].duty_cycle = 0;
        p_rgb_led_descriptor->leds[BLUE_MODE].duty_cycle = 0;
        p_rgb_led_descriptor->leds[RED_MODE].intensity_ascending = TRUE;
        p_rgb_led_descriptor->leds[GREEN_MODE].intensity_ascending = TRUE;
        p_rgb_led_descriptor->leds[BLUE_MODE].intensity_ascending = TRUE;
        p_rgb_led_descriptor->iterations_in_rgb_mode = ITEGRATIONS_IN_RGB_MODE;
    }
}
static void prvLedCtrlTask(void *pvParameters)
{
    const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
    for(;;)
    {
        /* Delay for 1 second. */
        vTaskDelay(x1second);
        // TODO: instead of just waiting for timers do something useful in main thread
    }
}
static void vUpdateDutyCycle(RGB_LED_Descriptor_Struct * p_rgb_led_descriptor,
    LED_Descriptor_Struct * p_led_descriptor, uint8_t progress_mode)
{
    UBaseType_t uxSavedInterruptStatus;
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    if (
        (p_rgb_led_descriptor->blinking_mode == RGB_MODE) ||
        (p_rgb_led_descriptor->blinking_mode == RED_MODE && p_led_descriptor->id == RED_MODE) ||
        (p_rgb_led_descriptor->blinking_mode == BLUE_MODE && p_led_descriptor->id == BLUE_MODE) ||
        (p_rgb_led_descriptor->blinking_mode == GREEN_MODE && p_led_descriptor->id == GREEN_MODE)
        )
    {
        Xil_Out32(RGB_LED_PWM_BASE + p_led_descriptor->register_offset * RGB_LED_OFFSET_MULTIPLIER, p_led_descriptor->duty_cycle);
        if ( p_led_descriptor->intensity_ascending == TRUE )
        {
            p_led_descriptor->duty_cycle++;
            if (p_led_descriptor->duty_cycle >= RGB_LED_MAX_DUTY_CYCLE)
            {
                p_led_descriptor->intensity_ascending = FALSE;
            }
        }
        else
        {
            p_led_descriptor->duty_cycle--;
            if (p_led_descriptor->duty_cycle == 0)
            {
                p_led_descriptor->intensity_ascending = TRUE;
                if ((progress_mode == TRUE && p_rgb_led_descriptor->blinking_mode == RGB_MODE) ||
                     p_rgb_led_descriptor->blinking_mode == RED_MODE ||
                     p_rgb_led_descriptor->blinking_mode == BLUE_MODE ||
                     p_rgb_led_descriptor->blinking_mode == GREEN_MODE
                   )
                {
                    // In RGB mode we progress blinking mode only at the end of cycle of just one color
                    // in other modes we always progress
                    progressBlinkingMode(p_rgb_led_descriptor);
                }
            }
        }
    }
    taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
}
//=== LED 0 ===
static void vBlue0TimerCallback(TimerHandle_t pxTimer)
{
    long lTimerId;
    configASSERT(pxTimer);
    lTimerId = (long)pvTimerGetTimerID(pxTimer);
    if (lTimerId != BTIMER_ID)
    {
        xil_printf("Blue 0 Timer failed\r\n");
    }
    vUpdateDutyCycle(&rgb_led0, &rgb_led0.leds[BLUE_MODE], TRUE);
    xTimerStart(rgb_led0.leds[BLUE_MODE].xTimer, 0);
}
static void vGreen0TimerCallback(TimerHandle_t pxTimer)
{
    long lTimerId;
    configASSERT(pxTimer);
    lTimerId = (long)pvTimerGetTimerID(pxTimer);
    if (lTimerId != GTIMER_ID)
    {
        xil_printf("Green 0 Timer failed\r\n");
    }
    vUpdateDutyCycle(&rgb_led0, &rgb_led0.leds[GREEN_MODE], FALSE);
    xTimerStart(rgb_led0.leds[GREEN_MODE].xTimer, 0);
}
static void vRed0TimerCallback(TimerHandle_t pxTimer)
{
    long lTimerId;
    configASSERT(pxTimer);
    lTimerId = (long)pvTimerGetTimerID(pxTimer);
    if (lTimerId != RTIMER_ID)
    {
        xil_printf("Red 0 Timer failed\r\n");
    }
    vUpdateDutyCycle(&rgb_led0, &rgb_led0.leds[RED_MODE], FALSE);
    xTimerStart(rgb_led0.leds[RED_MODE].xTimer, 0);
}
//=== LED 1 ===
static void vBlue1TimerCallback(TimerHandle_t pxTimer)
{
    long lTimerId;
    configASSERT(pxTimer);
    lTimerId = (long)pvTimerGetTimerID(pxTimer);
    if (lTimerId != BTIMER_ID)
    {
        xil_printf("Blue 1 Timer failed\r\n");
    }
    vUpdateDutyCycle(&rgb_led1, &rgb_led1.leds[BLUE_MODE], TRUE);
    xTimerStart(rgb_led1.leds[BLUE_MODE].xTimer, 0);
}
static void vGreen1TimerCallback(TimerHandle_t pxTimer)
{
    long lTimerId;
    configASSERT(pxTimer);
    lTimerId = (long)pvTimerGetTimerID(pxTimer);
    if (lTimerId != GTIMER_ID)
    {
        xil_printf("Green 1 Timer failed\r\n");
    }
    vUpdateDutyCycle(&rgb_led1, &rgb_led1.leds[GREEN_MODE], FALSE);
    xTimerStart(rgb_led1.leds[GREEN_MODE].xTimer, 0);
}
static void vRed1TimerCallback(TimerHandle_t pxTimer)
{
    long lTimerId;
    configASSERT(pxTimer);
    lTimerId = (long)pvTimerGetTimerID(pxTimer);
    if (lTimerId != RTIMER_ID)
    {
        xil_printf("Red 1 Timer failed\r\n");
    }
    vUpdateDutyCycle(&rgb_led1, &rgb_led1.leds[RED_MODE], FALSE);
    xTimerStart(rgb_led1.leds[RED_MODE].xTimer, 0);
}

Video tutorial:

The complete process of building Vivado and SDK projects from scratch to a point of deploying it on a target platform is given below:
The process of building the demo from TCL scripts and flashing it to Arty’s QSPI is given below:

Downloads:

References: