一、状态机思想
1.概念
状态机(Finite State Machine, FSM)是计算机科学和工程领域中的一种抽象模型,用于描述系统在不同状态之间的转换逻辑。其核心思想是将复杂的行为拆解为有限的状态,并通过事件触发状态间的转移。
2.状态机的分类
(1)Moore型状态机
输出仅由当前状态决定。
例如:交通灯的颜色(状态)直接决定输出。
(2)Mealy型状态机
输出由当前状态和输入事件共同决定。
例如:自动售货机根据投币金额(事件)和当前状态计算找零。
二、状态机思想实现LED流水灯
1.用状态机思想重写LED流水灯的FPGA代码
实现功能与我之前文章(FPGA实现LED流水灯(开发板为DE2-115)-CSDN博客)中实现的流水灯效果相同。
代码如下:
module led_state_machine(
input clk, // 50MHz时钟(PIN_Y2)
input rst_n, // 复位信号(PIN_M23,低有效)
input key1, // 启动按键(PIN_M21)
input key2, // 停止按键(PIN_N21)
output reg [5:0] led // 高电平有效LED(PIN_E21~G20)
);
// 系统参数定义
parameter CLK_FREQ = 50_000_000; // 50MHz时钟频率
parameter DEBOUNCE_CYCLES = 1_000_000; // 20ms消抖周期
// 状态机状态定义
localparam
S0 = 3'd0, // LED0亮
S1 = 3'd1, // LED1亮
S2 = 3'd2, // LED2亮
S3 = 3'd3, // LED3亮
S4 = 3'd4, // LED4亮
S5 = 3'd5; // LED5亮
reg [2:0] current_state, next_state;
reg [25:0] counter;
reg run_enable;
// 按键同步与消抖逻辑
reg [1:0] key1_sync, key2_sync;
reg key1_stable, key2_stable;
reg [19:0] key1_cnt, key2_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key1_sync <= 2'b11;
key2_sync <= 2'b11;
key1_stable <= 1'b1;
key2_stable <= 1'b1;
key1_cnt <= 0;
key2_cnt <= 0;
end else begin
// 按键同步链
key1_sync <= {key1_sync[0], key1};
key2_sync <= {key2_sync[0], key2};
// KEY1消抖逻辑
if (key1_sync[1] != key1_stable) begin
key1_cnt <= (key1_cnt == DEBOUNCE_CYCLES) ? 0 : key1_cnt + 1;
if (key1_cnt == DEBOUNCE_CYCLES)
key1_stable <= key1_sync[1];
end else begin
key1_cnt <= 0;
end
// KEY2消抖逻辑
if (key2_sync[1] != key2_stable) begin
key2_cnt <= (key2_cnt == DEBOUNCE_CYCLES) ? 0 : key2_cnt + 1;
if (key2_cnt == DEBOUNCE_CYCLES)
key2_stable <= key2_sync[1];
end else begin
key2_cnt <= 0;
end
end
end
// 运行控制逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
run_enable <= 1'b0;
end else begin
case ({key2_stable, key1_stable})
2'b10: run_enable <= 1'b1; // KEY1按下启动
2'b01: run_enable <= 1'b0; // KEY2按下停止
default: ; // 保持状态
endcase
end
end
// 状态转移控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= S0;
counter <= 0;
end else begin
// 1秒定时器
if (run_enable) begin
counter <= (counter == CLK_FREQ-1) ? 0 : counter + 1;
// 状态转移条件
if (counter == CLK_FREQ-1)
current_state <= next_state;
end
end
end
// 状态转移逻辑
always @(*) begin
case (current_state)
S0: next_state = S1;
S1: next_state = S2;
S2: next_state = S3;
S3: next_state = S4;
S4: next_state = S5;
S5: next_state = S0;
default: next_state = S0;
endcase
end
// 输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led <= 6'b000001;
end else begin
case (current_state)
S0: led <= 6'b000001;
S1: led <= 6'b000010;
S2: led <= 6'b000100;
S3: led <= 6'b001000;
S4: led <= 6'b010000;
S5: led <= 6'b100000;
default: led <= 6'b000001;
endcase
end
end
endmodule
具体过程看之前的文章 ,此处不再过多赘述。
在DE2-115板子上的效果如下:
状态机思想LED流水灯
2.写出仿真测试代码并用Modelsim进行仿真分析
仿真测试代码
`timescale 1ns/1ps
module tb_led_state_machine();
reg clk;
reg rst_n;
reg key1;
reg key2;
wire [5:0] led;
// 实例化被测模块,调整参数以加速仿真
led_state_machine #(
.CLK_FREQ(10), // 原CLK_FREQ=50_000_000改为10
.DEBOUNCE_CYCLES(10) // 原DEBOUNCE_CYCLES=1_000_000改为10
) uut (
.clk(clk),
.rst_n(rst_n),
.key1(key1),
.key2(key2),
.led(led)
);
// 生成50MHz时钟(周期20ns)
always #10 clk = ~clk;
initial begin
// 初始化信号
clk = 0;
rst_n = 0; // 复位有效
key1 = 1; // 按键未按下(高电平)
key2 = 1;
// 释放复位信号
#100;
rst_n = 1;
#20; // 等待稳定
// 验证复位后初始状态
if (led !== 6'b000001)
$display("错误:复位后初始状态应为S0,LED=000001,实际值为%b", led);
else
$display("测试1:复位成功,LED=000001");
// 测试启动按键(key1)
#20;
key1 = 0; // 按下key1
#200; // 等待消抖完成(200ns)
key1 = 1; // 释放key1
// 验证状态转换(每个状态持续200ns)
#200; // S1
if (led === 6'b000010)
$display("测试2:成功转换到S1,LED=000010");
else
$display("错误:应为S1,LED=%b", led);
#200; // S2
if (led === 6'b000100)
$display("测试3:成功转换到S2,LED=000100");
else
$display("错误:应为S2,LED=%b", led);
#200; // S3
if (led === 6'b001000)
$display("测试4:成功转换到S3,LED=001000");
else
$display("错误:应为S3,LED=%b", led);
#200; // S4
if (led === 6'b010000)
$display("测试5:成功转换到S4,LED=010000");
else
$display("错误:应为S4,LED=%b", led);
#200; // S5
if (led === 6'b100000)
$display("测试6:成功转换到S5,LED=100000");
else
$display("错误:应为S5,LED=%b", led);
#200; // S0
if (led === 6'b000001)
$display("测试7:成功返回S0,LED=000001");
else
$display("错误:应为S0,LED=%b", led);
// 测试停止按键(key2)
key2 = 0; // 按下key2
#200;
key2 = 1;
#400; // 等待两个周期,状态应保持不变
if (led === 6'b000001)
$display("测试8:成功停止,保持S0");
else
$display("错误:停止后状态异常,LED=%b", led);
// 再次启动
key1 = 0;
#200;
key1 = 1;
#200; // 应进入S1
if (led === 6'b000010)
$display("测试9:重新启动成功,进入S1");
else
$display("错误:重启失败,LED=%b", led);
$display("所有测试完成");
$finish;
end
endmodule
三、CPLD和FPGA芯片的主要技术区别以及适用场合(借助deepseek)
1.CPLD和FPGA芯片对比
特性 | CPLD | FPGA |
---|---|---|
核心架构 | 基于乘积项(Product-Term)结构,逻辑单元通过全局互连连接 | 基于查找表(LUT)结构,逻辑单元通过细粒度互连网络连接 |
逻辑资源规模 | 较小(通常为数千至数万门) | 较大(数万门至数百万门) |
存储技术 | 基于非易失性存储器(EEPROM/Flash) | 基于易失性存储器(SRAM),需外挂配置芯片 |
时序确定性 | 固定延迟(信号路径可预测) | 可变延迟(依赖布线路径) |
功耗特性 | 静态功耗低,适合低功耗场景 | 静态功耗较高,动态功耗依赖逻辑复杂度 |
启动时间 | 瞬时启动(配置信息内置) | 需要从外部加载配置(毫秒级延迟) |
灵活性 | 适合固定逻辑和简单状态机 | 支持复杂算法、并行计算和动态重构 |
2. CPLD和FPGA芯片适用场合
(1)CPLD适用场景
低复杂度控制逻辑
工业控制系统的接口管理(如PLC信号转换)
通信协议转换(UART转SPI/I2C)
简单的状态机实现(如电梯控制逻辑)
确定性时序要求
实时控制系统(电机驱动时序控制)
高速信号译码(如键盘扫描电路)
低功耗与高可靠性
便携式设备的电源管理
汽车电子中的传感器接口电路
(2)FPGA适用场景
高复杂度数据处理
通信系统(5G基带处理、光纤通信编解码)
图像处理(实时视频压缩、医学影像增强)
人工智能加速(CNN推理、边缘计算)
动态可重构需求
软件定义无线电(SDR)的硬件重配置
数据中心网络加速(动态负载均衡)
高性能计算
金融高频交易算法加速
科学仿真(流体力学、分子动力学)
四、hdlbits网站中组合逻辑(combinational logic)题
4.1 2选1多路复用器(MUX)
1位宽 2选1多路复用器(MUX)是数字电路中最基础的逻辑单元之一,其核心功能是 根据选择信号(sel)从两个输入(a和b)中选择一个输出。它的作用可以类比为一个“数据开关”
问题描述:
解决方案:
module top_module(
input a,
input b,
input sel,
output out
);
assign out = sel ? b : a; // 三元运算符实现
endmodule
说明: sel ?b:a;是条件运算符,等价于 if - else 的逻辑,这是最简洁的组合逻辑实现方式。
用于测试用例的时序图:Sel 在 a 和 b 之间进行选择
4.2 256:1 多路复用器
是2对1多路复用器的升级版
问题描述:
代码 :
module top_module (
input [255:0] in,
input [7:0] sel,
output out
);
assign out = in[sel]; // 直接使用 sel 作为索引
endmodule
4.3 半加器设计
真值表:
a | b | sum | cout |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
问题描述:
代码:
module top_module (
input a, b,
output sum, cout
);
assign sum = a ^ b; // 异或:本位和
assign cout = a & b; // 与:进位
endmodule
4.4 全加器设计
全加器将两个1位二进制数和一个进位输入相加,产生和(sum)和进位输出(carry out)。
问题描述:
代码:
module top_module(
input a, b, cin,
output cout, sum );
// 全加器实现
assign sum = a ^ b ^ cin; // 和的计算
assign cout = (a & b) | (a & cin) | (b & cin); // 进位输出的计算
endmodule
结果:
4.5 4位BCD加法器
这个加法器可以处理两个4位BCD数字(每个数字0-9,共16位输入)和一个进位输入,产生4位BCD和和进位输出。
问题描述:
代码:
module top_module(
input [15:0] a, b,
input cin,
output cout,
output [15:0] sum );
// 定义中间进位信号
wire c1, c2, c3;
// 实例化4个bcd_fadd模块,形成波纹进位链
bcd_fadd add0 (
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(c1),
.sum(sum[3:0])
);
bcd_fadd add1 (
.a(a[7:4]),
.b(b[7:4]),
.cin(c1),
.cout(c2),
.sum(sum[7:4])
);
bcd_fadd add2 (
.a(a[11:8]),
.b(b[11:8]),
.cin(c2),
.cout(c3),
.sum(sum[11:8])
);
bcd_fadd add3 (
.a(a[15:12]),
.b(b[15:12]),
.cin(c3),
.cout(cout),
.sum(sum[15:12])
);
endmodule
说明(工作原理):
·最低位BCD加法器(add0)接收cin
作为进位输入
·每个加法器的进位输出连接到下一个高位加法器的进位输入
·最高位BCD加法器(add3)的进位输出作为整个模块的cout
·每个加法器产生的4位和组合成最终的16位sum
输出
测试用例的时序图:
4.6 100位二进制加法器
使用波纹进位(Ripple Carry)结构,该加法器将两个100位二进制数和一个进位输入相加,产生100位和和一个进位输出。
问题描述:
代码:
module top_module(
input [99:0] a, b,
input cin,
output cout,
output [99:0] sum );
// 定义中间进位信号
wire [100:0] carry;
// 初始进位设为输入进位
assign carry[0] = cin;
// 生成100个全加器实例
genvar i;
generate
for (i = 0; i < 100; i = i + 1) begin : adder_chain
// 每个全加器实例
full_adder fa (
.a(a[i]),
.b(b[i]),
.cin(carry[i]),
.cout(carry[i+1]),
.sum(sum[i])
);
end
endgenerate
// 最终进位输出
assign cout = carry[100];
endmodule
// 全加器模块定义
module full_adder(
input a, b, cin,
output cout, sum
);
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (a & cin) | (b & cin);
endmodule