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;
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 |
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;
/* 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); }
Leave a Reply
You must be logged in to post a comment.