FPGA入门学习Day0——状态机相关内容解析HDLbits练习

发布于:2025-04-10 ⋅ 阅读:(56) ⋅ 点赞:(0)

目录

一、状态机简单介绍

(一)状态机的核心要素

(二)状态机的类型

(三)状态机的设计步骤

(四)状态机的应用要点

二、实现LED流水灯及其仿真

(一)流水灯代码

(二)modesim仿真

(三)结果展示

1、modesim仿真结果

2、硬件结果

三、 CPLD芯片介绍及与FPGA芯片进行对比

(一) CPLD芯片简介

(二)CPLD和FPGA芯片主要技术区别

1. 结构与逻辑容量

2. 配置技术与易失性

3. 时序特性与时钟管理

4. 功耗与成本

5. 适用场景

四、HDLbitsFPGA组合逻辑题目训练

(一)创建一个半加器

(二)创建一个全加器

(三)创建一个或门运算

 (四)创建一个4位BCD码加法器

(五)创建一个2对1多路复用器 

(六)创建一个2对1总线多路复用器

五、总结 


一、状态机简单介绍

(一)状态机的核心要素

  1. 状态(State)
    系统在任意时刻只能处于其中一个预设的状态。例如,交通灯控制器可能包含“红灯”“绿灯”“黄灯”三种状态。每个状态代表系统的一种特定行为模式。

  2. 转移条件(Transition Condition)
    状态之间的切换由事件或条件触发。例如,当倒计时结束时,交通灯从“绿灯”转移到“黄灯”。转移条件可以是外部输入(如按键信号)或内部逻辑(如计数器溢出)。

  3. 动作(Action)
    在进入、离开或保持某一状态时执行的操作。例如,进入“绿灯”状态时启动倒计时,离开时关闭当前灯。

(二)状态机的类型

1、Moore型状态机

        输出仅依赖于当前状态,与输入无关。例如,交通灯的亮灯逻辑完全由当前状态决定,与外部输入(如车辆检测信号)无关。其特点是输出稳定,但灵活性较低。

特点

  • 输出仅取决于当前状态
  • 时序逻辑输出,稳定性更好
  • 典型应用:模式识别、定时控制

2、Mealy型状态机
        输出由当前状态和输入共同决定。例如,自动售货机在投币后根据金额和当前状态决定是否出货或找零。这种类型能更灵活地响应输入,但输出可能因输入变化而产生瞬时波动。

特点

  • 输出由当前状态和输入共同决定
  • 组合逻辑输出,响应更快
  • 典型应用:通信协议、实时控制

(三)状态机的设计步骤

1、需求分析
        明确系统的所有可能状态、触发条件和预期动作。例如,流水灯系统需要定义四个状态(每个LED亮起),并确定切换的时间间隔。

2、绘制状态转移图
        用图形化工具(如UML状态图)表示状态、转移条件和动作。例如,用箭头连接S0→S1→S2→S3→S0,标注转移条件为计时器溢出。

3、状态编码
为每个状态分配唯一的二进制编码。例如,S0=00、S1=01、S2=10、S3=11。编码需考虑资源优化和可扩展性。

4、代码实现
使用硬件描述语言(如Verilog)描述状态转移和输出逻辑。常见的实现方式分为以下三种:

  • 一段式:将所有逻辑(状态转移、输出)放在单个时序块中。代码紧凑但可维护性差,适合简单逻辑。
  • 二段式:用时序逻辑处理状态转移,组合逻辑处理输出和条件判断。可读性高,但输出可能存在毛刺。
  • 三段式:独立时序块处理状态转移、组合逻辑判断条件、另一时序块生成输出。资源消耗稍高,但输出稳定且易于维护。

5、验证与调试
通过仿真覆盖所有状态路径,确保逻辑正确;硬件验证时需检查实际时序和物理信号。

(四)状态机的应用要点

  1. 复杂度管理
    状态机通过模块化设计,将复杂逻辑分解为独立状态,降低系统整体复杂度。例如,通信协议解析器可划分为“空闲”“接收头”“接收数据”“校验”等状态。

  2. 可读性与维护性
    状态转移图直观展示逻辑流程,便于团队协作和后期修改。相比之下,嵌套的 if-else 语句在扩展时易引入错误。

  3. 硬件实现优化
    明确的状态编码和分离的组合/时序逻辑,有助于综合工具生成高效电路。避免冗余逻辑(如未使用的状态)可节省FPGA资源。

  4. 抗干扰设计
    添加“默认状态”处理未定义状态,防止系统锁死;关键路径添加时序约束,确保信号稳定。

二、实现LED流水灯及其仿真

(一)流水灯代码

