Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Adam Taylor
Published © LGPL

Debugging AMD FPGA AXI designs with a RPi Pico

FPGA design use AXI networks, this can be a complex protocol to work with. Lets look at how you can access AXI using a UART and RPi PICO

IntermediateFull instructions provided3 hours3,942
Debugging AMD FPGA AXI designs with a RPi Pico

Things used in this project

Story

Read more

Code

Uart

VHDL
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

use work.adiuvo_uart.all;

entity uart is generic (
  reset_level : std_logic := '0'; -- reset level which causes a reset
  clk_freq    : natural   := 100_000_000; -- oscillator frequency
  baud_rate   : natural   := 115200 -- baud rate 
);
port (
  --!System Inputs 
  clk   : in std_logic;
  reset : in std_logic;

  --!External Interfaces
  rx : in std_logic;
  tx  : out std_logic;

  --! Master AXIS Interface  
  m_axis_tready : in  std_logic;
  m_axis_tdata  : out std_logic_vector(7 downto 0);
  m_axis_tvalid : out std_logic;

  --! Slave AXIS Interface
  s_axis_tready : out  std_logic;
  s_axis_tdata  : in std_logic_vector(7 downto 0);
  s_axis_tvalid : in std_logic
  
  );
  
end entity;
architecture rtl of uart is

  constant bit_period : integer := (clk_freq/baud_rate) - 1;
  

  type cntrl_fsm is (idle, set_tx,wait_tx);
  type rx_fsm is (idle, start, sample, check, wait_axis);

  signal current_state : cntrl_fsm; --:= idle;
  signal rx_state      : rx_fsm;-- := idle;
  signal baud_counter  : unsigned(vector_size(real(clk_freq), real(baud_rate)) downto 0) := (others => '0'); --timer for outgoing signals 
  signal baud_en       : std_logic                                                       := '0';
  signal meta_reg      : std_logic_vector(3 downto 0)                                    := (others => '0'); -- fe detection too
  signal capture       : std_logic_vector(7 downto 0)                                    := (others => '0'); -- data and parity
  signal bit_count     : integer range 0 to 1023                                         := 0;
  signal pos_count     : integer range 0 to 15                                           := 0;
  signal running       : std_logic                                                       := '0';
  signal load_tx  : std_logic := '0';
  signal complete : std_logic := '0';

  signal tx_reg  : std_logic_vector(11 downto 0) := (others => '0');
  signal tmr_reg : std_logic_vector(11 downto 0) := (others => '0');
  signal payload : std_logic_vector(7 downto 0)  := (others => '0');
  constant zero  : std_logic_vector(tmr_reg'range) := (others => '0');
begin

  process (reset, clk)
  begin
    if reset = reset_level then
      current_state <= idle;
      payload       <= (others => '0');
      load_tx <= '0';
    elsif rising_edge(clk) then
      load_tx <= '0';
      case current_state is
        when idle =>
          if s_axis_tvalid = '1' then
            current_state <= set_tx;
            load_tx       <= '1';
            payload       <= s_axis_tdata;
          end if;
        when set_tx =>
          current_state <= wait_tx;
        when wait_tx =>
          if complete = '1' then
            current_state <= idle;
          end if;
        when others => 
         current_state <= idle;
      end case;
    end if;
  end process;

  s_axis_tready <= '1' when (current_state = idle) else '0';

  process (reset, clk)
  --! baud counter for output TX 
  begin
    if reset = reset_level then
      baud_counter <= (others => '0');
      baud_en      <= '0';
    elsif rising_edge(clk) then
      baud_en <= '0';
      if (load_tx = '1') then
        baud_counter <= (others => '0');
      elsif (baud_counter = bit_period) then
        baud_en      <= '1';
        baud_counter <= (others => '0');
      else
        baud_counter <= baud_counter + 1;
      end if;
    end if;
  end process;

  process (reset, clk)
  --!metastability protection rx signal
  begin
    if reset = reset_level then
      meta_reg <= (others => '1');
    elsif rising_edge(clk) then
      meta_reg <= meta_reg(meta_reg'high - 1 downto meta_reg'low) & rx;
    end if;
  end process;

  process (reset, clk)
  begin
    if reset = reset_level then
      pos_count <= 0;
      bit_count <= 0;
      capture     <= (others => '0');
      rx_state    <= idle;
      m_axis_tvalid <= '0';
      m_axis_tdata     <= (others => '0');
      
    elsif rising_edge(clk) then
      case rx_state is
 
        when idle =>
          m_axis_tvalid  <= '0';
          if meta_reg(meta_reg'high downto meta_reg'high - 1) = fe_det then 
            pos_count <= 0;
            bit_count <= 0;
            capture  <= (others => '0');
            rx_state <= start;
          end if;
        when start =>
          if bit_count = bit_period then
            bit_count <= 0;
            rx_state  <= sample;
          else
            bit_count <= bit_count + 1;
          end if;
        when sample =>
          bit_count <= bit_count + 1;
          rx_state  <= sample;
          if bit_count = (bit_period/2) and (pos_count < 8) then 
            capture <= meta_reg(meta_reg'high) & capture(capture'high downto capture'low + 1);
          elsif bit_count = bit_period then
            if pos_count = 8 then 
              rx_state <= check;
            else
              pos_count <= pos_count + 1;
              bit_count <= 0;
            end if;
          end if;
        when check =>
          if parity(capture) = '1' then
            m_axis_tvalid <= '1';
            m_axis_tdata  <= capture(7 downto 0);
            rx_state      <= wait_axis;
          else
            m_axis_tvalid <= '1';
            m_axis_tdata  <= capture(7 downto 0);
            rx_state      <= wait_axis;
          end if;
        when wait_axis =>
          if m_axis_tready = '1' then 
            m_axis_tvalid <= '0';
            rx_state      <= idle;    
          end if;
      end case;
    end if;
  end process;

  op_uart : process (reset, clk)
  begin
    if reset = reset_level then
      tx_reg  <= (others => '1');
      tmr_reg <= (others => '0');
    elsif rising_edge(clk) then
      if load_tx = '1' then
        tx_reg  <= stop_bit & not(parity(payload)) & payload & start_bit ;
        tmr_reg <= (others => '1');
      elsif baud_en = '1' then
        tx_reg  <= '1' & tx_reg(tx_reg'high downto tx_reg'low + 1);
        tmr_reg <= tmr_reg(tmr_reg'high - 1 downto tmr_reg'low) & '0';
      end if;
    end if;
  end process;

  tx       <= tx_reg(tx_reg'low);
  complete <= '1' when (tmr_reg = zero and current_state = wait_tx) else '0';
end architecture;

UART PKG

VHDL
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;


package adiuvo_uart is 

    function vector_size(clk_freq, baud_rate : real) return integer;
    function parity (a : std_logic_vector) return std_logic;
    constant fe_det     : std_logic_vector(1 downto 0) := "10";
    constant start_bit  : std_logic                    := '0';
    constant stop_bit   : std_logic_vector             := "11";
end package;

package body adiuvo_uart is 

    function vector_size(clk_freq, baud_rate : real) return integer is
        variable div                             : real;
        variable res                             : real;
      begin
        div := (clk_freq/baud_rate);
        res := CEIL(LOG(div)/LOG(2.0));
        return integer(res - 1.0);
      end;
    
      function parity (a : std_logic_vector) return std_logic is
        variable y         : std_logic := '0';
      begin
        for i in a'range loop
          y := y xor a(i);
        end loop;
        return y;
      end parity;
    

      
end package body adiuvo_uart;

protocol

VHDL
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

--Declare entity
entity axi_protocol is
	generic(
            G_AXIL_DATA_WIDTH    :integer   := 32;                                          --Width of AXI Lite data bus
            G_AXI_ADDR_WIDTH     :integer   := 32;                                          --Width of AXI Lite Address Bu
            G_AXI_ID_WIDTH       :integer   := 8;                                           --Width of AXI ID Bus
            G_AXI_AWUSER_WIDTH   :integer   := 1                                            --Width of AXI AW User bus
	);
	port(	
			--Master clock & reset
			clk 			 :in std_ulogic;                                            --System clock
			reset			 :in std_ulogic;                                            --System reset, async active low

            --! Master AXIS Interface  
            m_axis_tready : in  std_logic;
            m_axis_tdata  : out std_logic_vector(7 downto 0);
            m_axis_tvalid : out std_logic;

            --! Slave AXIS Interface
            s_axis_tready : out  std_logic;
            s_axis_tdata  : in std_logic_vector(7 downto 0);
            s_axis_tvalid : in std_logic;
            
            --! AXIL Interface
            --!Write address
            axi_awaddr    : out std_logic_vector(G_AXI_ADDR_WIDTH-1 downto 0);                  
            axi_awprot    : out std_logic_vector(2 downto 0);                   
            axi_awvalid   : out std_logic;
            --!write data
            axi_wdata     : out std_logic_vector(G_AXIL_DATA_WIDTH-1 downto 0);  
            axi_wstrb     : out std_logic_vector(G_AXIL_DATA_WIDTH/8-1 downto 0);
            axi_wvalid    : out std_logic; 
            --!write response
            axi_bready    : out std_logic;
            --!read address
            axi_araddr    : out std_logic_vector(G_AXI_ADDR_WIDTH-1 downto 0);               
            axi_arprot    : out std_logic_vector(2 downto 0);                   
            axi_arvalid   : out std_logic; 
            --!read data
            axi_rready    : out std_logic;    
            --write address
            axi_awready   : in std_logic;
            --write data
            axi_wready    : in std_logic;
            --write response
            axi_bresp     : in std_logic_vector(1 downto 0);                   
            axi_bvalid    : in std_logic;
            --read address
            axi_arready   : in std_logic;
            --read data       
            axi_rdata     : in std_logic_vector(G_AXIL_DATA_WIDTH-1 downto 0);  
            axi_rresp     : in std_logic_vector(1 downto 0);                   
            axi_rvalid    : in std_logic 

		);
		
end entity axi_protocol;

architecture rtl of axi_protocol is 

    constant C_SINGLE_READ            : std_logic_vector(7 downto 0) := x"05";
    constant C_SINGLE_WRITE           : std_logic_vector(7 downto 0) := x"09";

    constant C_NUMB_ADDR_BYTES        : integer := 4;
    constant C_NUMB_LENGTH_BYTES      : integer := 1;
    constant C_NUMB_DATA_BYTES        : integer := 4;
    constant C_NUMB_AXIL_DATA_BYTES   : integer := 4;
    constant C_NUMB_CRC_BYTES         : integer := 4;   
    constant C_MAX_NUMB_BYTES         : integer := 4; -- max number of the above constant for number of bytes 
    constant C_ZERO_PAD               : std_logic_vector(7 downto 0) := (others => '0');
    
    type t_fsm is (idle, address, length, dummy, write_payload, read_payload, crc, write_axil, write_axi, read_axi, read_axil);
    type t_op_fsm is (idle, output, check);
    type t_array is array (0 to 7) of std_logic_vector(31 downto 0);
    type axil_read_fsm is (IDLE, START, CHECK_ADDR_RESP, READ_DATA, DONE);
    type axil_write_fsm is (IDLE, START, CHECK_ADDR_RESP, WRITE_DATA, RESP_READY, CHECK_RESP, DONE);
    signal write_state : axil_write_fsm;
    signal read_state  : axil_read_fsm;

    signal s_current_state : t_fsm;

    signal s_command            : std_logic_vector(7 downto 0);
    signal s_address            : std_logic_vector((C_NUMB_ADDR_BYTES * 8)-1 downto 0);
    signal s_length             : std_logic_vector(7 downto 0);
    signal s_length_axi         : std_logic_vector(7 downto 0);
    signal s_buf_cnt            : unsigned(7 downto 0);
    signal s_byte_pos           : integer range 0 to C_MAX_NUMB_BYTES; 
    signal s_num_bytes          : integer range 0 to C_MAX_NUMB_BYTES; 
    signal s_s_tready           : std_logic;
    signal s_write_buffer       : t_array :=(others=>(others=>'0'));
    signal s_read_buffer        : t_array :=(others=>(others=>'0'));
    signal s_write_buffer_temp  : std_logic_vector(31 downto 0);
    signal s_read_buffer_temp   : std_logic_vector(31 downto 0);

    --axil lite data interface 
    signal s_axil_data          : std_logic_vector(G_AXIL_DATA_WIDTH-1 downto 0);
    signal s_axil_valid         : std_logic;
    signal s_axil_idata         : std_logic_vector(G_AXIL_DATA_WIDTH-1 downto 0);


    --axi mstream 
    signal s_opptr              : unsigned(7 downto 0);
    signal s_start              : std_logic;
    signal s_op_state           : t_op_fsm;
    signal s_op_byte            : integer range 0 to C_MAX_NUMB_BYTES; 
    signal start_read           : std_logic;
    signal start_write          : std_logic;
    signal s_m_axis_tvalid      : std_logic;

begin

    s_axis_tready <= s_s_tready;

FSM : process(clk, reset )
begin 
    if (reset = '0') then 
        start_read  <= '0';
        start_write <= '0';

        s_s_tready  <= '0';
    elsif rising_edge(clk) then
        s_s_tready  <= '1';
        s_start     <= '0';
        start_read  <= '0';
        start_write <= '0';
        case s_current_state is

            when idle => -- to do needs to check the command is valid
                s_buf_cnt           <= (others =>'0');
                if (s_axis_tvalid = '1' and s_s_tready = '1') and 
                    (s_axis_tdata = C_SINGLE_READ  or s_axis_tdata = C_SINGLE_WRITE) then
                        s_s_tready <= '0';
                        s_command <= s_axis_tdata;
                        s_current_state <= address;
                        s_byte_pos <= C_NUMB_ADDR_BYTES;
                end if;

            when address =>
                if s_byte_pos = 0 then
                    s_s_tready <= '0';
                    s_byte_pos <= C_NUMB_LENGTH_BYTES;
                    s_current_state <= length;    
                elsif s_axis_tvalid = '1' and s_s_tready = '1'  then
                    s_address <= s_address(s_address'length-8-1 downto 0) & s_axis_tdata;
                    s_byte_pos <= s_byte_pos - 1;
                    if s_byte_pos = 1 then 
                        s_s_tready <= '0';
                    end if; 
                end if;

            when length => 
                if s_byte_pos = 0 then
                    s_s_tready <= '0';
                    if s_command = C_SINGLE_READ and unsigned(s_length) = 1 then
                        s_current_state <= read_axil; 
                        start_read      <= '1';
                        s_num_bytes     <= C_NUMB_AXIL_DATA_BYTES;
                    elsif s_command = C_SINGLE_WRITE then
                        s_buf_cnt       <= (others =>'0');
                        s_byte_pos      <= C_NUMB_AXIL_DATA_BYTES;
                        s_num_bytes     <= C_NUMB_AXIL_DATA_BYTES;
                        s_current_state <= write_payload;
                    end if;    
                elsif s_axis_tvalid = '1' and s_s_tready = '1'  then
                    s_length            <= s_axis_tdata;
                    s_length_axi        <= std_logic_vector(unsigned(s_axis_tdata)-1);
                    s_byte_pos          <= s_byte_pos - 1;
                    s_s_tready <= '0';
                end if;

            when read_axil =>  
                if s_axil_valid = '1' then 
                    s_start             <= '1';
                    s_read_buffer(0)(G_AXIL_DATA_WIDTH-1 downto 0) <= s_axil_data;
                end if;
                if (read_state = DONE) then
                    s_current_state <= read_payload;
                end if;
            

            when write_payload =>
                if s_buf_cnt = unsigned(s_length) then 
                    s_s_tready <= '0';
                    s_current_state <= write_axil;
                    start_write <= '1';
                else
                    if s_byte_pos = 0 then 
                        s_s_tready <= '0';
                        s_byte_pos <= s_num_bytes;
                        s_write_buffer(to_integer(s_buf_cnt)) <= s_write_buffer_temp;
                        s_buf_cnt <= s_buf_cnt + 1;  
                    elsif (s_axis_tvalid = '1' and s_s_tready = '1')  then
                        s_write_buffer_temp <= s_write_buffer_temp(s_write_buffer_temp'length-8-1 downto 0) & s_axis_tdata;
                        s_byte_pos <= s_byte_pos - 1;  
                        if s_byte_pos = 1 then 
                            s_s_tready <= '0';
                        end if;   
                    end if;
                end if;

            when write_axil =>  
                s_s_tready <= '0';
                s_axil_idata <= s_write_buffer(0);
                if (write_state = DONE) then
                    s_current_state <= idle;
                end if;

            when read_payload =>
                s_current_state <= idle;
            when others => null;
        end case;
    end if;

end process;


m_axis_tvalid <= s_m_axis_tvalid;

process(clk, reset)
begin
    if (reset = '0') then 
        s_m_axis_tvalid      <= '0';
        m_axis_tdata        <= (others =>'0');
        s_opptr             <= (others => '0');
        s_op_byte           <= C_NUMB_AXIL_DATA_BYTES;
    elsif rising_edge(clk) then 
        case s_op_state is  
            when idle => 
                s_m_axis_tvalid <= '0';
                if s_start = '1' then 
                    s_opptr     <= (others => '0');
                    s_read_buffer_temp <= s_read_buffer(0);
                    s_op_byte   <= s_num_bytes;
                    s_op_state  <= output;
                end if;
            when output =>
                if s_opptr = unsigned(s_length) then 
                    s_op_state <= idle;
                    s_m_axis_tvalid <= '0';
                else 
                    s_m_axis_tvalid <= '1';
                    m_axis_tdata <= s_read_buffer_temp(7 downto 0);
                    if s_op_byte = 0 then 
                        s_op_byte   <= s_num_bytes;
                        s_opptr     <= s_opptr + 1;
                        s_m_axis_tvalid <= '0';   
                    elsif m_axis_tready = '1'  then 
                        s_m_axis_tvalid <= '1';                  
                        s_read_buffer_temp <= C_ZERO_PAD & s_read_buffer_temp(s_read_buffer_temp'length-1 downto 8);
                        s_op_byte <= s_op_byte - 1; 
                        s_op_state  <= check;  
                    end if;      
                end if;
            when check =>
                s_m_axis_tvalid <= '0';   
                s_op_state  <= output;
        end case;
    end if;

end process;


process(clk, reset)
begin  

    if (reset = '0') then 
        write_state <= IDLE;

        axi_awaddr  <= (others =>'0');
        axi_awprot  <= (others =>'0');
        axi_awvalid <= '0';
        axi_wdata   <= (others =>'0');
        axi_wstrb   <= (others =>'0');
        axi_wvalid  <= '0';
        axi_bready  <= '0';
    elsif rising_edge(clk) then 
        axi_wstrb   <= (others =>'0');
        case write_state is
            --Send write address
            when IDLE =>
                if start_write = '1' then
                    write_state <= START;
                end if;
            when START =>

                axi_awaddr  <= s_address;
                axi_awprot  <= "010";
                axi_awvalid <= '1';
                axi_wdata   <= s_axil_idata;
                axi_wvalid  <= '1';
                axi_wstrb   <= (others =>'1');
                write_state <= WRITE_DATA;--CHECK_ADDR_RESP;

            --Wait for slave to acknowledge receipt
            when CHECK_ADDR_RESP =>

                if (axi_awready = '1' ) then

                    axi_awaddr  <= (others => '0');
                    axi_awprot  <= (others => '0');
                    axi_awvalid <= '0';

                    write_state <= WRITE_DATA;
                else
                    write_state <= CHECK_ADDR_RESP;
                end if;

            --Send write data
            when WRITE_DATA => 

                if (axi_awready = '1' ) then
                    axi_awaddr  <= (others => '0');
                    axi_awprot  <= (others => '0');
                    axi_awvalid <= '0';
                    axi_wstrb   <= (others =>'0');
                end if;
            
                axi_wdata  <= s_axil_idata;
                axi_wvalid <= '1';
                axi_wstrb   <= (others =>'1');
                if (axi_wready = '1') then     
                    write_state <= RESP_READY;
                else
                    write_state <= WRITE_DATA;
                end if;

            --Set response ready
            when RESP_READY => 
                axi_wstrb   <= (others =>'0'); 
                axi_wvalid <= '0';
                axi_bready <= '1';
                write_state <= CHECK_RESP;

            --Check the response
            when CHECK_RESP =>
                if (axi_bvalid = '1') then
                    axi_bready <= '0';
                    write_state <= DONE;
                end if; 

            --Indicate the transaction has completed
            when DONE =>
                write_state <= IDLE;

            when others =>
                write_state <= START;

        end case;
    end if;
end process;

process(clk, reset)
begin  

    if (reset = '0') then 
        read_state <= IDLE;
        
        axi_araddr  <= (others =>'0');
        axi_arprot  <= (others =>'0');
        axi_arvalid <= '0';
        axi_rready  <= '0';

    elsif rising_edge(clk) then 
case read_state is
    when IDLE =>
        if start_read = '1' then
         read_state <= START;
        end if;
    --Send read address
    when START =>

        axi_araddr  <= s_address;
        axi_arprot  <= "010";
        axi_arvalid <= '1';

        s_axil_valid <= '0';

        read_state <= CHECK_ADDR_RESP;

    --Wait for the slave to acknowledge receipt of the address
    when CHECK_ADDR_RESP =>

        if (axi_arready = '1' ) then

            axi_araddr  <= (others => '0');
            axi_arprot  <= (others => '0');
            axi_arvalid <= '0';

            read_state <= READ_DATA;
        else
            read_state <= CHECK_ADDR_RESP;
        end if;

        s_axil_valid <= '0';

    --Read data from the slave
    when READ_DATA =>

        s_axil_data  <= axi_rdata; 

        if (axi_rvalid = '1') then                   
            s_axil_valid <= '1';
            read_state <= DONE;
        else
            s_axil_valid <= '0';
            read_state <= READ_DATA;
        end if;

        axi_rready <= '1';
                    
    --Indicate the transaction has completed
    when DONE =>

        axi_rready <= '0';
        s_axil_data  <= (others => '0');
        s_axil_valid <= '0';
        read_state <= IDLE;
    
    when others =>
         read_state <= START;

end case;
end if;
end process;

end architecture;

Credits

Adam Taylor
133 projects • 2274 followers
Adam Taylor is an expert in design and development of embedded systems and FPGA’s for several end applications (Space, Defense, Automotive)

Comments