引言
FPGA(Field-Programmable Gate Array,现场可编程门阵列)是一种灵活、可编程的硬件平台,可以在实验、原型验证和产品开发中发挥重要作用。本实验报告旨在介绍并分析在FPGA板上实现的三个关键模块:divide、Display和TopModule,以及对实验进行更深入的细节解释和分析。
实验目的
- 了解FPGA硬件描述语言(Verilog)的基本语法和模块组合。
- 学习如何使用FPGA板实现数字逻辑电路,如分频器和数码管驱动器。
- 掌握FPGA硬件设计流程,包括模块设计、仿真验证和综合实现。
实验步骤
- 模块设计首先,针对实验需求和功能设计,编写了
divide、Display和TopModule这三个模块的Verilog代码。在divide模块中,根据分频系数N设计了相应的计数器逻辑和分频时钟输出逻辑。在Display模块中,利用了7段数码管的编码原理,将计数器的值映射到对应的数码管编码,并驱动LED进行数字显示。在TopModule中,整合了按钮控制、分频器和数码管驱动器,形成完整的数字计数器系统。 - 仿真验证使用Xilinx Vivado等FPGA开发工具进行功能仿真验证。通过构建仿真测试台,输入不同的时钟信号和按钮控制信号,观察仿真波形,验证系统各模块功能的正确性和稳定性。确保分频器、数码管驱动器和控制逻辑符合设计要求。
- 综合实现将Verilog代码综合到目标FPGA板上,生成对应的比特流文件(bitstream)。连接FPGA开发板,加载比特流文件,进行硬件验证和测试。检查时钟分频是否准确,数码管LED显示是否正常,按钮控制是否可靠。
- 性能评估测试不同分频系数N下系统的性能和稳定性。观察分频后的时钟信号波形,分析分频器的分频精度和占空比情况。对数码管LED的显示进行评估,验证数字显示的准确性和稳定性。测试按钮控制功能,评估系统的响应速度和稳定性。
原理框图如下,具有启动、停止、递增、归零四个功能。
实验内容
1. divide 模块
divide 模块是一个用于分频的模块,根据输入的时钟信号和分频系数N,输出相应分频后的时钟信号。该模块包含以下关键部分:
- 计数器和分频时钟的控制逻辑。
- 条件判断以选择输出的分频时钟信号。
- 对时钟信号的上升沿和下降沿进行触发,实现不同的分频功能。
在这个模块中,我们需要对WIDTH和N进行合理的选择,确保分频器能够按照预期的分频比例工作。比如,我们设定WIDTH为24位,这意味着计数器的最大值为2^24-1,即16777215,可以满足大多数的分频需求。而选择合适的N值则需要根据具体的应用场景和系统要求来确定。实现代码如下图所示
module divide (clk, rst_n, clkout);
input clk, rst_n; // Input signals, where clk is connected to FPGA's C1 pin with a frequency of 12MHz
output clkout; // Output signal, can be connected to LED for observing the divided clock
// Parameters in Verilog for constants
parameter WIDTH = 24; // Width of the counter, maximum count value is 2**WIDTH-1
parameter N = 1_200_000; // Division factor, ensure N < 2**WIDTH-1, otherwise counting will overflow
reg [WIDTH-1:0] cnt_p, cnt_n; // cnt_p for the counter triggered on rising edge, cnt_n for the counter triggered on falling edge
reg clk_p, clk_n; // clk_p for the divided clock on rising edge, clk_n for the divided clock on falling edge
// Control of counter triggered on rising edge
always @ (posedge clk or negedge rst_n ) // posedge and negedge indicate rising and falling edges in Verilog
begin
if (!rst_n)
cnt_p <= 0;
else if (cnt_p == (N-1))
cnt_p <= 0;
else
cnt_p <= cnt_p + 1; // Counter keeps counting, resets to 0 when reaching N-1, acting as a modulo-N counter
end
// Output of divided clock triggered on rising edge, duty cycle is not 50% if N is odd; 50% duty cycle for even N
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
clk_p <= 0;
else if (cnt_p < (N >> 1)) // N >> 1 is right shift by one bit, equivalent to division by 2 removing the remainder
clk_p <= 0;
else
clk_p <= 1; // Divided clock has one more positive cycle than negative cycle
end
// Control of counter triggered on falling edge
always @ (negedge clk or negedge rst_n)
begin
if (!rst_n)
cnt_n <= 0;
else if (cnt_n == (N-1))
cnt_n <= 0;
else
cnt_n <= cnt_n + 1;
end
// Output of divided clock triggered on falling edge, offset by half clock cycle compared to clk_p
always @ (negedge clk)
begin
if (!rst_n)
clk_n <= 0;
else if (cnt_n < (N >> 1))
clk_n <= 0;
else
clk_n <= 1; // Divided clock has one more positive cycle than negative cycle
end
// Conditional assignment for clkout
assign clkout = (N == 1) ? clk : (N[0]) ? (clk_p & clk_n) : clk_p;
// Conditional assignment explanation:
// When N=1, directly output clk
// When N is even (lowest bit of N is 0), N[0] = 0, output clk_p
// When N is odd (lowest bit of N is 1), N[0] = 1, output clk_p & clk_n. Positive cycle is more, so it's an AND operation
endmodule
2. Display 模块
Display 模块用于驱动两个7段数码管LED显示计数器的个位和十位数字。该模块的主要功能包括:
- 初始化数码管LED的显示编码。
- 将个位和十位计数器的值映射到对应的数码管编码。
- 输出驱动数码管LED的信号,实现数字显示。
在编写 Display 模块时,需要注意数码管LED的驱动方式和对应的编码方式。常见的数码管编码方式包括共阴极和共阳极,需要根据实际的数码管类型选择合适的编码方式,并确保驱动信号的频率和占空比符合数码管的工作要求。实现代码如下图。
module Display(seg_led_1,seg_led_2,cnt_ge,cnt_shi);
input [3:0] cnt_ge;
input [3:0] cnt_shi;
output [8:0] seg_led_1,seg_led_2;
reg [6:0] seg [9:0];
initial
begin
seg[0] = 7'h3f; // 0
seg[1] = 7'h06; // 1
seg[2] = 7'h5b; // 2
seg[3] = 7'h4f; // 3
seg[4] = 7'h66; // 4
seg[5] = 7'h6d; // 5
seg[6] = 7'h7d; // 6
seg[7] = 7'h07; // 7
seg[8] = 7'h7f; // 8
seg[9] = 7'h6f; // 9
end
assign seg_led_1[8:0] = {2'b01,seg[cnt_shi]};
assign seg_led_2[8:0] = {2'b00,seg[cnt_ge]};
endmodule
3. TopModule 模块
TopModule 模块是整个系统的顶层模块,将 divide、Display 以及其他必要的逻辑组合起来。该模块实现了以下功能:
- 控制启动、停止和增量按钮,并根据按钮状态控制计数器的运行。
- 使用 divide 模块生成1 Hz的时钟信号,用于驱动计数器。
- 将计数器的个位和十位数字传递给 Display 模块,实现数码管LED的数字显示。
在 TopModule 模块中,我们需要合理设计按钮控制逻辑,确保按键操作能够正确地控制计数器的运行。例如,在按下启动按钮时,需要使计数器开始工作;在按下停止按钮时,需要暂停计数器的工作;在按下增量按钮时,需要增加计数器的值。同时,还需要考虑到按键消抖和按键优先级等问题,以提高系统的稳定性和可靠性。由于时间问题,以及适配性问题,没能结合防抖,代码实现如下。
module TopModule(clk,rst,start,stop,increment,seg_led_1,seg_led_2);
input clk,rst,start,stop,increment;
output [8:0] seg_led_1,seg_led_2;
// Internal signals declaration
wire clk1h; // 1Hz clock
reg start_flag; // Start button flag
reg stop_flag; // Stop button flag
reg increment_flag = 0; // Increment button flag
reg [6:0] seg [9:0]; // Array for segment display encoding
reg [3:0] cnt_ge; // Units counter
reg [3:0] cnt_shi; // Tens counter
// Instantiate divide module for generating 1Hz clock
divide #(.WIDTH(32), .N(1200000)) U1 (
.clk(clk),
.rst_n(rst),
.clkout(clk1h)
);
// Instantiate Display module for segment display
Display U2 (
.seg_led_1(seg_led_1),
.seg_led_2(seg_led_2),
.cnt_ge(cnt_ge),
.cnt_shi(cnt_shi)
);
// Change flag when start or stop button is pressed
always @ (negedge start or negedge stop)
if (!rst == 1)
start_flag <= 0;
else begin
if (!start)
start_flag <= 1'd1;
else if (!stop)
start_flag <= 1'd0;
end
// Counter and increment logic based on clock and flags
always @ (posedge clk1h or negedge rst) begin
if (!rst == 1) begin
cnt_ge <= 4'd0;
cnt_shi <= 4'd0;
end
else if (start_flag == 1) begin
if (cnt_shi == 9 && cnt_ge == 9) begin
cnt_shi <= 0;
cnt_ge <= 0;
end
else if (cnt_ge == 9) begin
cnt_ge <= 4'd0;
cnt_shi <= cnt_shi + 1;
end
else
cnt_ge <= cnt_ge + 1;
end
else if (start_flag == 0 && increment) begin
if (increment_flag) begin
if (cnt_shi == 9 && cnt_ge == 9) begin
cnt_shi <= 0;
cnt_ge <= 0;
end
else if (cnt_ge == 9) begin
cnt_ge <= 4'd0;
cnt_shi <= cnt_shi + 1;
end
else
cnt_ge <= cnt_ge + 1;
increment_flag <= 1'd0;
end
end
else if (!increment)
increment_flag <= 1'd1;
end
endmodule
FPGA资源利用情况
- 设备型号和参数:
- FPGA型号:LCMXO2-4000HC
- 封装:CSBGA132
- 速度等级:5
- 设计资源利用情况:
- 寄存器资源利用:44个寄存器,占比约为1%(4635个寄存器中)
- SLICE资源利用:54个SLICE,占比约为2%(2160个SLICE中)
- LUT4资源利用:107个LUT4,占比约为2%(4320个LUT4中)
- PIO资源利用:23个PIO,占比约为9.6%(280个PIO中)
- 时钟资源分配:
- 时钟数量:4个时钟
- 主要时钟资源使用情况:主时钟 "clk_c" 使用了主时钟资源,但是其驱动器 "clk" 位于非专用引脚 "C1",可能会受到通用路由资源的影响,导致延迟或失真。
- 时钟使能资源:使用了2个时钟使能资源
- IO资源利用:
- IO资源利用情况总结了各个IO银行的利用率和VCCIO设置情况,占比在10%-50%之间。
- 总体布局布线和时序分析情况:
- 完成了布局布线(place and route),并且通过了DRC检查,没有错误或警告。
- 完成了时序分析(timing analysis),但存在1097个设置时间(setup)上的时序错误,没有保持时间(hold)上的时序错误。
- UFM(用户可编程存储器)资源利用:
- UFM大小:767页(128*767位)
- UFM利用:用于通用目的的闪存存储器
总体来说,FPGA 设计在资源利用方面相对充裕,但时序分析显示存在较多的设置时间上的时序错误,需要进一步优化和调整设计以确保时序满足要求。同时,IO资源利用情况也处于合理范围内,没有出现过度利用或浪费的情况。具体运用情况如下:
Clock Nets
Number of Clocks: 4
Net : clk_c, loads : 33
Net : U1/clk1h, loads : 9
Net : start_flag_N_31, loads : 2
Net : stop_c, loads : 1
Clock Enable Nets
Number of Clock Enables: 2
Top 2 highest fanout Clock Enables:
Net : clk1h_enable_9, loads : 10
Net : clk1h_enable_4, loads : 1
Highest fanout non-clock nets
Top 10 highest fanout non-clock nets:
Net : U1/n521, loads : 32
Net : cnt_ge_2, loads : 14
Net : cnt_ge_3, loads : 13
Net : cnt_ge_0, loads : 13
Net : cnt_ge_1, loads : 12
Net : cnt_shi_0, loads : 12
Net : cnt_shi_1, loads : 11
Net : cnt_shi_2, loads : 10
Net : clk1h_enable_9, loads : 10
Net : cnt_shi_3, loads : 9
Design Summary:
Number of registers: 44 out of 4635 (1%)
PFU registers: 44 out of 4320 (1%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 54 out of 2160 (3%)
SLICEs as Logic/ROM: 54 out of 2160 (3%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 30 out of 2160 (1%)
Number of LUT4s: 107 out of 4320 (2%)
Number used as logic LUTs: 47
Number used as distributed RAM: 0
Number used as ripple logic: 60
Number used as shift registers: 0
Number of PIO sites used: 23 + 4(JTAG) out of 105 (26%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)
Notes:-
1. Total number of LUT4s = (Number of logic LUT4s) + 2*(Number of distributed RAMs) + 2*(Number of ripple logic)
2. Number of logic LUT4s does not include count of distributed RAM and ripple logic.
Number of clocks: 4
Net start_flag_N_31: 1 loads, 1 rising, 0 falling (Driver: i1_2_lut )
Net clk1h: 5 loads, 5 rising, 0 falling (Driver: U1/clk_p_29 )
Net clk_c: 18 loads, 18 rising, 0 falling (Driver: PIO clk )
Net stop_c: 1 loads, 0 rising, 1 falling (Driver: PIO stop )
Number of Clock Enables: 2
Net clk1h_enable_9: 4 loads, 4 LSLICEs
Net clk1h_enable_4: 1 loads, 1 LSLICEs
Number of LSRs: 4
Net n516: 2 loads, 2 LSLICEs
Net start_flag_N_31: 1 loads, 1 LSLICEs
Net n520: 2 loads, 2 LSLICEs
Net U1/n521: 17 loads, 17 LSLICEs
Number of nets driven by tri-state buffers: 0
Top 10 highest fanout non-clock nets:
Net U1/n521: 17 loads
Net cnt_ge_2: 14 loads
Net cnt_ge_0: 13 loads
Net cnt_ge_3: 13 loads
Net cnt_ge_1: 12 loads
Net cnt_shi_0: 12 loads
Net cnt_shi_1: 11 loads
Net cnt_shi_2: 10 loads
Net cnt_shi_3: 9 loads
Net clk1h_enable_9: 6 loads
Total REAL Time: 0 secs
Peak Memory Usage: 180 MB
UFM Size: 767 Pages (128*767 Bits).
UFM Utilization: General Purpose Flash Memory.
Available General Purpose Flash Memory: 767 Pages (Page 0 to Page 766).
Initialized UFM Pages: 0 Page.
Total REAL Time: 3 secs
Peak Memory Usage: 411 MB
Global Clock Resources:
CLK_PIN : 0 out of 8 (0%)
General PIO: 1 out of 280 (0%)
PLL : 0 out of 2 (0%)
DCM : 0 out of 2 (0%)
DCC : 0 out of 8 (0%)
Global Clocks:
PRIMARY "clk_c" from comp "clk" on PIO site "C1 (PL4A)", clk load = 18
SECONDARY "U1SLICE_41" on site "R12C15B", clk load = 0, ce load = 0, sr load = 17
SECONDARY "clk1h" from Q1 on comp "U1/SLICE_17" on site "R12C15A", clk load = 5, ce load = 0, sr load = 0
PRIMARY : 1 out of 8 (12%)
SECONDARY: 2 out of 8 (25%)
chatgpt的运用
在这个示例中,我们使用了一个简单的计数器来实现分频功能。输入时钟信号 clk 经过分频器后,输出为 clk_out。通过调整参数 N 的值,可以实现不同的分频比例。在仿真或实际应用中,可以根据实际需求调整参数 N 的值和其他参数。
GPT生成代码如下
module Divider (
input clk, // 输入时钟信号
input rst, // 复位信号
output reg clk_out // 输出时钟信号
);
// 参数定义分频比,例如 N=2 表示二分频
parameter N = 2;
reg [N-1:0] count; // 计数器
always @(posedge clk or posedge rst) begin
if (rst) begin
count <= 0; // 复位时清零计数器
clk_out <= 0; // 复位时输出低电平
end
else begin
if (count == N-1) begin
count <= 0; // 计数到 N-1 时清零计数器
clk_out <= ~clk_out; // 输出时钟信号取反
end
else begin
count <= count + 1; // 计数器加一
end
end
end
endmodule
GPT生成代码如下
module Display (
input [3:0] digit_in, // 四位二进制输入信号,控制数码管显示的数字
output [6:0] segment_out // 数码管段选信号输出
);
// 数码管的数字编码,每个数字对应的七段显示编码
reg [6:0] segments [15:0] = {
7'b1000000, // 0
7'b1111001, // 1
7'b0100100, // 2
7'b0110000, // 3
7'b0011001, // 4
7'b0010010, // 5
7'b0000010, // 6
7'b1111000, // 7
7'b0000000, // 8
7'b0011000, // 9
7'b0001000, // A
7'b0000011, // B
7'b1000110, // C
7'b0100001, // D
7'b0000110, // E
7'b0001110 // F
};
// 选择要显示的数字
assign segment_out = segments[digit_in];
endmodule
可以看出GPT能够起到便捷我们进行编程的好处,但是GPT生成的代码也会遇到一些编译不成功的问题,需要我们后续对代码进行改写。
波形仿真分析
module TopModule_tb;
// 信号声明,与被测模块保持一致
reg clk, rst, start, stop, increment;
wire [8:0] seg_led_1, seg_led_2;
// 实例化被测模块
TopModule dut (
.clk(clk),
.rst(rst),
.start(start),
.stop(stop),
.increment(increment),
.seg_led_1(seg_led_1),
.seg_led_2(seg_led_2)
);
// 时钟信号生成
reg clk_tb = 0;
always #5 clk_tb = ~clk_tb;
// 为输入信号赋值
initial begin
clk = 0;
rst = 1;
start = 0;
stop = 0;
increment = 0;
// 模拟复位过程
#10 rst = 0;
#100;
// 启动计数器
start = 1;
#1000;
// 停止计数器
start = 0;
stop = 1;
#100;
// 重新启动计数器
stop = 0;
start = 1;
#1000;
// 停止计数器
start = 0;
stop = 1;
#100;
// 逐次增加计数器
start = 1;
increment = 1;
#100;
increment = 0;
#100;
increment = 1;
#100;
increment = 0;
#100;
increment = 1;
#100;
increment = 0;
#100;
increment = 1;
#100;
increment = 0;
#100;
// 停止计数器
start = 0;
stop = 1;
#100;
// 重新启动计数器
stop = 0;
start = 1;
#1000;
// 停止计数器
start = 0;
stop = 1;
#100;
// 结束仿真
$finish;
end
endmodule
波形仿真遇到了一些没能解决的问题。
实验结果与分析
经过实验验证,我们成功在FPGA板上实现了分频器、数码管驱动器以及控制逻辑的功能。通过按键控制,可以启动、停止和增量计数器的值,并在数码管LED上显示相应的数字。经过综合实现后,在FPGA硬件上运行稳定,达到了预期的效果。实验效果见视频 <iframe src="//player.bilibili.com/player.html?aid=1801759129&bvid=BV1at421V7mv&cid=1472039710&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
在实验过程中,我们还发现了一些问题并进行了相应的优化和调整。例如,针对数码管LED显示时可能出现的闪烁现象,我们优化了驱动信号的频率和占空比,提高了显示效果。仿真部分还没能得到完善,再使用modelsim中遇到了一些软件问题。
实验总结与展望
通过本次实验,我们深入理解了FPGA硬件设计的流程和方法,掌握了Verilog语言的基本语法和模块设计技巧。成功实现了分频器、数码管驱动器和控制逻辑的功能,并进行了全面的性能评估和测试。在未来的学习和工作中,我们将继续探索FPGA技术的应用,进一步优化设计和实现,提升硬件设计水平和创新能力。