module LS_LED(
    input          clk,
    input          rst_n,
    input          pause_sw,
    output reg [5:0] led
);

// 仿真时使用较小的计数值,实际部署时改为50_000_000
`ifdef SIMULATION
    parameter T = 50;       // 仿真时缩短计数周期
`else
    parameter T = 50_000_000; // 实际硬件运行时1秒定时
`endif

// 使用独热码(one-hot)编码状态,提高可读性
typedef enum logic [5:0] {
    STATE_LED0 = 6'b000001,
    STATE_LED1 = 6'b000010,
    STATE_LED2 = 6'b000100,
    STATE_LED3 = 6'b001000,
    STATE_LED4 = 6'b010000,
    STATE_LED5 = 6'b100000
} state_t;

state_t cstate, nstate;  // 使用枚举类型定义状态
reg [25:0] cnt;          // 26位计数器

// 三段式状态机实现

// 第一段:状态寄存器更新
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cstate <= STATE_LED0;
        cnt <= 0;
    end
    else begin
        if (!pause_sw) begin
            if (cnt == T - 1) begin
                cnt <= 0;
                cstate <= nstate;
            end
            else begin
                cnt <= cnt + 1;
            end
        end
        // 暂停时保持当前状态和计数值
    end
end

// 第二段:次态逻辑
always @(*) begin
    case (cstate)
        STATE_LED0: nstate = (cnt == T - 1) ? STATE_LED1 : STATE_LED0;
        STATE_LED1: nstate = (cnt == T - 1) ? STATE_LED2 : STATE_LED1;
        STATE_LED2: nstate = (cnt == T - 1) ? STATE_LED3 : STATE_LED2;
        STATE_LED3: nstate = (cnt == T - 1) ? STATE_LED4 : STATE_LED3;
        STATE_LED4: nstate = (cnt == T - 1) ? STATE_LED5 : STATE_LED4;
        STATE_LED5: nstate = (cnt == T - 1) ? STATE_LED0 : STATE_LED5;
        default:    nstate = STATE_LED0;
    endcase
end

// 第三段:输出逻辑
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        led <= STATE_LED0;
    end
    else begin
        led <= cstate;  // 状态直接映射到LED输出
    end
end

endmodule

测试代码:

`timescale 1ns / 1ps

module LS_LED_tb;

    // 输入信号
    reg clk;
    reg rst_n;
    reg pause_sw;

    // 输出信号
    wire [5:0] led;

    // 生成50MHz时钟
    initial begin
        clk = 0;
        forever #10 clk = ~clk; // 50MHz时钟周期20ns
    end

    // 实例化被测模块
    LS_LED uut(
        .clk(clk),
        .rst_n(rst_n),
        .pause_sw(pause_sw),
        .led(led)
    );

    // 测试流程
    initial begin
        // 初始化
        rst_n = 0;
        pause_sw = 1; // 初始暂停
        #100;
        
        // 测试1: 复位释放
        rst_n = 1;
        #100;
        
        // 测试2: 正常流水灯效果
        pause_sw = 0;
        #5000; // 观察多个状态转换
        
        // 测试3: 暂停功能
        pause_sw = 1;
        #1000;
        
        // 测试4: 恢复运行
        pause_sw = 0;
        #3000;
        
        // 测试5: 复位功能
        rst_n = 0;
        #50;
        rst_n = 1;
        #100;
        
        $display("Simulation complete");
        $finish;
    end

    // 波形记录
    initial begin
        $dumpfile("ls_led.vcd");
        $dumpvars(0, LS_LED_tb);
    end

endmodule

(二)modesim仿真

  • 生成.vt文件

        点击Processing→Start→Start test bench template writer,便生成了对应的.vt文件。

  • 修改.vt文件内容为测试代码

        在quartus界面中打开,随后将测试代码写入其中并保存

  • 然后对仿真文件设置点击Assignments→Settings,再点击下面的Simulation按照如图设置

  • 然后浏览刚才的.vt文件,最后点击Add添加达到如下图效果,再点击OK即可。

  • 随后回到初始界面点击下图按钮进行仿真得到流水灯的仿真结果

(三)结果展示

1、modesim仿真结果

2、硬件结果

FPGA流水灯0.0

三、 CPLD芯片介绍及与FPGA芯片进行对比

(一) CPLD芯片简介

        CPLD(复杂可编程逻辑器件)是基于EEPROM/Flash的非易失性半定制数字电路,集成可编程逻辑块、互连矩阵和I/O单元,具备确定时序、低功耗、上电即用特性。较FPGA规模小(数百至万门级)但延迟固定且无需外置配置芯片,专用于接口协议转换(如UART/SPI)、状态机控制及信号处理,是嵌入式系统胶合逻辑核心,开发通过HDL设计、仿真至JTAG烧录完成,代表型号包括Xilinx CoolRunner和Altera MAX系列。

