文章目录
-
- [1. 介绍](#1. 介绍)
- [2. Makefile 基本使用](#2. Makefile 基本使用)
-
-
- [2.1 更通用例子](#2.1 更通用例子)
-
- [3. Vivado 提供的命令行工具](#3. Vivado 提供的命令行工具)
-
-
- [3.1 TCL 脚本介绍与基本使用](#3.1 TCL 脚本介绍与基本使用)
-
- [3.1.1 变量与替换](#3.1.1 变量与替换)
- [3.1.2 控制结构与过程](#3.1.2 控制结构与过程)
- [3.2 在 vivado 中使用 tcl 脚本](#3.2 在 vivado 中使用 tcl 脚本)
-
- [3.2.1 创建并初始化 vivado 工程](#3.2.1 创建并初始化 vivado 工程)
- [3.2.2 对设计文件进行综合](#3.2.2 对设计文件进行综合)
- [3.2.3 实现与布局布线](#3.2.3 实现与布局布线)
- [3.2.4 生成 bit 文件和 ltx 可调试文件](#3.2.4 生成 bit 文件和 ltx 可调试文件)
-
- [4. 通过 Makefile 生成 tcl 脚本](#4. 通过 Makefile 生成 tcl 脚本)
-
-
- [4.1 最终目标](#4.1 最终目标)
- [4.2 生成 bit 文件的目标](#4.2 生成 bit 文件的目标)
- [4.3 综合和实现步骤的目标](#4.3 综合和实现步骤的目标)
- [4.4 项目文件夹中的 Makefile](#4.4 项目文件夹中的 Makefile)
- [4.5 其它实用性目标](#4.5 其它实用性目标)
-
- [4.5.1 GUI 目标](#4.5.1 GUI 目标)
- [4.5.2 program 目标](#4.5.2 program 目标)
- [4.5.3 ip_gen 目标](#4.5.3 ip_gen 目标)
-
- [5. 总结](#5. 总结)
1. 介绍
构建 FPGA 时通常依赖 FPGA 提供商提供的开发环境和一系列工具,虽然每个厂商提供的集成开发环境不尽相同,但是基本原理都是给定 HDL 设计源文件, 仿真文件,以及约束文件,然后根据这些文件进行仿真或综合约束,最终输出可用于烧录的比特文件 .bit
。
一般来说集成开发环境 (IDE) 都由前端 GUI 编辑器以及后端的代码编译器以及代码分析等各种命令行工具组成,FPGA 的集成开发环境也不例外。对于比较常见的 FPGA 提供商,例如 Altera 和 Xilinx,它们在官方文档中都有提供基于 GUI 工程和非 GUI 工程的开发教程。
GUI 的构建方式具有上手快,可视化的优点,在不太依赖合作和版本管理的项目来说是比较不错的方案。但是一旦项目规模变得庞大,而且需要 git 等版本管理工具进行版本控制时,GUI 的方式便显得不够灵活,而且代码可移植性低。倘若更换不同的 FPGA 厂商,则整个项目需要重新构建。
Makefile 是一种常用的构建工具,它基于一系列的目标和规则,能够利用规则自动化地输出指定地目标。
为了更灵活地构建 FPGA 以及对项目进行版本管理,本文将介绍一种使用 Makefile 来完成 FPGA 构建的系统。并以 Xilinx 提供的 Vivado 集成开发环境为例进行实现。
2. Makefile 基本使用
Makefile 通常需要将目标和规则写在一个名为 Makefile
的文本文件中,然后执行 make
命令,这时 make
命令将解析 Makefile 文件中的规则然后输出目标。
Makefile 的基本格式如下
目标 ... : 依赖 ...
命令1
命令2
. . .
其中规则有多条命令组成,规则将利用依赖项输出目标内容。
例如一个简单的 Makefile 文件如下
sh
main: a.o b.o
gcc a.o b.o -o main
a.o b.o: a.c b.c
gcc -c a.c -o a.o
gcc -c b.c -o b.o
第一个目标将作为 Makefile 的最终目标,因此上面例子的最终目标是输出可执行文件 main, 为此需要依赖 a.o
和 b.o
文件。
而 a.o
和 b.o
的输出又依赖 a.c
和 b.c
, 这两个文件在文件系统中能够找到,依赖满足。于是执行该目标下的命令规则 gcc -c a.c -o a.o
和 gcc -c b.c -o b.o
。
输出 a.o
和 b.o
再将其作为依赖执行 main
目标的规则 gcc a.o b.o -o main
,最终输出可执行文件 main
。
2.1 更通用例子
通常可以使用一些 Makefile 内置命令来完成源文件地自动扫描和编译, 例如下面例子将能够自动扫描当前目录和 src 目录中地 .c
文件, 然后将它们编译输出成 a.exe
sh
SRCS += $(wildcard src/*.c *.c)
OBJS = $(addprefix build/, $(SRCS:%.c=%.o))
CFLAGS = -Iinc
a.exe: $(OBJS)
CC $(CFLAGS) $(OBJS) -o build/a.exe
build/%.o: %.c
@mkdir -p $(dir $@)
CC $(CFLAGS) -c $< -o $@
clean:
rm -fR build
内置命令 wildcard
可用于匹配指定模式的文件名,并返回一个文件名列表。然后我们将其赋值给 SRCS 变量。获取的 SRCS 列表再通过 addprefix
命令为每一个文件名列表项添加路径, 从而得到目标文件列表 OBJS,这是一个以 .o 结尾的文件名列表。
最终目标 a.exe
依赖目标文件列表 OBJS
,而目标 build/%.o: %.c
能够将源文件转化为目标文件列表。其规则为对于每一个源文件, 使用 cc 将其编译生成目标文件。
当从源文件中获取所有的目标文件后, 最终目标 a.exe 的依赖 OBJS 得到满足。于是 make 再回到最终目标,将多个目标文件链接成一个可执行文件。
目标 clean
是一个伪目标。伪目标是 Makefile 中特殊的目标,它们不会实际生成任何文件,但可以用于控制 Makefile 的行为。这里其行为是将构建生成的临时目录 build 清除掉。
3. Vivado 提供的命令行工具
Vivado 集成开发环境在安装后会包含代前端 GUI 界面软件以及后台一系列工具。这些工具包含在 Vivado 安装目录的 bin 目录下, 例如 C:\ProgramFiles\Xilinx\Vivado\2022.2\bin
。将该路径导出到环境变量 PATH 后即可通过命令行调用 Vivado 提供的各种命令行工具。
其中 vivado.bat
是包装了各项功能的批处理文件,只需调用 vivado.bat
即可完成 HDL 设计文件的综合仿真以及约束等各种在 GUI 中所完成的工作。使用 vivado.bat 命令模式将那些在GUI的需要点击输入等操作进行自动化,从而加快了开发和验证的效率。
3.1 TCL 脚本介绍与基本使用
vivado 内部使用 TCL (Tool Command Language) 语言作为其自动化处理的脚本。这是一种通用的脚本语言,具有很高的扩展性,可以将许多于硬件操作的功能实现为指令,并提供了通用的编程能力:支持变量、过程和控制结构。
tcl 命令类似于 shell 中的命令,接收参数然后返回结果。每一条 tcl 命令都会返回一个结果。如果该命令没有有意义的结果,他会返回一个空字符串。
例如下面将使用 expr 命令来实现表达式计算,expr 接收一个表达式字符串, 并输出表达式的计算结果
sh
expr 14.1 * 6 * sin(.2)
expr rand()
3.1.1 变量与替换
sh
set a 1.5
set b [expr $a * 4]
puts $b
通过 set 定义和设置一个变量, 使用 $
来对变量进行引用,tcl 在处理时会将 $
符和它后面的变量名替换该改变量的值, 因此执行 expr $a * 4
时, expr
接收的参数实际是 1.5 * 4
使用 [ ] 可以将一条命令的执行结果作为另一条命令的输入参数,因此上例中变量 b 的值将为 6.0, 因此最终输出为 6.0
3.1.2 控制结构与过程
可以将多个命令组合成一个全新的命令,从而可以直接调用这个新的命令实现某一特定的功能, 例如下面实现一个计算阶乘的 tcl 过程, 然后
sh
proc factorial {val} {
set result 1
while {$val > 0} {
set result [expr $result * $val]
# incr 命令将对整型变量 val 加上 -1
incr val -1
}
return $result
}
puts [factorial 3]
3.2 在 vivado 中使用 tcl 脚本
vivado 提供了丰富的 tcl 命令,每个命令都具有不同的功能和使用方法,当不了解一个命令的使用时,可通过对 command_name -h
来获取命令的使用方法。
3.2.1 创建并初始化 vivado 工程
以下是通过 tcl 脚本自动创建以及初始化一个 vivado 工程的例子
sh
create_project -part xc7z020clg400-1 myproject build
add_files -fileset sources_1 [glob hdl/test.v hdl/top.v]
add_files -fileset constrs_1 pin.xdc timing.xdc
create_project 命令可创建一个空的 vivado 工程, 其中 -part 指定工程中使用的 FPGA 芯片类型, 然后是设置工程名称为 myproject 并将工程输出的中间文件的路径指定为 build
创建好工程后使用 add_files 命令往工程中添加 Verilog 源文件以及管脚约束和时钟约束文件。创建完工程后将在 build 目录下生成 myproject.xpr 工程文件。该文件可以可以直接使用 vivado 打开,并在 GUI 中可以看到 Verilog 源文件和约束文件已经被添加到工程中。
3.2.2 对设计文件进行综合
综合(Synthesis)是将设计输入编译成由与门、或门、非门、RAM、触发器等基本逻辑单元组成的逻辑连接网表的过程。综合的目标是将较高级的抽象描述转化成较低层次的描述,并进行优化,以提高电路的性能和效率。综合的结果是一个 .dcp 文件,其中包含了电路的门级实现。
sh
open_project build/myproject.xpr
reset_run synth_1
launch_runs -jobs 4 synth_1
wait_on_run synth_1
open_project 命令可以打开一个已经存在的工程,对已经存在的工程执行 reset_run synth_1 以及 launch_runs synth_1 加载一个综合实例,-jobs 4 参数可以指定综合处理的线程数。然后通过 wait_on_run 执行综合。这和在 GUI 中直接点综合按钮效果是一致的。
综合后如果没有问题,则会在构建文件夹中生成 myproject.runs/synth_1/myproject.dcp
文件。因此在 Makefile 中可将其作为综合的目标文件。
3.2.3 实现与布局布线
实现 (Implemente) 是将综合生成的逻辑网表配置到具体的FPGA芯片,布局布线根据时序约束条件,以及芯片内部各个逻辑单元的布局结构,通过连线资源,将逻辑网表中的硬件原语和底层单元合理地配置到芯片内部的固有硬件结构上。
sh
open_project build/myproject.xpr
reset_run impl_1
launch_runs -jobs 4 impl_1
wait_on_run impl_1
open_run impl_1
report_utilization -file build/design_bd_wrapper_utilization.rpt
report_utilization -hierarchical -file build/design_bd_wrapper_utilization_hierarchical.rpt
同样的,可以通过打开刚才创建的工程,创建一个实现的实例并运行实现。实现成功会在项目文件夹下生成 myproject.runs/impl_1/myproject_routed.dcp
report_utilization 用于生成 .rpt
报告, 这是一种文本文档格式的报告,可以直接使用记事本打开。
3.2.4 生成 bit 文件和 ltx 可调试文件
实现结果无误后重新打开工程,并使用 write_bitstream 和 write_debug_probes 命令来生成 bit 文件和 ltx 可调试文件
sh
open_project build/myproject.xpr
open_run impl_1
write_bitstream -force build/myproject.runs/impl_1/myproject.bit
write_debug_probes -force build/myproject.runs/impl_1/myproject.ltx
至此,从设计文件到比特文件的整个流程都可以使用 tcl 脚本进行描述和自动化执行。下面将配合 Makefile 来将各个步骤组织起来实现自动化构建。
4. 通过 Makefile 生成 tcl 脚本
了解到 Vivado 使用 tcl 作为自动化脚本后便可通过 Makefile 来自动生成 tcl 脚本,然后传给 vivado.bat 进行自动化处理,从而完成目标的构建。
4.1 最终目标
默认情况下 Makefile 的最终目标是生成可烧录的 bit 文件。因此最终目标将依赖工程目录下的 .bit 文件
sh
BUILD_DIR ?= build
FPGA_TOP ?= fpga
PROJECT ?= $(FPGA_TOP)
VIVADO_OPTS += -nojournal -nolog -tempDir $(BUILD_DIR)
BPREFIX = $(BUILD_DIR)/$(PROJECT)
.ONESHELL:
all: fpga
fpga: $(BPREFIX).bit
clean:
rm -rf $(BUILD_DIR)
4.2 生成 bit 文件的目标
定义一个 $(BPREFIX).bit
目标, 因为最终目标依赖 $(BPREFIX).bit
目标,因此默认情况下会先执行 $(BPREFIX).bit
目标。
sh
$(BPREFIX).bit $(BPREFIX).ltx: $(BPREFIX).runs/impl_1/$(PROJECT)_routed.dcp
@cat << EOF > $(BUILD_DIR)/generate_bit.tcl
open_project $(BPREFIX).xpr
open_run impl_1
write_bitstream -force $(BPREFIX).runs/impl_1/$(PROJECT).bit
write_debug_probes -force $(BPREFIX).runs/impl_1/$(PROJECT).ltx
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/generate_bit.tcl
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).bit $(BUILD_DIR)
if [ -e $(BPREFIX).runs/impl_1/$(PROJECT).ltx ]; then
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).ltx $(BUILD_DIR)
fi
mkdir -p $(BUILD_DIR)/rev
COUNT=100
while [ -e $(BUILD_DIR)/rev/$(PROJECT)_rev$$COUNT.bit ]; do
COUNT=$$((COUNT+1))
done
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).bit $(BUILD_DIR)/rev/$(PROJECT)_rev$$COUNT.bit
if [ -e $(BPREFIX).runs/impl_1/$(PROJECT).ltx ]; then
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).ltx $(BUILD_DIR)/rev/$(PROJECT)_rev$$COUNT.ltx
fi
这里 $(BPREFIX).bit
目标依赖 $(BPREFIX).runs/impl_1/$(PROJECT)_routed.dcp
目标,这是因为生成比特文件需要在实现步骤已经成功生成布局布线的 dcp。
假设依赖得到满足,那么将执行该目标的规则。在这里,我们首先通过 cat << EOF >
语法将后续的内容输出到 $(BUILD_DIR)/generate_bit.tcl
文件, 而里面的内容正是前一小节中用于生成 bit 文件的 tcl 脚本。
当生成 generate_bit.tcl 脚本后通过执行 vivado.bat
的 batch 模式, 来自动执行该脚本。
倘若执行成功, 会在实现的目录 impl_1
中生成 bit 文件,因此我们使用 cp 命令将其拷贝到构建目录的根目录下。同时创建一个 rev 文件夹,用于存放每次构建出来的 bit 文件,以备份不同时刻生成的比特文件。
4.3 综合和实现步骤的目标
生成 bit 文件目标依赖实现的结果,而实现结果的生成依赖综合的输出内容,以下是它们之间的 Makefile 关系以及具体规则的实现。
sh
# synthesis run
$(BPREFIX).runs/synth_1/$(PROJECT).dcp: $(BUILD_DIR)/create_project.tcl $(BUILD_DIR)/update_config.tcl \
$(SYN_FILES) $(INC_FILES) $(XDC_FILES) | $(BPREFIX).xpr
@cat << EOF > $(BUILD_DIR)/run_synth.tcl
open_project $(BPREFIX).xpr
reset_run synth_1
launch_runs -jobs 4 synth_1
wait_on_run synth_1
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/run_synth.tcl
# implementation run
$(BPREFIX).runs/impl_1/$(PROJECT)_routed.dcp: $(BPREFIX).runs/synth_1/$(PROJECT).dcp
@cat << EOF > $(BUILD_DIR)/run_impl.tcl
open_project $(BPREFIX).xpr
reset_run impl_1
launch_runs -jobs 4 impl_1
wait_on_run impl_1
open_run impl_1
report_utilization -file $(BPREFIX)_utilization.rpt
report_utilization -hierarchical -file $(BPREFIX)_utilization_hierarchical.rpt
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/run_impl.tcl
创建工程的目标
创建工程 create_project.tcl
脚本是最基础的目标,其依赖输入的 HDL 设计文件,然后输出 .xpr
文件。
sh
# Vivado project file
$(BUILD_DIR)/create_project.tcl: Makefile $(XCI_FILES) $(IP_TCL_FILES)
mkdir -p $(BUILD_DIR)
rm -rf $(BUILD_DIR)/defines.v
touch $(BUILD_DIR)/defines.v
for x in $(DEFS); do
echo '`define'
$$x >> $(BUILD_DIR)/defines.v
done
@cat << EOF > $@
create_project -force -part $(FPGA_PART) $(PROJECT) $(BUILD_DIR)
EOF
if [ "$(SYN_FILES)" ]; then echo "add_files -fileset sources_1 $(SYN_FILES)" >> $@; fi
if [ "$(XDC_FILES)" ]; then echo "add_files -fileset constrs_1 $(XDC_FILES)" >> $@; fi
if [ "$(SIM_FILES)" ]; then echo "add_files -fileset sim_1 $(SIM_FILES)" >> $@; fi
if [ "$(IP_REPO_PATHS)" ]; then
echo "set_property ip_repo_paths [concat $(IP_REPO_PATHS)] [current_project]" >> $@; fi
for x in $(XCI_FILES); do echo "import_ip $$x" >> $@; done
for x in $(IP_TCL_FILES); do echo "source $$x" >> $@; done
for x in $(CONFIG_TCL_FILES); do echo "source $$x" >> $@; done
for x in $(BD_TCL_FILES); do
echo "source $$x" >> $@;
echo "make_wrapper -files [get_files [glob $(BPREFIX).srcs/sources_1/bd/**/*.bd]] -top" >> $@
echo "add_files -fileset sources_1 [glob $(BPREFIX).gen/sources_1/bd/**/hdl/*wrapper.v]" >> $@
done
if [ "$(FPGA_TOP)" ]; then echo "set_property top $(FPGA_TOP) [get_filesets sources_1]" >> $@; fi
if [ "$(FPGA_SIM_TOP)" ]; then echo "set_property top $(FPGA_SIM_TOP) [get_filesets sim_1]" >> $@; fi
$(BUILD_DIR)/update_config.tcl: $(CONFIG_TCL_FILES) $(SYN_FILES) $(INC_FILES) $(XDC_FILES)
mkdir -p $(BUILD_DIR)
echo "open_project -quiet $(BPREFIX).xpr" > $@
for x in $(CONFIG_TCL_FILES); do echo "source $$x" >> $@; done
$(BPREFIX).xpr: $(BUILD_DIR)/create_project.tcl $(BUILD_DIR)/update_config.tcl
mkdir -p $(BUILD_DIR)
vivado.bat $(VIVADO_OPTS) -mode batch $(foreach x,$?,-source $x)
4.4 项目文件夹中的 Makefile
将以上所有基本和公共的构建目标和规则编写到一个 vivado.mk 文件,然后在项目的顶层 Makefile 文件中通过 include 的方式引入 vivado.mk,从而实现多个项目共有同一套预定义的构建目标和规则,这使得项目中的 Makefile 能够更加简洁清晰。
因此一个项目文件夹中的顶层 Makefile 可以写成如下形式
sh
BUILD_DIR = build
# FPGA settings
FPGA_PART = xc7z020clg400-1
FPGA_TOP = ov5640
# Files for synthesis
SYN_FILES += $(wildcard hdl/*.v)
# IP
XCI_FILES += ip/axis_vid_fifo.xci
include ../common/vivado.mk
该 Makefile 通过预定义一些变量,比如设计文件 SYN_FILES 以及 IP 核文件 XCI_FILES 等构建所需要的文件,然后简单地 include 公共文件 vivado.mk 即可实现自动构建。
4.5 其它实用性目标
除了基本的构建以外,还可以在 vivado.mk 中添加一些实用性的目标。
4.5.1 GUI 目标
有时需要通过 gui 查看仿真以及调试,因此创建一个 gui 目标用于打开 vivado 的 GUI 模式, 这时只需执行 make gui
即可打开 vivado 的 GUI 模式。
sh
gui: $(BPREFIX).xpr
vivado.bat $(VIVADO_OPTS) $(BPREFIX).xpr
4.5.2 program 目标
生成 bit 文件后通常需要将 bit 文件下载到 FPGA 芯片上进行验证,因此 program 目标可以快速地完成该目的。
sh
program: $(BUILD_DIR)/$(FPGA_TOP).bit
@cat << EOF > $(BUILD_DIR)/program.tcl
open_hw_manager
connect_hw_server
open_hw_target
current_hw_device [lindex [get_hw_devices] 1]
refresh_hw_device -update_hw_probes false [current_hw_device]
set_property PROGRAM.FILE {$<} [current_hw_device]
program_hw_devices [current_hw_device]
exit
EOF
@vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/program.tcl
4.5.3 ip_gen 目标
将当前项目导出成一个自定义 IP 核是模块化开发中经常使用的功能,因此这里定义了一个最基本的 ip 核打包目标用于打包当前项目并输出为 XCI 格式的 IP。
sh
ip_gen: $(BPREFIX).xpr
@cat << EOF > $(BUILD_DIR)/ip_gen.tcl
open_project $(BPREFIX).xpr
ipx::package_project -import_files -force -root_dir ../../ip_gen/$(FPGA_TOP)
# 设置 ip 属性和信息
set_property vendor {xilinx.com} [ipx::current_core]
set_property library {user} [ipx::current_core]
set_property taxonomy {{/demo}} [ipx::current_core]
set_property vendor_display_name {shino} [ipx::current_core]
set_property company_url {xilinx.com} [ipx::current_core]
set_property supported_families {
virtex7 Production \
qvirtex7 Production \
kintex7 Production \
kintex7l Production \
qkintex7 Production \
qkintex7l Production \
artix7 Production \
artix7l Production \
aartix7 Production \
qartix7 Production \
zynq Production \
qzynq Production \
azynq Production \
zynquplus Production
} [ipx::current_core]
ipx::save_core [ipx::current_core]
close_project
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/ip_gen.tcl
5. 总结
本文主要利用 Makefile 和 tcl 脚本实现了一个基本的 FPGA 自动构建系统。虽然只针对 vivado 平台进行讨论,但是该方法可以简单地进行扩展到其它任意平台的 FPGA 开发环境。例如对于 Altera 的 Quartus 开发环境,可以按照同样的方法,实现一个 quartus.mk 的公共文件,并在顶层 Makefile 中进行引用。
由于没有将源文件之间的依赖关系绑定到特定厂商的工程文件,因此顶层项目的设计文件将同时支持多个平台的 FPGA, 只需更改顶层 Makefile 中的 include 方式即可生成指定厂商的 bit 文件,这对设计文件的可移植性和可复用性有了很大的提高。
文中完整的 vivado.mk 文件内容如下:
sh
###################################################################
#
# Parameters:
# BUILD_DIR - 构建时的输出路径, 默认=build
# CONFIG - 用户配置 Makefile
# PROJECT - 项目名称, 默认=FPGA_TOP
# FPGA_TOP - 顶层模块名, 默认=fpga
# FPGA_SIM_TOP - 仿真部分的顶层模块
# FPGA_PART - FPGA 设备 (例如 xcvu095-ffva2104-2-e)
# SYN_FILES - 可综合源文件
# SIM_FILES - 仿真文件
# INC_FILES - 要包含的文件
# XDC_FILES - 约束文件
# XCI_FILES - xci 格式的 IP 文件
# IP_TCL_FILES - tcl 格式的 IP 文件
# BD_TCL_FILES - BD 设计文件
# IP_REPO_PATHS - IP 仓库地址
# CONFIG_TCL_FILES - tcl 配置文件
#
# Example:
#
# FPGA_TOP = fpga
# FPGA_PART = xcvu095-ffva2104-2-e
# SYN_FILES = rtl/fpga.v
# XDC_FILES = fpga.xdc
# XCI_FILES = ip/pcspma.xci
# include ../common/vivado.mk
#
###################################################################
# phony targets
.PHONY: fpga vivado clean program config synth impl flash
# prevent make from deleting intermediate files and reports
.PRECIOUS: %.xpr %.bit %.mcs %.prm
.SECONDARY:
CONFIG ?= config.mk
-include $(CONFIG)
BUILD_DIR ?= build
FPGA_TOP ?= fpga
PROJECT ?= $(FPGA_TOP)
VIVADO_OPTS += -nojournal -nolog -tempDir $(BUILD_DIR)
BPREFIX = $(BUILD_DIR)/$(PROJECT)
###################################################################
# Main Targets
#
# all: build everything
# clean: remove output files and project files
###################################################################
.ONESHELL:
all: fpga
fpga: $(BPREFIX).bit
vivado: $(BPREFIX).xpr
vivado.bat $(VIVADO_OPTS) $(BPREFIX).xpr
clean:
rm -rf $(BUILD_DIR)
###################################################################
# Target implementations
###################################################################
# Vivado project file
$(BUILD_DIR)/create_project.tcl: Makefile $(XCI_FILES) $(IP_TCL_FILES)
mkdir -p $(BUILD_DIR)
rm -rf $(BUILD_DIR)/defines.v
touch $(BUILD_DIR)/defines.v
for x in $(DEFS); do
echo '`define'
$$x >> $(BUILD_DIR)/defines.v
done
@cat << EOF > $@
create_project -force -part $(FPGA_PART) $(PROJECT) $(BUILD_DIR)
EOF
if [ "$(SYN_FILES)" ]; then echo "add_files -fileset sources_1 $(SYN_FILES)" >> $@; fi
if [ "$(XDC_FILES)" ]; then echo "add_files -fileset constrs_1 $(XDC_FILES)" >> $@; fi
if [ "$(SIM_FILES)" ]; then echo "add_files -fileset sim_1 $(SIM_FILES)" >> $@; fi
if [ "$(IP_REPO_PATHS)" ]; then
echo "set_property ip_repo_paths [concat $(IP_REPO_PATHS)] [current_project]" >> $@; fi
for x in $(XCI_FILES); do echo "import_ip $$x" >> $@; done
for x in $(IP_TCL_FILES); do echo "source $$x" >> $@; done
for x in $(CONFIG_TCL_FILES); do echo "source $$x" >> $@; done
for x in $(BD_TCL_FILES); do
echo "source $$x" >> $@;
echo "make_wrapper -files [get_files [glob $(BPREFIX).srcs/sources_1/bd/**/*.bd]] -top" >> $@
echo "add_files -fileset sources_1 [glob $(BPREFIX).gen/sources_1/bd/**/hdl/*wrapper.v]" >> $@
done
if [ "$(FPGA_TOP)" ]; then echo "set_property top $(FPGA_TOP) [get_filesets sources_1]" >> $@; fi
if [ "$(FPGA_SIM_TOP)" ]; then echo "set_property top $(FPGA_SIM_TOP) [get_filesets sim_1]" >> $@; fi
$(BUILD_DIR)/update_config.tcl: $(CONFIG_TCL_FILES) $(SYN_FILES) $(INC_FILES) $(XDC_FILES)
mkdir -p $(BUILD_DIR)
echo "open_project -quiet $(BPREFIX).xpr" > $@
for x in $(CONFIG_TCL_FILES); do echo "source $$x" >> $@; done
$(BPREFIX).xpr: $(BUILD_DIR)/create_project.tcl $(BUILD_DIR)/update_config.tcl
mkdir -p $(BUILD_DIR)
vivado.bat $(VIVADO_OPTS) -mode batch $(foreach x,$?,-source $x)
# synthesis run
$(BPREFIX).runs/synth_1/$(PROJECT).dcp: $(BUILD_DIR)/create_project.tcl $(BUILD_DIR)/update_config.tcl \
$(SYN_FILES) $(INC_FILES) $(XDC_FILES) | $(BPREFIX).xpr
@cat << EOF > $(BUILD_DIR)/run_synth.tcl
open_project $(BPREFIX).xpr
reset_run synth_1
launch_runs -jobs 4 synth_1
wait_on_run synth_1
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/run_synth.tcl
# implementation run
$(BPREFIX).runs/impl_1/$(PROJECT)_routed.dcp: $(BPREFIX).runs/synth_1/$(PROJECT).dcp
@cat << EOF > $(BUILD_DIR)/run_impl.tcl
open_project $(BPREFIX).xpr
reset_run impl_1
launch_runs -jobs 4 impl_1
wait_on_run impl_1
open_run impl_1
report_utilization -file $(BPREFIX)_utilization.rpt
report_utilization -hierarchical -file $(BPREFIX)_utilization_hierarchical.rpt
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/run_impl.tcl
# bit file
$(BPREFIX).bit $(BPREFIX).ltx: $(BPREFIX).runs/impl_1/$(PROJECT)_routed.dcp
@cat << EOF > $(BUILD_DIR)/generate_bit.tcl
open_project $(BPREFIX).xpr
open_run impl_1
write_bitstream -force $(BPREFIX).runs/impl_1/$(PROJECT).bit
write_debug_probes -force $(BPREFIX).runs/impl_1/$(PROJECT).ltx
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/generate_bit.tcl
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).bit $(BUILD_DIR)
if [ -e $(BPREFIX).runs/impl_1/$(PROJECT).ltx ]; then
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).ltx $(BUILD_DIR)
fi
mkdir -p $(BUILD_DIR)/rev
COUNT=100
while [ -e $(BUILD_DIR)/rev/$(PROJECT)_rev$$COUNT.bit ]; do
COUNT=$$((COUNT+1))
done
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).bit $(BUILD_DIR)/rev/$(PROJECT)_rev$$COUNT.bit
if [ -e $(BPREFIX).runs/impl_1/$(PROJECT).ltx ]; then
cp -pv $(BPREFIX).runs/impl_1/$(PROJECT).ltx $(BUILD_DIR)/rev/$(PROJECT)_rev$$COUNT.ltx
fi
config: $(BPREFIX).xpr
make $<
synth: $(BPREFIX).runs/synth_1/$(PROJECT).dcp
make $<
impl: $(BPREFIX).runs/synth_1/$(PROJECT).dcp
make $<
program: $(BUILD_DIR)/$(FPGA_TOP).bit
@cat << EOF > $(BUILD_DIR)/program.tcl
open_hw_manager
connect_hw_server
open_hw_target
current_hw_device [lindex [get_hw_devices] 1]
refresh_hw_device -update_hw_probes false [current_hw_device]
set_property PROGRAM.FILE {$<} [current_hw_device]
program_hw_devices [current_hw_device]
exit
EOF
@vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/program.tcl
%.mcs %.prm: %.bit
@cat << EOF > $(BUILD_DIR)/generate_mcs.tcl
write_cfgmem -force -format mcs -size 128 -interface SPIx4 -loadbit {up 0x01002000 $*.bit} -checksum -file $*.mcs
exit
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/generate_mcs.tcl
mkdir -p $(BUILD_DIR)/rev
COUNT=100
while [ -e $(BUILD_DIR)/rev/$*_rev$$COUNT.bit ]; do
COUNT=$$((COUNT+1))
done
COUNT=$$((COUNT-1))
for x in .mcs .prm; do
cp $*$$x $(BUILD_DIR)/rev/$*_rev$$COUNT$$x
echo "Output: $(BUILD_DIR)/rev/$*_rev$$COUNT$$x"
done
ip_gen: $(BPREFIX).xpr
@cat << EOF > $(BUILD_DIR)/ip_gen.tcl
open_project $(BPREFIX).xpr
ipx::package_project -import_files -force -root_dir ../../ip_gen/$(FPGA_TOP)
# 设置 ip 属性和信息
set_property vendor {xilinx.com} [ipx::current_core]
set_property library {user} [ipx::current_core]
set_property taxonomy {{/demo}} [ipx::current_core]
set_property vendor_display_name {shino} [ipx::current_core]
set_property company_url {xilinx.com} [ipx::current_core]
set_property supported_families {
virtex7 Production \
qvirtex7 Production \
kintex7 Production \
kintex7l Production \
qkintex7 Production \
qkintex7l Production \
artix7 Production \
artix7l Production \
aartix7 Production \
qartix7 Production \
zynq Production \
qzynq Production \
azynq Production \
zynquplus Production
} [ipx::current_core]
ipx::save_core [ipx::current_core]
close_project
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source $(BUILD_DIR)/ip_gen.tcl
flash: $(BUILD_DIR)/$(FPGA_TOP).mcs $(BUILD_DIR)/$(FPGA_TOP).prm
@cat << EOF > $(BUILD_DIR)/flash.tcl
open_hw
connect_hw_server
open_hw_target
current_hw_device [lindex [get_hw_devices] 0]
refresh_hw_device -update_hw_probes false [current_hw_device]
create_hw_cfgmem -hw_device [current_hw_device] [lindex [get_cfgmem_parts {mt25qu01g-spi-x1_x2_x4}] 0]
current_hw_cfgmem -hw_device [current_hw_device] [get_property PROGRAM.HW_CFGMEM [current_hw_device]]
set_property PROGRAM.FILES [list \"$(BUILD_DIR)/$(FPGA_TOP).mcs\"] [current_hw_cfgmem]
set_property PROGRAM.PRM_FILES [list \"$(BUILD_DIR)/$(FPGA_TOP).prm\"] [current_hw_cfgmem]
set_property PROGRAM.ERASE 1 [current_hw_cfgmem]
set_property PROGRAM.CFG_PROGRAM 1 [current_hw_cfgmem]
set_property PROGRAM.VERIFY 1 [current_hw_cfgmem]
set_property PROGRAM.CHECKSUM 0 [current_hw_cfgmem]
set_property PROGRAM.ADDRESS_RANGE {use_file} [current_hw_cfgmem]
set_property PROGRAM.UNUSED_PIN_TERMINATION {pull-none} [current_hw_cfgmem]
create_hw_bitstream -hw_device [current_hw_device] [get_property PROGRAM.HW_CFGMEM_BITFILE [current_hw_device]]
program_hw_devices [current_hw_device]
refresh_hw_device [current_hw_device]
program_hw_cfgmem -hw_cfgmem [current_hw_cfgmem]
boot_hw_device [current_hw_device]
exit
EOF
vivado.bat $(VIVADO_OPTS) -mode batch -source flash.tcl