实验平台的顶层项目
实验平台的顶层项目是一个 Vivado 的工程项目。教学实验平台提供的顶层项目匹配了实验平台的硬件环境,可以提供同学们直接使用。顶层项目规定了整个 FPGA 芯片的输入输出,可以直接看到硬件的连线情况。顶层项目也规定了约束,这样同学们自己可以不用写约束文件,直接用顶层项目的约束文件即可。顶层项目还提供了一个仿真的测试环境,包括外围的各个硬件的仿真模块,这样同学们不需要硬件平台也可以进行开发和测试。注意,硬件开发中仿真是非常重要的技能,先在仿真器中跑通能够大大减少调试的时间。
顶层项目文件的位置
Thinpad 的顶层项目文件可以从下面的网址中下载:
git clone https://github.com/thu-cs-lab/thinpad_top.git
注意
此 GitHub 仓库提供的是通用的工程模板,未包含本学期实验的具体内容。
开展本学期实验,请使用 ThinPAD-Cloud 平台的“创建 GitLab 仓库”功能,创建清华 Git 上的仓库,使用仓库及其中的模板开展实验。
Windows 平台上,双击其中的 thinpad_top.xpr
项目文件就可以直接打开了。项目的顶层文件(SystemVerilog 源代码)是:
thinpad_top/thinpad_top.srcs/sources_1/new/thinpad_top.sv
分实验的顶层文件模板
thinpad_top.sv
中提供了一个顶层模块的样例,适合于完成各种实验。但为方便同学上手开始实验,模板内还提供了实验 2~5 的顶层模块,其中包含了各个实验所需的辅助模块(例如 Wishbone MUX)等。在完成这些实验时,请同学们分别使用这些模块。
以实验 2 为例,项目的 thinpad_top/thinpad_top.srcs/sources_1/new/lab2/lab2_top.sv
中提供了 lab2_top
模块,可以在 Vivado 中将其指定为顶层模块。
在每个样例文件中,都包含了一些以 TODO 标记的注释,这些是做实验时需要完成的部分。如果使用 VSCode 编辑器,推荐安装 Todo Tree 插件,可以方便地查看和管理这些注释。在每个 TODO 完成后,请删除注释。
// 内部信号声明
logic trigger;
logic [3:0] count;
// 计数器模块
// TODO: 在 lab2 目录中新建 counter.sv,实现该模块
counter u_counter (
.clk (clk_10M),
.reset (reset_of_clk10M),
.trigger(trigger),
.count (count)
);
// 按键检测模块,在按键上升沿(按下)后输出高电平脉冲
// TODO: 同上,实现 trigger 模块,并例化
// 低位数码管译码器
// TODO: 例化模板中的 SEG7_LUT 模块
项目结构
为了保持项目的结构清晰,针对具体实验所编写的模块文件,请放置在 labX
目录下,命名为 模块名称.sv
。而多个实验中共用的模块,例如实验 2 中编写的按键检测 trigger
模块,可以放置在 common
目录下。对于所有新建的文件,都需要将其添加进 Vivado 项目中,否则 Vivado 将报错找不到模块。
部分模块已经提供了模板,可以无需新建文件。
如何在 Windows 下面打开源代码文件
从 git 库下载下来的顶层项目文件的编码是 UTF-8 的,在 Windows 中文系统下面直接使用 Vivado 打开会出现乱码(Linux 下面和 Windows 英文系统不会)。这是因为 Windows 中文系统下面的默认编码页是 GBK(或者 GB2312,或者 GB-18030,都是兼容的)。
需要用别的编辑器给重新用 GBK 编码给保存一下。可以使用任意一种支持多种编码的编辑器就可以。VS Code 可以达到这个目的。
打开这个文件,使用 Ctrl+Shift+P 打开菜单,输入 encoding,选择 Change File Encoding,就可以完成转码操作。
下面是使用 VSCode 打开 UTF-8 编码的文件,然后转存为 GBK 编码的过程。
使用 Vivado 打开,看到的是乱码。
直接使用 VSCode 打开,看到的文字是正常的。
输入 Ctrl+Shift+P,打开菜单,输入 encoding,点击 Change File Encoding。
选择 Save with Encoding。
选择 GBK 编码(或者 GB18030 编码,它们是兼容的)。
再使用 Vivado 打开的时候就可以看到正常的文字,就不是乱码了。
上述过程完成了 thinpad_top.sv
的文件编码的转换。另外一个需要转码的文件是 srcs/sim_1/new/tb.sv
文件,这个是仿真模块文件,构造了教学实验板的仿真环境。
下面是使用 VSCode 打开一个为 GBK 编码文件的过程。
VSCode 默认是 utf-8 编码的,打开 GBK 编码的文件为乱码。换一种编码方式打开就可以看到正常的文字。
打开文件,发现是乱码。输入 Ctrl+Shift+P,输入 encoding,点击 Change File Encoding。
选择 Reopen with Encoding。
选择使用 Simplified Chinese(GB2312)打开,或者使用 GBK 打开,两者在常用字上是兼容的。
此时就可以看到正常的文字。
代码中的一些相关内容解释
`default_nettype none
这是建议的做法。在 Verilog 中,所有没有被定义的标记 label 都被默认认为是 wire 类型的。但是,这种默认的行为是非常危险的,比如在信号名字上出现拼写错误不会被探测出来。因此,建议在所有的源文件的开始加上这一句,取消默认行为。
下面的代码是 7 段数码管的输出:
module SEG7_LUT (oSEG1, iDIG);
input wire[3:0] iDIG; // 输入数据
output wire[7:0] oSEG1; // 7 段数码管的输出
reg [6:0] oSEG;
always_comb begin
case(iDIG)
4'h1: oSEG = 7'b1110110; // ---t----
4'h2: oSEG = 7'b0100001; // | |
4'h3: oSEG = 7'b0100100; // lt rt
4'h4: oSEG = 7'b0010110; // | |
4'h5: oSEG = 7'b0001100; // ---m----
4'h6: oSEG = 7'b0001000; // | |
4'h7: oSEG = 7'b1100110; // lb rb
4'h8: oSEG = 7'b0000000; // | |
4'h9: oSEG = 7'b0000110; // ---b----
4'ha: oSEG = 7'b0000010;
4'hb: oSEG = 7'b0011000;
4'hc: oSEG = 7'b1001001;
4'hd: oSEG = 7'b0110000;
4'he: oSEG = 7'b0001001;
4'hf: oSEG = 7'b0001011;
4'h0: oSEG = 7'b1000000;
endcase
end
assign oSEG1 = {~oSEG,1'b0}; // 1 点亮,0 不点亮
endmodule
常见错误和警告
编译出现的错误和警告是非常正常的现象,大部分情况下是对 Verilog 不熟悉造成的,和一般编程一样,可以依据不同的情况进行处理。
下面的几个错误是在进行实验的时候出现过的错误和解决办法,同学们还是需要依据具体的情况来解决。
错误:
原因:凡是在 always 中赋值的信号都要声明为 reg,reg 最终也不一定综合成 register。
警告:
对应于这一语句
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_11M0592_IBUF]
警告说明的是:没用到的约束。
可能会出现下面的错误:
ERROR: [DRC NSTD-1] Unspecified I/O Standard: 12 out of 12 logical ports use I/O standard (IOSTANDARD) value 'DEFAULT', instead of a user assigned specific value. This may cause I/O contention or incompatibility with the board power or connectivity affecting performance, signal integrity or in extreme cases cause damage to the device or the components to which it is connected. To correct this violation, specify all I/O standards. This design will fail to generate a bitstream unless all logical ports have a user specified I/O standard value defined. To allow bitstream creation with unspecified I/O standard values (not recommended), use this command: set_property SEVERITY {Warning} [get_drc_checks NSTD-1]. NOTE: When using the Vivado Runs infrastructure (e.g. launch_runs Tcl command), add this command to a .tcl file and add that file as a pre-hook for write_bitstream step for the implementation run. Problem ports: RxD_data[7:0], RxD, RxD_clear, RxD_data_ready, and clk.
ERROR: [DRC UCIO-1] Unconstrained Logical Port: 12 out of 12 logical ports have no user assigned specific location constraint (LOC). This may cause I/O contention or incompatibility with the board power or connectivity affecting performance, signal integrity or in extreme cases cause damage to the device or the components to which it is connected. To correct this violation, specify all pin locations. This design will fail to generate a bitstream unless all logical ports have a user specified site LOC constraint defined. To allow bitstream creation with unspecified pin locations (not recommended), use this command: set_property SEVERITY {Warning} [get_drc_checks UCIO-1]. NOTE: When using the Vivado Runs infrastructure (e.g. launch_runs Tcl command), add this command to a .tcl file and add that file as a pre-hook for write_bitstream step for the implementation run. Problem ports: RxD_data[7:0], RxD, RxD_clear, RxD_data_ready, and clk.
原因是:顶层的信号名称跟约束文件中不一样,所以它认为这个信号没有被约束。这一问题通常是由于 thinpad_top.sv
中出现了语法错误,导致 Vivado 自动选择了其他模块作为顶层。
解决方法:首先解决一切可能的语法错误,随后在 Vivado Hierarchy 窗口中,找到 thinpad_top
模块,在其上右键,选择 Set as Top 将其设置为顶层模块。
[DRC LUTLP-1] Combinatorial Loop Alert: 1 LUT cells form a combinatorial loop. This can create a race condition. Timing analysis may not be accurate. The preferred resolution is to modify the design to remove combinatorial logic loops. If the loop is known and understood, this DRC can be bypassed by acknowledging the condition and setting the following XDC constraint on any one of the nets in the loop: 'set_property ALLOW_COMBINATORIAL_LOOPS TRUE [get_nets <myHier/myNet>]'. One net in the loop is dpy1_OBUF[3]. Please evaluate your design. The cells in the loop are: dpy1_OBUF[7]_inst_i_2.
问题:发现了组合逻辑环路。
解决方法:根据错误信息,寻找设计中对应的组合逻辑环路,在其中插入寄存器或修改逻辑。
锁相环电路
这里最后一部分介绍一下锁相环电路,可以用于生成不同频率的时钟。锁相环电路在前面几个实验中不是必须的(但是建议可以用上,以便对后面的实验带来帮助。)
锁相环电路(PLL: Phase-locked loops)是一种利用反馈(Feedback)控制原理实现的频率及相位的同步技术,其作用是将电路输出的时钟与其外部的参考时钟保持同步。当参考时钟的频率或相位发生改变时,锁相回路会检测到这种变化,并且通过其内部的反馈系统来调节输出频率,直到两者重新同步,这种同步又称为"锁相"(Phase-locked)。 --摘自 wikipedia。
关于锁相环电路的原理可以参考网络上的内容或者其它的教科书,在实验中,只需要使用锁相环电路来生成所需的时钟信号即可。
生成特定的时钟信号的原因是电路都是有延迟的,在实验环境中,存储器 SRAM 的延迟大约为 20ns,正好是 50MHz 的频率。因此如果是直接用 50MHz 的频率访问的 SRAM 话,电信号是不能稳定的,必须要使用分频的方式来接入低一点频率的时钟信号。
在顶层项目文件中有实例的代码,需要仔细阅读以下的代码。
// PLL 分频示例
wire locked, clk_10M, clk_20M;
pll_example clock_gen (
// Clock in ports
.clk_in1(clk_50M), // 外部时钟输入
// Clock out ports
.clk_out1(clk_10M), // 时钟输出 1,频率在 IP 配置界面中设置
.clk_out2(clk_20M), // 时钟输出 2,频率在 IP 配置界面中设置
// Status and control signals
.reset(reset_btn), // PLL 复位输入
.locked(locked) // PLL 锁定指示输出,"1"表示时钟稳定,
// 后级电路复位信号应当由它生成(见下)
);
reg reset_of_clk10M;
// 异步复位,同步释放,将 locked 信号转为后级电路的复位 reset_of_clk10M
always_ff @ (posedge clk_10M or negedge locked) begin
if (~locked) reset_of_clk10M <= 1'b1;
else reset_of_clk10M <= 1'b0;
end
always_ff @ (posedge clk_10M or posedge reset_of_clk10M) begin
if (reset_of_clk10M) begin
// Your Code
end
else begin
// Your Code
end
end
上面的模块进行了分频的演示,输入是 50MHz,输出是两个时钟信号,一个是 10MHz 的信号,一个是 20MHz 的信号。这样在后续的电路中就可以使用这两个时钟信号了。
需要注意的是,锁相环电路的输出中有一个 .locked(locked)
的信号。这个信号说明输出的时钟是否稳定。在后续的电路中,必须要依赖这个信号来使用时钟。上述的代码生成 reset_of_clk10M
中指明了这一点。这样,后续代码中只需要这个信号即可。
always_ff @ (posedge clk_10M or posedge reset_of_clk10M) begin
if (reset_of_clk10M) begin
// Your Code,这一段写初始化的代码,一般即为状态机的初始状态。
end
else begin
// Your Code,这一段写正常的工作代码,驱动状态机的执行。
end
end
下面看一下如何生成新的时钟信号,或者修改时钟频率:
双击 clock_gen
:
进入时钟配置环境,其它不用动,直接进入 output 即可:
可以加一路的时钟信号输出,设置为 5MHz 即可,可以可以看到,该器件增加了一路的输出:clk_out3
:
生成所需的 IP 核:
完成:
下面就可以修改源代码,增加一路时钟输出了。
之后的电路接入对应的信号,就可以依据所需要的频率执行。