Création d'un nouveau composant IP

Ajouter un nouveau bloc IP à la bibliothèque

Si vous commencez une nouvelle séance, démarrez Vivado en exécutant les commandes suivantes dans un terminal :

source /opt/Xilinx/Vivado/2019.1/settings64.sh
source /opt/Xilinx/SDK/2019.1/settings64.sh
cd /Data/etudiant/soc2020
LANG=en_US.UTF-8 vivado &

Ouvrez ensuite votre projet zybo-minimal.

À l’intérieur du composant Zynq-7000, le processeur communique avec ses périphériques à travers un bus respectant la spécification AXI (Advanced eXtensible Interface). Dans cette étape, nous allons créer un nouveau composant IP réalisant un contrôleur de bus SPI compatible avec le bus AXI.

  1. Dans le menu Tools de Vivado, choisissez Create and Package IP.
  2. Sélectionnez Create a new AXI4 peripheral.
  3. Donnez un nom au périphérique : SPIDevice.
  4. Avant de terminer, sélectionnez Edit IP.

Vivado ouvre une nouvelle fenêtre pour modifier le nouveau composant. Le panneau Sources contient une arborescence de deux fichiers VHDL.

  1. Ouvrez le fichier SPIDevice_v1_0_S00_AXI.vhd.

Le fichier contient des processus qui gèrent les demandes de lecture et d’écriture en provenance du processeur.

  1. Remplacez le contenu du fichier par le code suivant :
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity SPIDevice_v1_0_S00_AXI is
    generic (
        -- Nombre de bits du bus de données
        C_S_AXI_DATA_WIDTH : integer := 32;
        -- Nombre de bits utilisés sur le bus d'adresses
        C_S_AXI_ADDR_WIDTH : integer := 4
    );
    port (
        -- ---------------------------------------------------------------------
        -- Ajoutez vos ports ici:

        -- Fin des ports personnalisés.
        -- ---------------------------------------------------------------------

        -- Signal d'horloge global.
        s_axi_aclk : in std_logic;
        -- Commande de réinitialisation, active au niveau bas.
        s_axi_aresetn : in std_logic;

        -- ---------------------------------------------------------------------
        -- Bus AXI : accès en écriture.
        -- ---------------------------------------------------------------------

        -- Adresse du registre à modifier.
        s_axi_awaddr : in std_logic_vector(C_S_AXI_ADDR_WIDTH - 1 downto 0);
        -- Type de protection en écriture.
        s_axi_awprot : in std_logic_vector(2 downto 0);
        -- Indique que l'adresse en écriture est valide.
        s_axi_awvalid : in std_logic;
        -- Indique que ce composant est prêt à accepter une nouvelle adresse en écriture.
        s_axi_awready : out std_logic;
        -- La donnée à écrire.
        s_axi_wdata : in std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
        -- Sélection des octets à écrire.
        s_axi_wstrb : in std_logic_vector(C_S_AXI_DATA_WIDTH / 8 - 1 downto 0);
        -- Indique que la donnée à écrire est valide.
        s_axi_wvalid : in std_logic;
        -- Indique que ce composant est prêt à recevoir une nouvelle donnée à écrire.
        s_axi_wready : out std_logic;
        -- Indicateur d'état de l'opération d'écriture.
        s_axi_bresp : out std_logic_vector(1 downto 0);
        -- Indique que bresp est valide.
        s_axi_bvalid : out std_logic;
        -- Indique que le maître est prêt à recevoir une nouvelle valeur de bresp.
        s_axi_bready : in std_logic;

        -- ---------------------------------------------------------------------
        -- Bus AXI : accès en lecture.
        -- ---------------------------------------------------------------------

        -- Adresse du registre à lire.
        s_axi_araddr : in std_logic_vector(C_S_AXI_ADDR_WIDTH - 1 downto 0);
        -- Type de protection en lecture.
        s_axi_arprot : in std_logic_vector(2 downto 0);
        -- Indique que l'adresse en lecture est valide.
        s_axi_arvalid : in std_logic;
        -- Indique que ce composant est prêt à recevoir une nouvelle adresse en lecture.
        s_axi_arready : out std_logic;
        -- La donnée lue.
        s_axi_rdata : out std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
        -- Indicateur d'état de l'opération de lecture.
        s_axi_rresp : out std_logic_vector(1 downto 0);
        -- Indique que la donnée lue est valide.
        s_axi_rvalid : out std_logic;
        -- Indique que le maître est prêt à recevoir une nouvelle valeur sur rdata.
        s_axi_rready : in std_logic
    );
