目录
三、HDLbitsFPGA练习记录(combinational logic)
前言:
掌握状态机思想在FPGA开发中的应用。
学习Modelsim仿真与DE2-115开发板验证流程。
理解CPLD与FPGA技术差异及适用场景。
通过HDLbits刷题巩固组合逻辑设计能力。
一、用状态机实现LED流水灯
1.1状态机思想
状态机(Finite State Machine, FSM) 是一种描述系统状态转移的数学模型,广泛应用于时序逻辑设计。在FPGA中,状态机通过明确的状态划分和转移条件,简化复杂控制流程的设计。
①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
核心要素:
现态(Current State):系统当前所处的状态。
事件(Event):触发状态迁移的条件(如用户输入、传感器信号等)。
动作(Action):状态迁移时执行的操作(如开启设备、发送数据等)。
次态(Next State):事件触发后系统将进入的新状态。
分类与模型:
Moore型:输出仅由当前状态决定(如交通信号灯的红绿灯切换)。
Mealy型:输出由当前状态和输入共同决定(如网络协议中的应答机制)。
确定型(DFA):每个状态对同一事件有唯一迁移路径。
非确定型(NFA):同一事件可能触发多个迁移路径,需额外逻辑处理。
1.2状态机思想LED流水灯
1.代码编写(verilog):
状态机通过定义不同的状态和状态之间的转换逻辑,能够精确地控制LED灯的点亮顺序。在代码中,S0
到S7
这8个状态分别对应着8个LED灯依次点亮的状态。通过状态的转换,实现了LED流水灯的效果。
module led_fsm(
input clk, // 50MHz时钟
input rst_n, // 复位信号(低有效)
output reg [7:0] led
);
// 状态定义(8个状态)
localparam [2:0] S0=0, S1=1, S2=2, S3=3, S4=4, S5=5, S6=6, S7=7;
reg [2:0] state;
reg [24:0] cnt;
wire en = (cnt == 25'd24_999_999); // 分频使能
// 分频计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) cnt <= 0;
else if (en) cnt <= 0;
else cnt <= cnt + 1;
end
// 状态机主逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S0;
led <= 8'b00000001; // 初始状态S0点亮LED0
end
else if (en) begin
case(state)
S0: begin led <= 8'b00000010; state <= S1; end //S0→S1(第2个LED亮)
S1: begin led <= 8'b00000100; state <= S2; end //S1→S2(第3个LED亮)
S2: begin led <= 8'b00001000; state <= S3; end //S2→S3(第4个LED亮)
S3: begin led <= 8'b00010000; state <= S4; end //S3→S4(第5个LED亮)
S4: begin led <= 8'b00100000; state <= S5; end //S4→S5(第6个LED亮)
S5: begin led <= 8'b01000000; state <= S6; end //S5→S6(第7个LED亮)
S6: begin led <= 8'b10000000; state <= S7; end //S6→S7(第8个LED亮)
S7: begin led <= 8'b00000001; state <= S0; end //S7→S0(第1个LED亮,循环)
default: state <= S0; // 默认回到初始状态
endcase
end
end
endmodule
1.3 modesim仿真
创建工程文件后,跟着步骤来进行仿真操作(可参考参考文件三仿真步骤比较详细,由于之前的博客写过步骤,我就只保存截图了关键部分)
设置成为顶层文件后编译代码
点击Processing-->Start-->Start test bench template writer,这时我们的文件夹之下已经有了一个example.vt文件
(默认是保存在当前目录simulationmodelsim文件夹下的.vt格式文件。)对刚才生成的文件进行修改(可以直接复制下面代码,注意修改文件名称),然后在编译一次
`timescale 1ns/1ns
module led_fsm;
reg clk;
reg rst_n;
reg data;
wire pos_edge;
wire neg_edge;
wire data_edge;
wire [1:0] D;
example u1(
.clk(clk),
.rst_n(rst_n),
.data(data),
.pos_edge(pos_edge),
.neg_edge(neg_edge),
.data_edge(data_edge),
.D(D)
);
//产生时钟激励
initial clk = 1;
always #10 clk = ~clk;
//输入激励
initial begin
rst_n=0;
data=0;
#100;
rst_n = 1;
#50
data=1;
#201;
data=0;
#201;
data=1;
#101;
data=0;
#200;
$stop;
end
endmodule
现在设置仿真,点击Tools-->Options-->EDA ToolsOptions选项,点击浏览Modelsim安装目录下的Win64或者Win32
4、然后对仿真文件设置点击Assignments-->Settings,再点击下面的Simulation按照如图设置
5、选择上面的Compile test bench点击后面Test Bench进入选择,点击New新建
按照顺序先编辑名字,然后浏览刚才的.vt文件,最后点击Add添加达到如下图效果,再点击OK,这里如果浏览的是.vo文件则后面仿真需要自己设置时间及电平,然后一直点击OK回到代码编辑界面
来到主页点击:自动跳转至Modelsim,选择下面的Wave即可看到波形。
1.4 FPGA烧录实现
配置确认引脚,烧录程序(可参考之前的FPGA文件,有详细记录每一步操作,这里就给出关键操作截图)
烧录效果:
【博客调用】状态机思想实习流水灯
二、CPLD和FPGA芯片
CPLD(Complex Programmable Logic Device,复杂可编程逻辑器件)和FPGA(Field-Programmable Gate Array,现场可编程门阵列)是可编程逻辑器件的两大分支,它们在结构、性能和应用场景上有显著差异。
1. 核心结构与技术原理
特性 | CPLD | FPGA |
---|---|---|
逻辑单元 | 基于**乘积项(Product-Term)**结构,包含与阵列和或阵列,逻辑块较大且固定。 | 基于**查找表(LUT)**结构,每个LUT(如4/6输入)模拟任意组合逻辑,逻辑块细粒度。 |
互连资源 | 全局总线式互连,延迟固定且确定性高。 | 分布式可编程互连,延迟可变但灵活性高。 |
存储技术 | 非易失性(如Flash/EEPROM),配置掉电保留。 | 易失性(SRAM为主),需外部配置芯片(如Flash)存储代码。 |
触发器数量 | 较少,适合组合逻辑为主的设计。 | 大量触发器,支持复杂时序逻辑。 |
2. 性能与容量
指标 | CPLD | FPGA |
---|---|---|
逻辑规模 | 较小(数百至数千逻辑门) | 大(数万至数百万逻辑门) |
运行速度 | 高(固定延迟,适合高速控制) | 中高(依赖布线优化,适合并行计算) |
功耗 | 低(静态功耗为主) | 中高(动态功耗显著,尤其高频场景) |
配置时间 | 毫秒级(非易失性,上电即用) | 秒级(需从外部加载配置) |
3. 适用场景
场景 | CPLD | FPGA |
---|---|---|
简单控制逻辑 | 电源管理、接口转换、总线桥接。 | 不适用(资源浪费)。 |
复杂算法处理 | 不适用(容量不足)。 | 图像处理、通信协议(如5G)、AI推理。 |
确定性时序需求 | 高(如电机控制、工业PLC)。 | 低(时序受布线影响)。 |
原型验证 | 不适用。 | 高(可快速迭代设计) |
4. 选型建议
选择CPLD:
需要非易失性存储,上电即用。
设计简单(如组合逻辑、小型状态机)。
对功耗敏感(电池供电设备)。
选择FPGA:
处理复杂算法(如FFT、卷积运算)。
需要动态重构(如通信协议切换)。
高并行性需求(如数据中心加速)。
三、HDLbitsFPGA练习记录(combinational logic)
练习点击这里:HDLBitshttps://hdlbits.01xz.net/wiki/Main_Page
实验记录:
练习一:7420chip
7400 系列集成电路是一系列每个都包含几个门电路的数字芯片。7420 是一种包含两个 4 输入 NAND 门的芯片。
创建一个具有与 7420 芯片相同功能的模块。该模块有 8 个输入和 2 个输出。
分析:
- 每个 4 输入的 NAND 门的逻辑功能是:当所有输入都为 1 时,输出为 0;否则,输出为 1。
- 每个 4 输入 NAND 门的输入是4个输入信号,输出是 1 个信号。
代码:
module top_module (
input p1a, p1b, p1c, p1d,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y );
assign p1y=~(p1a&p1b&p1c&p1d);
assign p2y=~(p2a&p2b&p2c&p2d);
endmodule
仿真电路图;
练习二:电路描述
电路 B 可以通过以下模拟波形来描述:
代码:
module top_module ( input x, input y, output z );
assign z = ~(x ^ y);
endmodule
仿真图:
练习三、多路选择器1-9
设计一个 16 位宽、9 对 1 的多路复用器。当 sel=0 时选择 a,当 sel=1 时选择 b,以此类推。对于未使用的模式(sel=9 到 15),将所有输出位都设置为 '1'。
代码如下:
module top_module (
input [15:0] a,
input [15:0] b,
input [15:0] c,
input [15:0] d,
input [15:0] e,
input [15:0] f,
input [15:0] g,
input [15:0] h,
input [15:0] i,
input [3:0] sel,
output logic [15:0] out
);
// Case语句只能用在过程性块(always块)中
// 这是一个组合逻辑电路,所以使用组合逻辑的always @(*)块。
always @(*) begin
out = '1; // '1 是一种特殊的字面量语法,表示所有位都为1的数。
// '0、'x 和 'z 也是有效的。
// 给 'out' 赋一个默认值,
case (sel)
4'h0: out = a;
4'h1: out = b;
4'h2: out = c;
4'h3: out = d;
4'h4: out = e;
4'h5: out = f;
4'h6: out = g;
4'h7: out = h;
4'h8: out = i;
endcase
end
endmodule
仿真波形图:
练习四:Full adder
创建一个全加器。全加器接收三个位(包括进位输入)并输出和以及进输出。位
预期的解决方案长度:约 2 行。
代码:
module top_module (
input a, // 输入位a
input b, // 输入位b
input cin, // 进位输入
output sum, // 和输出
output cout // 进位输出
);
assign {cout, sum} = a + b + cin; // 使用加法运算直接计算和与进位
endmodule
代码解释
模块接口:
input a
和input b
:两个加数位。input cin
:进位输入(来自低位的进位)。output sum
:加法结果的和。output cout
:进位输出(用于更高位的加法)。
逻辑实现:
使用
assign
语句实现组合逻辑。a + b + cin
:直接对三个输入位进行加法运算。{cout, sum}
:将加法结果的高位赋值给cout
(进位输出),低位赋值给sum
(和输出)。
练习五、Bcdadd4
您将获得一个名为 bcd_fadd 的 BCD(二进制编码十进制)一位数加法器,它能将两个 BCD 数字以及进位输入相加,并生成和以及进位输出。
module bcd_fadd ( input [3:0] a, input [3:0] b, input cin, output cout, output [3:0] sum );
实例化 4 个 bcd_fadd 模块以创建一个 4 位 BCD 溪流进位加法器。您的加法器应将两个 4 位 BCD 数字(打包成 16 位向量)和一个进位输入相加,以产生一个 4 位和进位输出。
代码:
module top_module (
input [15:0] a, b, // 输入的两个 4 位 BCD 数字,打包成 16 位向量
input cin, // 进位输入
output cout, // 进位输出
output [15:0] sum // 4 位 BCD 和,打包成 16 位向量
);
// 中间进位信号,用于连接各个 BCD 加法器
wire c1, c2, c3;
// 实例化 4 个 bcd_fadd 模块
bcd_fadd fa0 (
.a(a[3:0]), // 第一个 BCD 数字的最低位
.b(b[3:0]), // 第二个 BCD 数字的最低位
.cin(cin), // 进位输入
.cout(c1), // 输出到下一个加法器的进位
.sum(sum[3:0]) // 第一个 BCD 位的和
);
bcd_fadd fa1 (
.a(a[7:4]), // 第一个 BCD 数字的次低位
.b(b[7:4]), // 第二个 BCD 数字的次低位
.cin(c1), // 从上一个加法器传来的进位
.cout(c2), // 输出到下一个加法器的进位
.sum(sum[7:4]) // 第二个 BCD 位的和
);
bcd_fadd fa2 (
.a(a[11:8]), // 第一个 BCD 数字的次高位
.b(b[11:8]), // 第二个 BCD 数字的次高位
.cin(c2), // 从上一个加法器传来的进位
.cout(c3), // 输出到下一个加法器的进位
.sum(sum[11:8]) // 第三个 BCD 位的和
);
bcd_fadd fa3 (
.a(a[15:12]), // 第一个 BCD 数字的最高位
.b(b[15:12]), // 第二个 BCD 数字的最高位
.cin(c3), // 从上一个加法器传来的进位
.cout(cout), // 最终的进位输出
.sum(sum[15:12]) // 第四个 BCD 位的和
);
endmodule
练习结果仿真图:
四、总结
本次实验完成了,收获颇丰,进一步掌握状态机思想,又一次复习了仿真操作,对FPGA的掌握更上一层楼。还学会了使用HDLbits 用来模拟仿真波形图,很是方便。在这个在线网站上也可以自己做题,理解底层电路设计的更多知识,收获很大,这是本人实验时候的一个记录过程,希望与大家共勉!
另外,本人才疏学浅,要是有错误的地方,还望大家海涵,感谢您的阅读!
参考文献:
什么是状态机(Finite-state machine)?-CSDN博客
FPGA学习(四)——状态机重写LED流水灯并仿真-CSDN博客