基于 Makefile 的 FPGA 构建系统

文章目录

    • 1. 介绍
    • 2. Makefile 基本使用
        • 2.1 更通用例子
    • 3. Vivado 提供的命令行工具
        • 3.1 TCL 脚本介绍与基本使用
          • 3.1.1 变量与替换
          • 3.1.2 控制结构与过程
        • 3.2 在 vivado 中使用 tcl 脚本
          • 3.2.1 创建并初始化 vivado 工程
          • 3.2.2 对设计文件进行综合
          • 3.2.3 实现与布局布线
          • 3.2.4 生成 bit 文件和 ltx 可调试文件
    • 4. 通过 Makefile 生成 tcl 脚本
        • 4.1 最终目标
        • 4.2 生成 bit 文件的目标
        • 4.3 综合和实现步骤的目标
          • 创建工程的目标
        • 4.4 项目文件夹中的 Makefile
        • 4.5 其它实用性目标
          • 4.5.1 GUI 目标
          • 4.5.2 program 目标
          • 4.5.3 ip_gen 目标
    • 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 文件如下

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.ob.o 文件。

a.ob.o 的输出又依赖 a.cb.c, 这两个文件在文件系统中能够找到,依赖满足。于是执行该目标下的命令规则 gcc -c a.c -o a.ogcc -c b.c -o b.o

输出 a.ob.o 再将其作为依赖执行 main 目标的规则 gcc a.o b.o -o main,最终输出可执行文件 main

2.1 更通用例子

通常可以使用一些 Makefile 内置命令来完成源文件地自动扫描和编译, 例如下面例子将能够自动扫描当前目录和 src 目录中地 .c 文件, 然后将它们编译输出成 a.exe

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 接收一个表达式字符串, 并输出表达式的计算结果

expr 14.1 * 6 * sin(.2)
expr rand()
3.1.1 变量与替换
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 过程, 然后

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 工程的例子

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 文件,其中包含了电路的门级实现。

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芯片,布局布线根据时序约束条件,以及芯片内部各个逻辑单元的布局结构,通过连线资源,将逻辑网表中的硬件原语和底层单元合理地配置到芯片内部的固有硬件结构上。

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 可调试文件

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 文件

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 目标。

$(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 关系以及具体规则的实现。

# 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 文件。

# 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 可以写成如下形式

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 模式。

gui: $(BPREFIX).xpr
	vivado.bat $(VIVADO_OPTS) $(BPREFIX).xpr
4.5.2 program 目标

生成 bit 文件后通常需要将 bit 文件下载到 FPGA 芯片上进行验证,因此 program 目标可以快速地完成该目的。

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。

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 文件内容如下:

###################################################################
# 
# 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

你可能感兴趣的:(fpga开发)