实践(一)从BASYS3入门FPGA:流水灯设计与硬件验证全流程
1. BASYS3开发板开箱初体验
第一次拿到BASYS3开发板时,我像个拿到新玩具的孩子一样兴奋。这块由Xilinx推出的FPGA开发板特别适合初学者,板载资源丰富但又不至于复杂到让人望而生畏。板子正面最显眼的就是那8颗排列整齐的LED灯,它们将成为我们第一个实验的主角。
BASYS3采用的是Artix-7系列的FPGA芯片,具体型号是xc7a35tcpg236-1。这个35T的芯片对于入门学习来说绰绰有余,它包含了33280个逻辑单元、1800Kb的块RAM和90个DSP切片。板子上还集成了USB转JTAG编程接口、VGA接口、USB HOST接口等实用外设,不过我们今天只需要关注最简单的LED部分。
开发环境我选择了Vivado 2019.1,这是Xilinx官方提供的集成开发环境,从设计输入到生成比特流文件都能一站式完成。安装过程比较常规,但要注意的是Vivado体积较大,建议预留至少50GB的硬盘空间。安装完成后,第一次启动可能会有点慢,这是正常现象。
2. 流水灯设计方案详解
2.1 功能需求分析
流水灯看似简单,但作为第一个FPGA项目,我们需要把它拆解清楚。基本需求是让8颗LED灯依次点亮,全亮后再依次熄灭,如此循环。每个状态保持1秒,这样人眼才能清晰看到变化过程。
这里有几个关键点需要考虑:
- 时间控制:如何精确产生1秒的时间间隔
- 状态切换:如何实现LED灯的依次点亮和熄灭
- 循环控制:如何让这个过程无限循环
2.2 系统架构设计
我决定采用模块化设计,将系统分为两个主要部分:
- 计时模块:负责产生精确的1Hz时钟信号
- 译码模块:根据当前状态控制LED灯的亮灭模式
这种分模块的设计方法在FPGA开发中很常见,每个模块专注于单一功能,最后通过信号连接起来。这样做不仅逻辑清晰,也便于后续调试和功能扩展。
3. RTL代码实现过程
3.1 计时模块编码
计时模块的核心任务是利用板载的100MHz时钟产生1Hz的信号。100MHz意味着每秒钟有100,000,000个时钟周期,要得到1Hz信号,我们需要对时钟进行分频。
module leds(
input clk, //100Mhz系统时钟
input rst, //复位信号
output reg [2:0] ctrl_data, //控制数据
output led_clk, //1Hz时钟
output led_cs //片选信号
);
reg[27:0] cnt; //计数值
parameter cnt_max = 28'd99_999_999; //计数最大值
parameter cnt_half = 28'd49_999_999; //计数中值
reg led_clk_r; //1Hz方波时钟
reg led_cs_r; //片选信号
// 产生1Hz方波
always @ (posedge clk or posedge rst) begin
if(rst) cnt <= 28'd0;
else if(cnt < cnt_max) cnt <= cnt+1'b1;
else cnt <= 28'd0;
end
always @ (posedge clk or posedge rst) begin
if(rst) led_clk_r <= 1'b0;
else if(cnt < cnt_half) led_clk_r <= 1'b0;
else led_clk_r <= 1'b1;
end
assign led_clk = led_clk_r;
// 在1Hz时钟下计数
always @ (negedge led_clk or posedge rst) begin
if(rst) begin
ctrl_data <= 3'd0;
led_cs_r <= 1'b0;
end
else if(ctrl_data < 3'd7) ctrl_data <= ctrl_data+1'b1;
else begin
ctrl_data <= 3'd0;
led_cs_r <= ~led_cs_r;
end
end
assign led_cs = led_cs_r;
endmodule
这段代码有几个关键点:
- 使用28位计数器cnt来累计时钟周期
- 当计数达到49,999,999时翻转led_clk_r信号,产生占空比50%的方波
- 在1Hz时钟的下降沿触发状态切换
3.2 译码模块实现
译码模块的作用是根据计时模块提供的控制信号,输出对应的LED亮灭模式。这里我借鉴了3-8译码器的思路,但做了些调整以适应我们的需求。
module code(
input clk, //100Mhz系统时钟
input rst, //复位信号
output reg [7:0] leds_out //输出LED信号
);
wire [2:0] ctrl; //控制地址
wire cs; //片选信号
// 实例化计时模块
leds myleds(
.clk(clk),
.rst(rst),
.ctrl_data(ctrl),
.led_cs(cs)
);
// 类3-8译码器设计
always @ (ctrl or cs or rst) begin
if(rst) leds_out = 8'b0000_0000;
else begin
if(!cs) begin // 依次点亮
case(ctrl)
3'b000: leds_out = 8'b0000_0001;
3'b001: leds_out = 8'b0000_0011;
//...其他状态
3'b111: leds_out = 8'b1111_1111;
endcase
end
else begin // 依次熄灭
case(ctrl)
3'b000: leds_out = 8'b1111_1110;
3'b001: leds_out = 8'b1111_1100;
//...其他状态
3'b111: leds_out = 8'b0000_0000;
endcase
end
end
end
endmodule
这个模块通过case语句实现了真值表功能,根据片选信号cs的不同,分别处理点亮和熄灭两个过程。这种组合逻辑设计简单直接,非常适合我们的需求。
4. 仿真验证与调试
4.1 测试平台搭建
在将代码烧录到FPGA之前,仿真是必不可少的步骤。我编写了两个测试模块,分别验证计时模块和整个系统的功能。
module leds_sim;
reg clk;
reg rst;
wire [2:0] ctrl_data;
wire led_clk;
wire led_cs;
leds text_leds(
.clk(clk),
.rst(rst),
.ctrl_data(ctrl_data),
.led_clk(led_clk),
.led_cs(led_cs)
);
initial begin
clk = 0;
rst = 0;
#1000 rst = 1;
#1000 rst = 0;
end
always #5 clk = ~clk; // 100MHz时钟
endmodule
这个测试平台主要验证计时模块能否正确产生1Hz信号。由于仿真时不可能真的运行1亿个周期,在实际项目中我们会调整参数来缩短仿真时间。
4.2 仿真结果分析
通过Vivado的仿真工具,我们可以清晰地看到各个信号的变化情况。计时模块的仿真显示:
- clk是100MHz的系统时钟
- led_clk确实被分频成了1Hz
- ctrl_data在led_clk的每个周期递增
- 当ctrl_data达到7后,led_cs信号会翻转
系统级仿真则验证了LED输出信号的正确性,能够按照预期依次点亮和熄灭。
5. 硬件实现与上板调试
5.1 管脚约束配置
仿真通过后,接下来就是硬件实现的关键步骤——管脚分配。BASYS3的管脚定义可以在官方文档中找到,我们需要将设计中的信号映射到具体的物理管脚上。
set_property PACKAGE_PIN W5 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN T17 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports rst]
set_property PACKAGE_PIN U16 [get_ports {leds_out[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {leds_out[0]}]
//...其他LED管脚类似
这些约束告诉Vivado如何将逻辑信号连接到实际的物理管脚。特别注意电平标准要设置为LVCMOS33,这是BASYS3使用的3.3V电平标准。
5.2 生成比特流与下载
在Vivado中完成综合、实现和生成比特流后,就可以通过USB连接开发板进行下载了。第一次使用时可能需要安装驱动,Vivado通常会提示相关操作。
下载完成后,按下开发板上的复位按钮,就能看到LED灯开始按照预期流动起来。如果效果不理想,可能需要检查:
- 管脚分配是否正确
- 时钟频率设置是否准确
- 复位信号极性是否正确
6. 常见问题与解决经验
在实际操作中,我遇到过几个典型问题,这里分享下解决经验:
-
时序不满足:第一次综合时出现了时序违例警告。解决方法是在Vivado中增加时序约束,明确告诉工具我们的时钟频率要求。
-
LED灯不亮:检查发现是管脚分配错误,将LED输出分配到了错误的管脚。通过仔细核对原理图解决了这个问题。
-
流水速度过快:最初设计时计数器位宽不够,导致流水速度比预期快很多。通过仿真发现并修正了这个错误。
-
复位信号无效:由于BASYS3的按钮是按下为高电平,我的代码中复位逻辑写反了。通过测试平台发现了这个问题。
这个简单的流水灯项目虽然基础,但涵盖了FPGA开发的完整流程:从需求分析、架构设计、代码实现、仿真验证到最终的上板调试。通过这个实践,我对Verilog语言和Vivado工具链都有了更深入的理解。
更多推荐
所有评论(0)