end SPIDevice_v1_0_S00_AXI;

architecture arch_imp of SPIDevice_v1_0_S00_AXI is
    -- Indice du bit de poids faible de l'adresse du registre sur les
    -- bus d'adresses awaddr et araddr.
    -- ADDR_LSB = 2 pour les bus 32 bits
    -- ADDR_LSB = 3 pour les bus 64 bits
    constant ADDR_LSB : integer := C_S_AXI_DATA_WIDTH / 32 + 1;

    -- J'utiliserai 2 bits du bus d'adresses pour identifier le registre où écrire.
    signal awaddr_reg : std_logic_vector(1 downto 0);
    signal awready_next, awready_reg : std_logic;
    signal bvalid_reg : std_logic;

    -- J'utiliserai 2 bits du bus d'adresses pour identifier le registre à lire.
    signal araddr_reg : std_logic_vector(1 downto 0);
    signal arready_next, arready_reg : std_logic;
    signal rvalid_reg : std_logic;

    ---- Registres de données, à personnaliser.
    signal data_reg0 :std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
    signal data_reg1 :std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
    signal data_reg2 :std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
    signal data_reg3 :std_logic_vector(C_S_AXI_DATA_WIDTH - 1 downto 0);
begin
    -- -------------------------------------------------------------------------
    -- Gestion des écritures
    -- -------------------------------------------------------------------------

    -- Si une adresse et une donnée sont disponibles, je serai prêt à accepter
    -- une nouvelle adresse et une nouvelle donnée au cycle d'horloge suivant.
    -- awready et wready seront actifs pendant une période d'horloge.
	awready_next <= not awready_reg and s_axi_awvalid and s_axi_wvalid;

    p_awready_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                awready_reg <= '0';
            else
                awready_reg <= awready_next;
            end if;
        end if;
    end process p_awready_reg;

    s_axi_awready <= awready_reg;
    s_axi_wready <= awready_reg;

    -- Je mémorise l'adresse avant d'effectuer l'écriture.
    p_awaddr_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                awaddr_reg <= (others => '0');
            elsif awready_next = '1' then
                awaddr_reg <= s_axi_awaddr(ADDR_LSB + awaddr_reg'length - 1 downto ADDR_LSB);
            end if;
        end if;
    end process p_awaddr_reg;

    -- Je copie les octets sélectionnés du bus wdata vers le registre
    -- dont l'adresse est indiquée par awaddr_reg.
    p_data_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                data_reg0 <= (others => '0');
                data_reg1 <= (others => '0');
                data_reg2 <= (others => '0');
                data_reg3 <= (others => '0');
            elsif awready_reg = '1' then
                for i in 0 to C_S_AXI_DATA_WIDTH / 8 - 1 loop
                    if s_axi_wstrb(i) = '1' then
                        case awaddr_reg is
                            when "00" => data_reg0(i*8+7 downto i*8) <= s_axi_wdata(i*8+7 downto i*8);
                            when "01" => data_reg1(i*8+7 downto i*8) <= s_axi_wdata(i*8+7 downto i*8);
                            when "10" => data_reg2(i*8+7 downto i*8) <= s_axi_wdata(i*8+7 downto i*8);
                            when "11" => data_reg3(i*8+7 downto i*8) <= s_axi_wdata(i*8+7 downto i*8);
                        end case;
                    end if;
                end loop;
            end if;
        end if;
    end process p_data_reg;

    -- Gestion des indicateurs d'état de l'écriture.
    -- Passer bvalid à '1' au moment de l'écriture.
    -- Passer bvalid à '0' lorsque le maître passe bready à '1'.
    p_bvalid_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                bvalid_reg <= '0';
            elsif awready_reg = '1' then
                bvalid_reg <= '1';
            elsif s_axi_bready = '1' then
                bvalid_reg <= '0';
            end if;
        end if;
    end process p_bvalid_reg;

    s_axi_bresp <= "00";
    s_axi_bvalid <= awready_reg;

    -- -------------------------------------------------------------------------
    -- Gestion des lectures
    -- -------------------------------------------------------------------------

    -- Si une adresse est disponibles, je serai prêt à accepter
    -- une nouvelle adresse au cycle d'horloge suivant.
    -- s_axi_arready sera actif pendant une période d'horloge.
	arready_next <= not arready_reg and s_axi_arvalid;

    p_arready_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                arready_reg <= '0';
            else
                arready_reg <= arready_next;
            end if;
        end if;
    end process p_arready_reg;

    s_axi_arready <= arready_reg;

    -- Je mémorise l'adresse en lecture lorsque arvalid = '1'.
    p_araddr_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                araddr_reg <= (others => '0');
            elsif arready_next = '1' then
                araddr_reg <= s_axi_araddr(ADDR_LSB + araddr_reg'length - 1 downto ADDR_LSB);
            end if;
        end if;
    end process p_araddr_reg;

    -- Je copie vers le bus de données le registre dont l'adresse est indiquée par awaddr_reg.
    p_s_axi_rdata : process(s_axi_aclk)
    begin
        if rising_edge (s_axi_aclk) then
            if arready_reg = '1' then
                case araddr_reg is
                    when "00"  => s_axi_rdata <= data_reg0;
                    when "01"  => s_axi_rdata <= data_reg1;
                    when "10"  => s_axi_rdata <= data_reg2;
                    when "11"  => s_axi_rdata <= data_reg3;
                end case;
            end if;
        end if;
    end process p_s_axi_rdata;

    -- Gestion des indicateurs d'état de la lecture.
    -- Passer rvalid à '1' au moment où s_axi_rdata est mis à jour.
    -- Passer rvalid à '0' lorsque le maître passe rready à '1'.
    p_rvalid_reg : process(s_axi_aclk)
    begin
        if rising_edge(s_axi_aclk) then
            if s_axi_aresetn = '0' then
                rvalid_reg <= '0';
            elsif arready_reg = '1' then
                rvalid_reg <= '1';
            elsif s_axi_rready = '1' then
                rvalid_reg <= '0';
            end if;
        end if;
    end process p_rvalid_reg;

    s_axi_rresp <= "00";
    s_axi_rvalid <= rvalid_reg;
end arch_imp;
  1. Enregistrez le fichier SPIDevice_v1_0_S00_AXI.vhd.
  2. Dans le panneau Flow Navigator (à gauche), sous Project Manager, exécutez l’action Package IP.
  3. Choisissez Review and Package et pressez le bouton Package IP. Acceptez la fermeture du projet.

Ajouter le nouveau bloc IP dans l’architecture matérielle

Complétez le schéma :

  1. Dans l’onglet Diagram, pressez le bouton « + » (Add IP) de la barre d’outils.
  2. Sélectionnez le composant SPIDevice_v1.0.
  3. En haut de l’onglet Diagram, repérez le message Designer Assistance available et pressez Run Connection Automation.
  4. Dans l’onglet Address Editor, repérez l’adresse affectée au nouveau périphérique.

Synthétisez le matériel :

  1. Dans le panneau Flow Navigator, sous Program and Debug, choisissez Generate Bitstream.
  2. À la fin de la synthèse, pressez le bouton Cancel dans la boîte de dialogue.

Mettre à jour le programme d’amorçage

Reconstruisez le fichier boot.bin en exécutant la commande suivante dans le terminal où vous avez lancé Vivado :

bootgen -w -image scripts/zybo-minimal.bif -o fpga/zybo-minimal/boot.bin

Copiez le nouveau fichier fpga/zybo-minimal/boot.bin sur la carte microSD et redémarrez la carte Zybo.

Utiliser l’IP depuis le shell

Comme dans les étapes précédentes, connectez-vous à la carte Zybo à l’aide d’un terminal série.

En utilisant les commandes poke et peek, vérifiez que vous pouvez écrire et relire dans les sept registres de votre IP.