(二)CPLD和FPGA芯片主要技术区别

1. 结构与逻辑容量

  • CPLD
    基于乘积项(与或阵列)结构,内部逻辑单元通过固定互连资源连接,逻辑容量较小(通常为千门级)。其核心模块包括可编程逻辑模块(PLM)和寄存器阵列(PRA),适合实现简单的组合逻辑和时序逻辑,如状态机、接口控制等。

  • FPGA
    采用查找表(LUT)和可编程互连结构,逻辑单元(CLB)与片上存储(Block RAM)、DSP模块等高度灵活组合,逻辑容量可达百万门级。FPGA的资源动态分配能力使其能够实现复杂算法(如FFT、卷积运算)和高并行任务。

2. 配置技术与易失性

  • CPLD
    使用非易失存储器(如EEPROM或Flash)存储配置信息,上电后自动加载,无需外部配置芯片。配置速度快,但无法实时修改设计。

  • FPGA
    依赖SRAM存储配置数据,断电后信息丢失,需外接配置芯片(如Flash)。支持动态重配置(部分型号),允许运行时更新逻辑功能,灵活性更高。

3. 时序特性与时钟管理

  • CPLD
    时序延迟固定,时钟管理简单,适用于对确定性延时要求高的场景(如实时控制)。

  • FPGA
    时序路径依赖布局布线结果,需通过约束文件优化关键路径。提供锁相环(PLL)、时钟分频器等复杂时钟管理模块,适合高频、高精度时序设计(如高速通信协议)。

4. 功耗与成本

  • CPLD
    静态功耗低,整体功耗较小,适合电池供电或低功耗场景。成本较低,适合中小规模设计。

  • FPGA
    逻辑资源丰富导致静态功耗较高,动态功耗随逻辑复杂度增加而显著上升。成本高于CPLD,但性能与灵活性优势明显。

5. 适用场景

  • CPLD
    控制密集型应用:电源管理、接口转换、简单协议实现(如UART、SPI)。
    特点:上电即运行、低延迟、设计周期短。

  • FPGA
    计算密集型应用:数字信号处理(DSP)、图像处理、高速通信(如PCIe、以太网)。
    特点:并行计算能力强、支持复杂算法、可重构性高。

四、HDLbitsFPGA组合逻辑题目训练

(一)创建一个半加器

        半加器实现两个二进制数相加,输出和与进位,无进位输入。

题目介绍:

答案代码:

module top_module( 
    input a, b,
    output cout, sum );

endmodule

关键点解析:

1、功能:实现两个1位二进制数(ab)的加法运算。

2、逻辑规则

  • 和(sum):由a^b(异或)计算,结果为两数相加的本位值。
  • 进位(cout):由a&b(与)计算,当两数均为1时进位为1。

系统生成仿真图:

(二)创建一个全加器

        全加器将三个一位二进制数相加,输出和及进位。

题目介绍:

答案代码:

module top_module(
    input a, b, cin,
    output cout, sum );

    assign sum = a ^ b ^ cin;         // 全加器求和逻辑
    assign cout = (a & b) | (a & cin) | (b & cin); // 进位生成逻辑
endmodule

关键点解析

1、功能:实现三个1位二进制数(a、b和进位Cin)的加法运算。

2、逻辑规则

  • 和(sum):由a ^ b ^ Cin 计算,结果为三数相加的本位值(奇偶校验逻辑)。
  • 进位(cout):若任意两个输入为1(a & b、a & Cin或b & Cin),则产生进位。

系统生成仿真图:

(三)创建一个或门运算

        或门电路实现逻辑或操作,当任一输入为高电平时输出高电平,全低则输出低电平。

题目介绍:

答案代码:

module top_module (
    input in1,
    input in2,
    output out);

    assign out = in1 & in2;  // 实现与门逻辑,输出为输入信号的逻辑与
endmodule

关键点解析

1、功能:实现两个1位二进制输入(in1和in2)的逻辑与操作。

2、逻辑规则

  • 输出 out 仅在 in1 和 in2 同时为 1 时输出 1,否则输出 0

系统生成仿真图:

 (四)创建一个4位BCD码加法器

        四位BCD码加法器用于两个十进制数的相加,通过两个4位二进制加法器实现。若相加结果超过9或产生进位,则加6校正并进位,确保结果符合BCD编码规则,适用于精确十进制运算。

题目介绍:

答案代码:

