The designs we create in out AMD FPGA and SoC implement complex functions for example motor control, image processing, machine learning or signal processing.
Like any industry to aid the development of the programmable logic application we use standard interfaces to enable reuse and simplify the design. The most popular interface in the FPGA development is the Arm eXtensible Interface (AXI) this provides developers with a full high performance and if desired cache coherent memory mapped bus.
AXI is designed for high speed burst transfers between a manager (initiator of the transaction) and subordinate (responder to the transaction). AXI memory mapped bus which provides the following channels
- Address Write - The address data is to be written, protection mode
- Write Channel - Write Data burst
- Write Response - Write Status following completion of the data bursts
- Address Read - The address to be read, protection mode
- Read Channel - Read Data Bursts
While AXI is often connected between one manager and multiple subordinates, AXI is a point to point protocol. To connect a single manager to multiple subordinates we need to use a AXI interconnect which implements a crossbar switch.
For many applications a cut down version of AXI is used which provides single beat read and write access called AXI Lite.
When your first new to FPGA design working with AXI can be a challenge, and often we use processors to configure the peripherals on the AXI network.
However, the ability to access the AXI network from external to the FPGA over a UART can be very important for board bring up and enabling devices to communicate with each other in the final development.
At Adiuvo we have developed a simple protocol which can be interfaced from either SPI, I2C or UART and will enable read and write access to the AXI network. Our developed IP provides the ability to do burst transfers but for this application we will look at how we can do the AXI Lite transfer in each direction.
This approach is very useful as you can access the AXI network externally very simply and during board bring up determine registers settings etc which can be useful late for software. It can also be used to enable a simple interface from MCU to a small FPGA in a system development.
The ConceptThe concept behind the implementation of this is modular, separating the communications interface from the protocol and AXI conversion. In this way a different communication module can be used depending on the circumstances.
The communications interface in this case a UART will send and receive information using AXI Stream. The protocol block will convert the AXI Stream byte into AXI Lite commands and vice versa.
This protocol block can then be connected to the AXI Interconnect enabling access to all the connected AXI periherals.
To be able to do transfers we need to have a defined protocol which enables a number of bytes to be translated into a AXI read or write command.
- Write Op Code – 1 Byte, Value 0x09
- Read Op Code – 1 Byte, Value 0x05
- Address – 4 Bytes the address of the AXI interaction
- Length – 1 Byte always 1 for AXI Lite implementations
- Payload – Words to be write or received data, 4 Bytes are provided for an AXI Lite read or write
To create this solution we need to create two custom IP blocks the AXIS UART and the Protocol Block
The UARTThe code for the AXIS UART can be see below - this UART has no protocol and requires 2 stop bits.
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;
While the package needed is
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;
The AXIS Protocol
The code to implement the AXI protocol can be seen below - if you were to productionise this like Adiuvo does the AXI state machines can be written into a procedures to simplify the calls.
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;
These modules can then be integrated into a new Vivado project.
The Vivado ProjectFor this Vivado project I am going to target a MicroZed board connected to its IO Carrier Card although this approach can be used with any AMD SoC or FPGA.
We can also add into the project the files above for the UART and Protocol
Once the project is open we need to add in the UART and Protocol blocks to a block design
Also add in the AXI BRAM controller and a BRAM the final diagram should look like below
Generate the output products and we are in a position that we can simulate the design using cocotb
COCOTB SimulationCocotb allows us to simulate the design using Python, if you are not sure how to set up Cocotb see my blog here
With cocotb we need a python stimulus file and a make file which pulls in all the build objects.
The python stimulus file looks like
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import Timer
from cocotbext.uart import UartSource, UartSink
async def reset_dut(reset, duration_ns):
reset.value = 0
await Timer(duration_ns, units="ns")
reset.value = 1
reset._log.debug("Reset complete")
@cocotb.test()
async def run_test(dut):
PERIOD = 10
global clk
cocotb.start_soon(Clock(dut.cll, PERIOD, units="ns").start())
clk = dut.cll
dut.rx.value = 1
await reset_dut(dut.reset, 50)
dut._log.debug("After reset")
await Timer(500*PERIOD, units='ns')
uart_source = UartSource(dut.rx, baud=115200, bits=8,stop_bits=4)
uart_sink = UartSink(dut.tx, baud=115200, bits=8)
dut._log.info("Running test 1")
data_uart = [0x09,0xC0, 0x00, 0x00, 0x00,0x01,0x55,0xaa,0x12,0x34]
await uart_source.write(data_uart)
await Timer(0.5*PERIOD, units='ms')
data_uart = [0x05,0xC0, 0x00, 0x00, 0x00,0x01]#0x55,0xaa,0x12,0x34]
await uart_source.write(data_uart)
await Timer(1500, units='us')
data = await uart_sink.recv()
while the make file looks like - you will need to update the paths an project name to where they are on your system
TOPLEVEL_LANG ?= vhdl
PWD=$(shell pwd)
TOPDIR=$(PWD)/
SIM ?= modelsim
WAVES ?= 1
COCOTB_HDL_TIMEUNIT = 1ps
COCOTB_HDL_TIMEPRECISION = 1ps
SIM_ARGS = -t 1ps
VHDL_SOURCES = $(PWD)/../hack_debug.gen/sources_1/bd/design_1/hdl/design_1_wrapper.vhd
VHDL_SOURCES += $(PWD)/../hack_debug.gen/sources_1/bd/design_1/synth/design_1.vhd
VHDL_SOURCES += $(PWD)/../hack_debug.gen/sources_1/bd/design_1/ip/design_1_axi_bram_ctrl_0_0/synth/design_1_axi_bram_ctrl_0_0.vhd
VHDL_SOURCES += $(PWD)/../hack_debug.gen/sources_1/bd/design_1/ip/design_1_axi_bram_ctrl_0_bram_0/synth/design_1_axi_bram_ctrl_0_bram_0.vhd
VHDL_SOURCES += $(PWD)/../hack_debug.gen/sources_1/bd/design_1/ip/design_1_axi_protocol_0_0/sim/design_1_axi_protocol_0_0.vhd
VHDL_SOURCES += $(PWD)/../hack_debug.gen/sources_1/bd/design_1/ip/design_1_uart_0_0/sim/design_1_uart_0_0.vhd
VHDL_SOURCES += $(PWD)/../../axis_uart/uart_pkg.vhd
VHDL_SOURCES += $(PWD)/../../axis_uart/uart.vhd
VHDL_SOURCES += $(PWD)/../../axis_protocol/protocol.vhd
TOPLEVEL = design_1_wrapper
MODULE = debug_top
include $(shell cocotb-config --makefiles)/Makefile.sim
Once the simulation shows a pass and reports back the informaton written over the UART we can build the application and test it on hardware.
To build it on hardware I inserted the processor core to the block design to enable the PS clock and reset to be used.
The updated block diagram looks like below
We can use any LOGIC LEVEL UART to test with the design however, I have several RPi PICO in the office and they make for great USB to UART convertors and enable me to write the application on the pico itself.
To do this I connect the TX and RX pins on UART 0 on the Pico to the FPGA Rx and TX pins. Remember to connect the grounds also so they have a common reference.
The application was created on the Pico using micro python
import machine
import utime
import ustruct
import sys
from machine import UART, Pin
uart0 = UART(0, baudrate=115200, parity=None, stop=2, tx=Pin(16), rx=Pin(17))
#uart1.write('hello') # write 5 bytes
#pins set to input to reduce loading on the SPI config bus
while True:
#print("hello world")
uart0.write(bytearray([0x09,0xC0,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01]))
utime.sleep(1)
uart0.write(bytearray([0x05,0xC0,0x00,0x00,0x00,0x01]))
data = uart0.read(4) # read up to 5 bytes
print("LED Pattern Received Over SPW",data)
utime.sleep(1)
When running the System ILA I added on to the design for debugging we can clearly see reads and writing being performed by the protocol block to the AXI Block RAM controller
We can see the data being read back in the Thony
We can use a simple interface such as UART / SPI/ I2C to provide access to the AXI network. This can be of great benefit when commissioning a design or to enable a small FPGA to be connected to a small MCU.
Comments