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.
SPIDevice
.Vivado ouvre une nouvelle fenêtre pour modifier le nouveau composant. Le panneau Sources contient une arborescence de deux fichiers VHDL.
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.
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;
SPIDevice_v1_0_S00_AXI.vhd
.Complétez le schéma :
SPIDevice_v1.0
.Synthétisez le matériel :
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.
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.