module top_module (
    input [15:0] a, b,
    input cin,
    output cout,
    output [15:0] sum );

    wire c1, c2, c3; // 中间进位信号
    
    // 实例化四个BCD加法器,级联进位
    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

关键点解析

1、输入与输出

  • a[15:0] 和  b[15:0]:16位BCD输入,每4位代表一个 十进制数字(如 a[3:0] 表示个位,a[7:4]表示十位,依此类推)。
  • sum[15:0]:16位BCD输出,格式与输入相同,每个4位段为对应十进制位的和。

2、级联结构

  • 通过中间进位信号c1、c2、c3 将4个bcd_fadd模块级联,依次计算个位→十位→百位→千位。

  • 进位传递:低位模块的进位输出(如c1)作为高位模块的进位输入(如add1 的 cin)。

3、BCD加法规则

  • 每个bcd_fadd 模块需实现BCD加法逻辑:

    • 对4位输入进行二进制加法。

    • 调整进位:若结果超过 9(即1001),则需 加6校正(0110)并产生进位。

    • 公式:

temp_sum = a + b + cin;            // 二进制加法结果
sum = (temp_sum > 9) ? temp_sum + 6 : temp_sum;  // 加6校正
cout = (temp_sum > 9) ? 1 : 0;     // 进位生成
  • 例如:5+7 = 12,二进制和为 1100(12),但BCD需要表示为 0010(2)并进位1,因此实际操作为 1100+ 0110= 10010,取低4位 0010,进位为1。

4、模块实例化

bcd_fadd 模块接口:

module bcd_fadd (
    input [3:0] a, b,  // 4位BCD输入
    input cin,          // 进位输入
    output cout,        // 进位输出
    output [3:0] sum    // 4位BCD和
);

系统生成仿真图:

(五)创建一个2对1多路复用器 

        2对1多路复用器根据选择信号(sel)从两个输入(a、b)中选择一个输出。sel=0时输出a,sel=1时输出b。

题目介绍: 

答案代码:

module top_module(
    input a, b, sel,
    output out );

    assign out = sel ? b : a;  // sel=1时选b,否则选a
endmodule

关键点解析

1、功能:通过选择信号 sel 从两个输入(a和b)中选择一个作为输出。

  • 当 sel = 1 时,输出 out = b。

  • 当 sel = 1 时,输出 out = 1。

2、逻辑规则

  • 使用条件运算符?: 实现选择逻辑,等效于以下逻辑表达式:

out = (sel & b) | (~sel & a)

系统生成仿真图:

(六)创建一个2对1总线多路复用器

        2对1总线多路复用器通过控制信号选择两路N位输入中的一路输出,每比特位均含独立2选1单元,实现并行数据切换,常用于总线系统、数据路由及多通道控制等场景。

题目介绍:

答案代码:

module top_module(
    input [99:0] a, b,
    input sel,
    output [99:0] out );

    assign out = sel ? b : a;  // sel=1时选择100位宽的b,否则选a
endmodule

关键点解析:

1、功能:通过单比特选择信号 sel,从两个100位宽的输入数据 a 和 b 中选择一个作为输出 out

  • 当 sel = 1 时,out 完全等于 b 的100位。

  • 当 sel = 0 时,out 完全等于 a 的100位。

2、逻辑规则

  • 并行选择:每个比特位独立选择,所有100位同时完成选择操作。

  • 等效电路:由100个独立的2选1多路选择器组成,每个选择器由 sel 信号控制。

系统生成仿真图:

五、总结 

        在本次FPGA入门学习中,我深刻体会到硬件设计与软件编程的思维差异。通过状态机的三段式设计(状态转移、次态逻辑、输出分离),我不仅掌握了Moore/Mealy型状态机的核心区别,更在实践中感受到模块化设计对复杂逻辑的简化作用——例如LED流水灯项目中,通过独热码编码和计时器控制,直观实现了状态切换与暂停功能,但仿真阶段因计数器位宽设置错误导致时序异常,调试后才理解硬件设计中“精确时序约束”的重要性。

        此外,对比CPLD与FPGA时,我意识到芯片选型需紧密结合需求:CPLD的确定性延迟适合实时控制,而FPGA的并行计算能力更适配算法加速。在HDLbits练习中,BCD加法器的“加6校正”逻辑让我初次体会到硬件设计中的“规则映射”思维——并非所有运算都能直接移植软件逻辑,需结合硬件特性调整(如进位链优化)。

        未来,我计划深入时序分析与高速接口协议实现,同时加强仿真覆盖率训练,以更系统性地提升FPGA开发能力。

参考博客:FPGA开发基础:详解状态机的类型、设计与实现(附代码示例)-CSDN博客


网站公告

今日签到

点亮在社区的每一天
去签到