作为一名FPGA工程师,经常需要在多个FPGA设备之间移植项目,核心的问题是IP的管理和移植,今天通过安装和使用 FuseSoC 在多个 AMD FPGA 之间移植一个简单的项目。从 AMD Spartan™ 7 更改为 AMD Artix™ 7 设备,然后是 AMD Kintex™ UltraSacle™。
FuseSoC 介绍
FuseSoC 是一款IP管理器和一套用于 HDL(硬件描述语言)代码的构建工具。
其主要目的是增加 IP 核心的重用,有助于创建、构建和仿真 SoC的解决方案。
FuseSoC 具有如下功能:
重复使用现有核心
创建编译时或运行时配置
针对多个仿真器运行回归测试
让其他项目轻松使用你的代码
FuseSoC 最新可扩展版本支持使用 GHDL、Icarus Verilog、Isim、ModelSim、Verilator 和 Xsim 进行仿真。还支持使用 Altera Quartus、IceStorm、Xilinx ISE 和 Xilinx Vivado 构建 FPGA 映像。支持新的 EDA 工具需要大约 100 行代码,并且会不断添加新工具。
FuseSoC 已成功用于构建或仿真 Nyuzi、Pulpino、VScale、OpenRISC SoC、picorv32、osvvm 等项目。
安装 FuseSoC
FuseSoC 以 Python 包的形式提供,因此我们可以使用 pip 安装。对于这个项目,将使用 VSCode 作为安装和使用 FuseSoC 的主要方法。
首先要检查是否安装了 Python
python --version
下一步是安装 FuseSoC
pip3 install --upgrade fusesoc
要检查 FuseSoC 是否已正确安装,可以运行命令
fusesoc --version
可以看到类似下面的内容
FuseSoC 结构
FuseSoC 提供包管理和构建系统功能,因此需要了解一些基本概念才能有效地使用它。
FuseSoC 的关键元素是核心,核心就像我们平时熟知的 HDL IP。核心由 FuseSoC 包管理器进行管理,为了能够管理核心,每个核心都有一个名称和附加信息,这些附加信息在核心文件中提供。
为了 FuseSoC 管理 IP 核,核心文件的扩展名为.core
FuseSoC 的一个优点是核心可以具有依赖关系,例如,实现图像直方图和通过 AXI 接口的核心可以依赖于实现 AXI 接口的核心。
核心可以存储在本地或远程服务器上。核心的集合称为核心库,核心库最简单的实现是包含多个核心的目录。
FuseSoC 构建系统时能够解决核心依赖关系,就顶层核心而言。它可以是位于 github 或 bitbucket 上的 git repo 上的远程库。
虽然 FuseSoC 构建系统整理了构建设计所需的所有文件,但 AMD Vivado™ Design Suite 中的实际使用 EDAlize。EDALize 抽象了项目创建过程并执行 AMD Vivado™ Design Suite 完成综合、布局和布线以及生成比特流。
我们可以使用顶层的.core文件来整合几个不同的核心库,并控制顶层入口点和最终 FPGA 设计的目标。
FuseSoC 能够与多个不同的库协同工作,为了向 FuseSoC 提供库的位置,需要使用名为 fusesoc.conf的文件。FuseSoC 将首先在当前工作目录中查找 .conf 文件,如果未找到,它将在主目录 (Linux) 或 Windows %homedirectory% 中查找。
虽然我们可以手动创建此文件,但我们可以使用下面的命令自动创建它。
fusesoc library add /path/to/directory
使用 FuseSoC
上面介绍的比较抽象,我们接下来使用一个实例来介绍FuseSoC的使用。
我们将在该项目中使用的源代码是 UART to AXI 逻辑(文末提供)。
针对以下主板:Digilent Arty S7、Digilent Arty A7、Alinx KU040进行相同的工程设计。
首相,创建一个名为 SRC 的核心库,在该库下添加 HDL 元素的三个源文件。
还展示如何使用 AMD Vivado™ Design Suite IP 集成器设计并使用 FuseSoC 构建它们。将在 IP 集成器中包含一些设计元素。这种方法可以被视为一种混合方法,IP 集成器设计将映射到顶层 VHDL 设计中。
由于不想在 AMD Vivado™ Design Suite 中为不同的构建版本创建几个不同的构建元素,所以将创建一个可由 FuseSoC 运行的 tcl 脚本。
该脚本将实例化 AXI BRAM 控制器和 BRAM 连接到自定义 RTL 模块。
# Start a new project or open an existing one in Vivado
# Open the IP Integrator design tool
create_bd_design "design_1"
# Add an AXI BRAM Controller
set axi_bram_ctrl [create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.1 axi_bram_ctrl_0]
# Configure the AXI BRAM Controller for AXI4-Lite interface
set_property CONFIG.PROTOCOL {AXI4LITE} [get_bd_cells $axi_bram_ctrl]
# Add a Block RAM (BRAM)
set bram [create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 bram_0]
# Connect the BRAM Controller to the BRAM
connect_bd_intf_net -intf_net S_AXI $axi_bram_ctrl/BRAM_PORTA $bram/BRAM_PORTA
# Make AXI interface, clock, and reset external
# Expose the AXI interface to external ports
make_bd_intf_pins_external [get_bd_intf_pins $axi_bram_ctrl/S_AXI]
# Expose the clock to an external port
make_bd_pins_external [get_bd_pins $axi_bram_ctrl/s_axi_aclk]
# Expose the reset to an external port
make_bd_pins_external [get_bd_pins $axi_bram_ctrl/s_axi_aresetn]
# Assign addresses
assign_bd_address
# Save and validate the design
validate_bd_design
save_bd_design
# Generate the HDL wrapper for the design and capture the generated filename
set wrapper_file [make_wrapper -files [get_files design_1.bd] -top]
# Add the generated wrapper file to the project
add_files $wrapper_file
# Update the project hierarchy to include the new wrapper file
update_compile_order -fileset sources_1
该脚本将创建如下所示的框图。
然后,将创建一个顶层 RTL 文件,将 IP 集成器框图与自定义 RTL 模块连接起来完成设计。
协议文件
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;
UART 及 UART 封装
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;
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;
TOP模块
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
entity top_level is
port(
clk : in std_logic;
reset : in std_logic;
rx : in std_logic;
tx : out std_logic
);
-- Declarations
end entity top_level ;
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
library UNISIM;
use UNISIM.VCOMPONENTS.ALL;
use ieee.math_real.all;
architecture struct of top_level is
-- Architecture declarations
-- Internal signal declarations
signal S_AXI_0_arready : STD_LOGIC;
signal S_AXI_0_awready : STD_LOGIC;
signal S_AXI_0_bresp : STD_LOGIC_VECTOR( 1 downto 0 );
signal S_AXI_0_bvalid : STD_LOGIC;
signal S_AXI_0_rdata : STD_LOGIC_VECTOR( 31 downto 0 );
signal S_AXI_0_rresp : STD_LOGIC_VECTOR( 1 downto 0 );
signal S_AXI_0_wready : STD_LOGIC;
signal S_AXI_0_wvalid : STD_LOGIC;
signal axi_araddr : std_logic_vector(31 downto 0);
signal axi_arprot : std_logic_vector(2 downto 0);
signal axi_arvalid : std_logic;
signal axi_awaddr : std_logic_vector(31 downto 0);
signal axi_awprot : std_logic_vector(2 downto 0);
signal axi_awvalid : std_logic;
signal axi_bready : std_logic;
signal axi_rready : std_logic;
signal axi_rvalid : std_logic;
signal axi_wdata : std_logic_vector(31 downto 0);
signal axi_wstrb : std_logic_vector(3 downto 0);
signal m_axis_tdata : std_logic_vector(7 downto 0);
signal m_axis_tready : std_logic;
signal m_axis_tvalid : std_logic;
signal s_axis_tdata : std_logic_vector(7 downto 0);
signal s_axis_tready : std_logic;
signal s_axis_tvalid : std_logic;
-- Component Declarations
component axi_protocol
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 (
axi_arready : in std_logic;
axi_awready : in std_logic;
axi_bresp : in std_logic_vector (1 downto 0);
axi_bvalid : in std_logic;
axi_rdata : in std_logic_vector (31 downto 0);
axi_rresp : in std_logic_vector (1 downto 0);
axi_rvalid : in std_logic;
axi_wready : in std_logic;
clk : in std_ulogic;
m_axis_tready : in std_logic;
reset : in std_ulogic;
s_axis_tdata : in std_logic_vector (7 downto 0);
s_axis_tvalid : in std_logic;
axi_araddr : out std_logic_vector (31 downto 0);
axi_arprot : out std_logic_vector (2 downto 0);
axi_arvalid : out std_logic;
axi_awaddr : out std_logic_vector (31 downto 0);
axi_awprot : out std_logic_vector (2 downto 0);
axi_awvalid : out std_logic;
axi_bready : out std_logic;
axi_rready : out std_logic;
axi_wdata : out std_logic_vector (31 downto 0);
axi_wstrb : out std_logic_vector (3 downto 0);
axi_wvalid : out std_logic;
m_axis_tdata : out std_logic_vector (7 downto 0);
m_axis_tvalid : out std_logic;
s_axis_tready : out std_logic
);
end component axi_protocol;
component design_1_wrapper
port (
S_AXI_0_araddr : in STD_LOGIC_VECTOR ( 11 downto 0 );
S_AXI_0_arprot : in STD_LOGIC_VECTOR ( 2 downto 0 );
S_AXI_0_arvalid : in STD_LOGIC;
S_AXI_0_awaddr : in STD_LOGIC_VECTOR ( 11 downto 0 );
S_AXI_0_awprot : in STD_LOGIC_VECTOR ( 2 downto 0 );
S_AXI_0_awvalid : in STD_LOGIC;
S_AXI_0_bready : in STD_LOGIC;
S_AXI_0_rready : in STD_LOGIC;
S_AXI_0_wdata : in STD_LOGIC_VECTOR ( 31 downto 0 );
S_AXI_0_wstrb : in STD_LOGIC_VECTOR ( 3 downto 0 );
S_AXI_0_wvalid : in STD_LOGIC;
s_axi_aclk_0 : in STD_LOGIC;
s_axi_aresetn_0 : in STD_LOGIC;
S_AXI_0_arready : out STD_LOGIC;
S_AXI_0_awready : out STD_LOGIC;
S_AXI_0_bresp : out STD_LOGIC_VECTOR ( 1 downto 0 );
S_AXI_0_bvalid : out STD_LOGIC;
S_AXI_0_rdata : out STD_LOGIC_VECTOR ( 31 downto 0 );
S_AXI_0_rresp : out STD_LOGIC_VECTOR ( 1 downto 0 );
S_AXI_0_rvalid : out STD_LOGIC;
S_AXI_0_wready : out STD_LOGIC
);
end component design_1_wrapper;
component uart
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 (
clk : in std_logic;
m_axis_tready : in std_logic;
reset : in std_logic;
rx : in std_logic;
s_axis_tdata : in std_logic_vector (7 downto 0);
s_axis_tvalid : in std_logic;
m_axis_tdata : out std_logic_vector (7 downto 0);
m_axis_tvalid : out std_logic;
s_axis_tready : out std_logic;
tx : out std_logic
);
end component uart;
-- Optional embedded configurations
-- pragma synthesis_off
for all : axi_protocol use entity src.axi_protocol;
for all : design_1_wrapper use entity src.design_1_wrapper;
for all : uart use entity src.uart;
-- pragma synthesis_on
begin
-- Instance port mappings.
U_0 : axi_protocol
generic map (
G_AXIL_DATA_WIDTH => 32, --Width of AXI Lite data bus
G_AXI_ADDR_WIDTH => 32, --Width of AXI Lite Address Bu
G_AXI_ID_WIDTH => 1, --Width of AXI ID Bus
G_AXI_AWUSER_WIDTH => 1 --Width of AXI AW User bus
)
port map (
clk => clk,
reset => reset,
m_axis_tready => m_axis_tready,
m_axis_tdata => m_axis_tdata,
m_axis_tvalid => m_axis_tvalid,
s_axis_tready => s_axis_tready,
s_axis_tdata => s_axis_tdata,
s_axis_tvalid => s_axis_tvalid,
axi_awaddr => axi_awaddr,
axi_awprot => axi_awprot,
axi_awvalid => axi_awvalid,
axi_wdata => axi_wdata,
axi_wstrb => axi_wstrb,
axi_wvalid => S_AXI_0_wvalid,
axi_bready => axi_bready,
axi_araddr => axi_araddr,
axi_arprot => axi_arprot,
axi_arvalid => axi_arvalid,
axi_rready => axi_rready,
axi_awready => S_AXI_0_wready,
axi_wready => S_AXI_0_awready,
axi_bresp => S_AXI_0_bresp,
axi_bvalid => S_AXI_0_bvalid,
axi_arready => S_AXI_0_arready,
axi_rdata => S_AXI_0_rdata,
axi_rresp => S_AXI_0_rresp,
axi_rvalid => axi_rvalid
);
U_1 : design_1_wrapper
port map (
S_AXI_0_araddr => axi_araddr(11 downto 0),
S_AXI_0_arprot => axi_arprot,
S_AXI_0_arready => S_AXI_0_arready,
S_AXI_0_arvalid => axi_arvalid,
S_AXI_0_awaddr => axi_awaddr(11 downto 0),
S_AXI_0_awprot => axi_awprot,
S_AXI_0_awready => S_AXI_0_awready,
S_AXI_0_awvalid => axi_awvalid,
S_AXI_0_bready => axi_bready,
S_AXI_0_bresp => S_AXI_0_bresp,
S_AXI_0_bvalid => S_AXI_0_bvalid,
S_AXI_0_rdata => S_AXI_0_rdata,
S_AXI_0_rready => axi_rready,
S_AXI_0_rresp => S_AXI_0_rresp,
S_AXI_0_rvalid => axi_rvalid,
S_AXI_0_wdata => axi_wdata,
S_AXI_0_wready => S_AXI_0_wready,
S_AXI_0_wstrb => axi_wstrb,
S_AXI_0_wvalid => S_AXI_0_wvalid,
s_axi_aclk_0 => clk,
s_axi_aresetn_0 => reset
);
U_2 : uart
generic map (
reset_level => '0', -- reset level which causes a reset
clk_freq => 100_000_000, -- oscillator frequency
baud_rate => 115200 -- baud rate
)
port map (
clk => clk,
reset => reset,
rx => rx,
tx => tx,
m_axis_tready => s_axis_tready,
m_axis_tdata => s_axis_tdata,
m_axis_tvalid => s_axis_tvalid,
s_axis_tready => m_axis_tready,
s_axis_tdata => m_axis_tdata,
s_axis_tvalid => m_axis_tvalid
);
end architecture struct;
创建 XDC
将要进行的三个工程之间的唯一区别在于约束文件。需要为每个目标板创建一个约束。
AMD Spartan™ 7
set_property PACKAGE_PIN R2 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_property PACKAGE_PIN L17 [get_ports reset]
set_property PACKAGE_PIN L18 [get_ports rx]
set_property PACKAGE_PIN M14 [get_ports tx]
# set I/O standard
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property IOSTANDARD LVCMOS33 [get_ports rx]
set_property IOSTANDARD LVCMOS33 [get_ports tx]
AMD Artix™ 7
set_property PACKAGE_PIN E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_property PACKAGE_PIN G13 [get_ports reset]
set_property PACKAGE_PIN B11 [get_ports rx]
set_property PACKAGE_PIN A11 [get_ports tx]
# set I/O standard
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property IOSTANDARD LVCMOS33 [get_ports rx]
set_property IOSTANDARD LVCMOS33 [get_ports tx]
AMD Kintex™ UltraSacle™
set_property PACKAGE_PIN AF9 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_property PACKAGE_PIN AE8 [get_ports reset]
set_property PACKAGE_PIN AE10 [get_ports rx]
set_property PACKAGE_PIN AD10 [get_ports tx]
# set I/O standard
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property IOSTANDARD LVCMOS33 [get_ports rx]
set_property IOSTANDARD LVCMOS33 [get_ports tx]
创建 FuseSoC 核心
创建 RTL 和XDC后,下一步是创建.core 文件和.conf 文件。
首先要做的是创建.core 文件,它将被分成几个部分,第一部分是定义 CAPI 版本和核心库,提供其名称和描述
CAPI=2:
name: adiuvo::hackster:0.1
description: Implementation for Hackster Project
下一步是创建文件集,这些文件集被分成几个不同的组。将其中第一个组命名为核心组,这些文件是所有工程中通用的。
对于每个文件,我们还定义了库和文件类型,在本例中为 vhdl。
下一步是定义创建 IP 集成器设计的 tcl 脚本。由于三个目标板之间的配置没有差异。此文件在所有实现中也是通用的。
如果我们正在创建需要特定电路板配置的 Zynq 或 Zynq MPSoC 设计,我们将需要为定义 PS 配置的每个电路板提供文件的变体。
下一个文件集是 IO 约束,每个所需的目标板都有一个文件集。
filesets:
core:
files:
- src/protocol.vhd : {logical_name: work}
- src/uart_pkg.vhd : {logical_name: work}
- src/uart.vhd : {logical_name: work}
- src/top_level.vhd : {logical_name: work}
file_type: vhdlSource
vivado_files_tcl:
files:
- src/build_ip.tcl: {file_type: tclSource}
artix_io:
files:
- constraints/artix7.xdc : {file_type : xdc}
kintex_io:
files:
- constraints/kintexus.xdc : {file_type : xdc}
spartan_io:
files:
- constraints/spartan.xdc : {file_type : xdc}
最后一步是定义目标,在这里定义一个包含核心文件集的默认目标。然后再定义三个目标,每个目标板一个。对于每个目标,将工具定义为 AMD Vivado™ Design Suite,并附加该特定目标所需的文件集。
在这种情况下,它是 IO 文件集和 tcl 脚本,用于演示如果每个目标不同,如何使用 TCL 脚本。
对于每个目标,还需要在 AMD Vivado™ Design Suite 中定义顶层模块,当然还有目标设备。
targets:
default: &default
filesets : [core]
artix7:
<<: *default
default_tool: vivado
filesets_append : [vivado_files_tcl, artix_io]
toplevel : top_level
tools:
vivado:
part : XC7A35TI-CSG324-1L
spartan7:
<<: *default
default_tool: vivado
filesets_append : [vivado_files_tcl, spartan_io]
toplevel : top_level
tools:
vivado:
part : xc7s50-csga324-1
kintexus:
<<: *default
default_tool: vivado
filesets_append : [vivado_files_tcl, kintex_io]
toplevel : top_level
tools:
vivado:
part : xcku040-ffva1156-2-i
.core 文件完成后,可以仔细检查是否能够看到刚刚定义的库。
下一步是定义 fusesoc.conf 文件,定义核心的位置
[library.hackster]
location = C:/hdl_projects/hackster_fusesoc
sync-uri = C:/hdl_projects/hackster_fusesoc/
sync-type = local
auto-sync = false
因为在这个例子中我们只有一个名为 Hackster 的核心库。
我们可以使用命令来检查
fusesoc core list
可以得到以下输出
FuseSoC 结果
创建源代码后,可以通过运行命令来构建这三个工程
AMD Artix™ 7
fusesoc --verbose run --target=artix7 --no-export hackster
AMD Kintex™ UltraSacle™
fusesoc --verbose run --target=kintexus--no-export hackster
AMD Spartan™ 7
fusesoc --verbose run --target=spartan7--no-export hackster
随着项目的构建,将看到实施过程的记录滚动过去。
一旦完成后,将看到一条消息,显示比特流生成已完成。
代码
FuseSoC
https://fusesoc.net/
https://github.com/fusesoc/fusesoc-cores
参考工程
https://github.com/ATaylorCEngFIET/hackster_fusesoc
总结
该项目概述了如何使用 FuseSoC 编写 FPGA 实现脚本,能够轻松简单地定位新的 FPGA 设备。由于 FuseSoC 是一个包管理器和构建系统工具,能够轻松进行IP管理和不同设别之间工程管理。同时该项目中构建了很多IP可以使用。
这里还有一点,就是使用 FuseSoC 可以进行快速验证,因为它还支持一系列仿真工具。
END
往期精选
FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群
加群主微信,备注姓名+公司/学校+岗位/专业进群
FPGA技术江湖QQ交流群
备注姓名+公司/学校+岗位/专业进群