【FPGA】状态机思想实现LED流水灯&HDLbits组合逻辑题训练

目录

一、状态机思想实现FPGA点亮LED流水灯

1.1状态机思想

(1)状态机类型

Moore型状态机

Mealy型状态机

(2)状态机设计步骤

1.2实现LED流水灯

(1)在VScode中进行代码编辑

(2)在QUartus中创建项目

(3)仿真测试

(4)DE2-115烧录实现

二、CPLD和FPGA芯片的对比

2.1主要技术区别

2.2适用场合对比

[(1)CPLD 的典型应用场景](#(1)CPLD 的典型应用场景)

[(2)FPGA 的典型应用场景](#(2)FPGA 的典型应用场景)

三、HDLbits组合逻辑题目训练

[3.1More logic gates-Gates](#3.1More logic gates-Gates)

[3.2 9-to-1 multiplexer](#3.2 9-to-1 multiplexer)

[3.3 Gates and vectors](#3.3 Gates and vectors)

[3.4Full adder](#3.4Full adder)

[3.5 Signed addition overflow](#3.5 Signed addition overflow)


一、状态机思想实现FPGA点亮LED流水灯

1.1状态机思想

状态机是一种用于描述系统行为的数学模型,它将系统抽象为:

有限的状态:系统在某一时刻只能处于其中一个状态。

转移条件:状态之间的切换规则(通常由事件或条件触发)。

动作:在进入、离开或保持状态时执行的操作。

下面是状态机和普通顺序逻辑的对比表:

对比项 状态机 普通顺序逻辑
复杂度管理 适合多条件分支的复杂逻辑 适合线性流程
可读性 状态转移图直观易维护 嵌套if-else难以扩展
灵活性 动态调整状态和转移条件 修改可能影响整体逻辑
硬件实现 明确对应寄存器+组合逻辑 可能产生冗余电路

(1)状态机类型

Moore型状态机

输出仅与当前状态有关,与输入事件无关。例:交通灯控制器(红灯、绿灯、黄灯的切换逻辑固定)。

公式:输出 = f(当前状态)

Mealy型状态机

输出与当前状态和输入事件都关。例:自动售货机(投币金额不同,输出找零行为不同)。

公式:输出 = f(当前状态, 输入事件)

(2)状态机设计步骤

明确系统需求:确定所有可能的状态和触发条件。

绘制状态转移图:图形化表示状态、事件和转移关系。

状态编码 :用二进制值表示每个状态(如 S0=00, S1=01)。

实现逻辑:通过代码(Verilog/VHDL/C)描述状态转移和输出。

验证:仿真测试覆盖所有状态路径,硬件验证实际行为。

1.2实现LED流水灯

(1)在VScode中进行代码编辑

新建工程项目文件,并在该文件夹中添加下面的文件目录

在rtl目录下创建LS_LED.v文件并编写代码

复制代码
module LS_LED(
    input                clk,
    input                rst_n,
    input                pause_sw,
    output reg [5:0]     led
);
 
parameter T = 50_000_000;             // 计数器最大值,用于生成 1 秒定时
 
reg [2:0] cstate;                     // 现态
reg [2:0] nstate;                     // 次态
 
// 状态划分
localparam state_led0 = 0;            // LED0 亮
localparam state_led1 = 1;           // LED1 亮
localparam state_led2 = 2;           // LED2 亮
localparam state_led3 = 3;           // LED3 亮
localparam state_led4 = 4;           // LED4 亮
localparam state_led5 = 5;           // LED5 亮
 
reg [25:0] cnt = 0;                   // 计时器赋初值为 0
 
// 计数器模块
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cnt <= 0;                     // 按下复位键,清零
    else if (cnt == T - 1)            // 计时器达到最大值,清零重新计数
        cnt <= 0;
    else if (!pause_sw)               // 如果未暂停,计数器继续计数
        cnt <= cnt + 1;
end
 
// 第一段:现态跟随次态,时序逻辑,非阻塞赋值
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cstate <= state_led0;         // 复位键被按下,当前状态设置为 LED0 亮
    else if (!pause_sw)               // 如果未暂停,更新状态
        cstate <= nstate;
end
 
//第二段:组合逻辑,阻塞赋值
always @(*) begin
    if (!rst_n)
        nstate = state_led0;          // 复位时回到初始状态
    else
        case (cstate)
            state_led0: begin
 
            if (cnt == T - 1)      // 该状态持续时间为 1 秒,1 秒后跳转到下一个状态
                    nstate = state_led1;
                    nstate = state_led0;
            end
            state_led1: begin
                if (cnt == T - 1)
                    nstate = state_led2;
                else
                    nstate = state_led1;
            end
            state_led2: begin
                if (cnt == T - 1)
                    nstate = state_led3;
                else
                    nstate = state_led2;
            end
            state_led3: begin
                if (cnt == T - 1)
                    nstate = state_led4;
                else
                    nstate = state_led3;
            end
            state_led4: begin
                if (cnt == T - 1)
                    nstate = state_led5;
                else
                    nstate = state_led4;
            end
            state_led5: begin
                if (cnt == T - 1)
                    nstate = state_led0;
                else
                    nstate = state_led5;
            end
            default: nstate = state_led0; // 默认状态
        endcase
end
 
//第三段:跟随状态输出
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        led <= 6'b000001;              // 复位时点亮第一个 LED
    else
        case (cstate)
            state_led0: led <= 6'b000001;
            state_led1: led <= 6'b000010;
            state_led2: led <= 6'b000100;
            state_led3: led <= 6'b001000;
            state_led4: led <= 6'b010000;
            state_led5: led <= 6'b100000;
            default: led <= 6'b000001; // 默认状态
        endcase
end
 
endmodule

在tb文件目录项新建LS_LED_tb.v仿真测试文件,并编写代码

复制代码
`timescale 1ns / 1ps
 
module LS_LED_tb;
 
    //输入信号
    reg clk;
    reg rst_n;
    reg pause_sw;
 
    //输出信号
    wire [5:0] led;  //6个LED输出
 
    //实例化被测模块
    LS_LED uut(
        .clk(clk),
        .rst_n(rst_n),
        .pause_sw(pause_sw),
        .led(led)
        );
 
        //生成时钟信号
        initial begin
            // 初始化信号
            rst_n = 0;      // 初始复位
            pause_sw = 0;   // 初始不暂停
            #20;            // 等待 20 ns
 
         // 释放复位信号
        rst_n = 1;
        #200;           // 等待一段时间观察初始状态
        
        // 测试正常流水灯效果
        pause_sw = 0;   // 不暂停
        #2_000_000;     // 运行 2 秒,观察 LED 变化
 
        // 测试暂停功能
        pause_sw = 1;   // 暂停
        #1_000_000;     // 停留 1 秒,观察 LED 是否保持不变
        pause_sw = 0;   // 恢复
        #2_000_000;     // 再次运行 2 秒,观察 LED 变化
 
        // 测试复位功能
        rst_n = 0;      // 触发复位
        #20;            // 等待复位完成
        rst_n = 1;      // 释放复位
        #200;           // 观察复位后的初始状态
 
        //仿真结束
        $stop;
    end
 
endmodule

(2)在QUartus中创建项目

添加代码文件

添加对应芯片

将LS_LED.v设置为top文件

进行编译

(3)仿真测试

若有同学是第一次进行Modesim仿真,还需要进行一些配置步骤,具体可以看我前面的博客:【FPGA】分秒计数器设计-CSDN博客

这里我们直接进行tb文件的添加,在工具栏处的Assignments选择Settings,然后进行下图的操作

**注:**Test bench name不能有.v

进行RTL Simulation仿真

跳转到Modelsim后就可以观察到波形

(4)DE2-115烧录实现

管脚配置

配置完成后,我们进行代码烧录:

点击Programmer,点击Hardware Setup

选择USB

添加.sof文件,若已经存在则无需再次添加

start开始烧录

二、CPLD和FPGA芯片的对比

2.1主要技术区别

对比项 CPLD (复杂可编程逻辑器件) FPGA (现场可编程门阵列)
基本结构 基于 乘积项(PAL/GAL结构),逻辑块通过全局互连连接 基于 查找表(LUT)+ 寄存器,逻辑单元为可配置逻辑块(CLB)
逻辑容量 较小(通常几千到几万门) 较大(几万门到数百万门)
时序特性 固定延迟,适合同步逻辑 延迟可变,需时序约束优化
存储资源 较少(依赖外部存储器) 丰富(内置Block RAM、分布式RAM)
配置方式 非易失性(EEPROM/Flash,上电即用) 易失性(需外部配置芯片加载SRAM,上电需配置)
功耗 静态功耗低,动态功耗中等 静态功耗较高,动态功耗依赖设计复杂度
灵活性 适合简单、确定性逻辑 适合复杂、并行化设计
开发工具 工具简单(如Quartus Prime的CPLD模块) 工具复杂(需综合、布局布线、时序分析,如Vivado)

2.2适用场合对比

(1)CPLD 的典型应用场景

简单控制逻辑

  • 地址译码、总线接口(如PCI局部总线控制)。

  • 替代传统74系列逻辑芯片(如多路复用器、编码器)。

实时性要求高的系统

  • 电机控制、电源管理(因固定延迟,响应快)。

上电即用场景

  • 汽车电子(点火控制)、工业设备(非易失性配置,无需外部ROM)。

胶合逻辑(Glue Logic)

  • 连接不同接口标准的芯片(如UART转SPI)。
(2)FPGA 的典型应用场景

高性能并行处理

  • 数字信号处理(DSP)、图像处理(如摄像头实时滤波)。

  • 深度学习加速(利用并行计算单元)。

可重构计算

  • 通信协议栈(如5G基带处理)、软件定义无线电(SDR)。

原型验证与ASIC开发

  • 芯片设计前期功能验证(如RTL仿真后的硬件验证)。

复杂时序逻辑

  • 高速接口(DDR控制器、PCIe PHY)、视频编解码(H.264/H.265)。

三、HDLbits组合逻辑题目训练

下面是HDLbits地址,可以通过点击【组合逻辑】进入该模块的学习:

Step one - HDLBits

3.1More logic gates-Gates

题目

该题目要求设计一个 多功能逻辑门电路模块 ,能够同时计算两个1位输入信号 ab 的7种基本逻辑运算结果。具体功能如下:

  1. 输入信号

    a:1位输入

    b:1位输入

  2. 输出信号

    out_anda AND b(逻辑与)

    out_ora OR b(逻辑或)

    out_xora XOR b(逻辑异或)

    out_nanda NAND b(逻辑与非)

    out_nora NOR b(逻辑或非)

    out_xnora XNOR b(逻辑同或)

    out_anotha AND (NOT b)(ab 的非)

代码实现

复制代码
module top_module(
    input a, b,
    output out_and,
    output out_or,
    output out_xor,
    output out_nand,
    output out_nor,
    output out_xnor,
    output out_anotb
);
    // AND 逻辑与
    assign out_and = a & b;
    
    // OR 逻辑或
    assign out_or = a | b;
    
    // XOR 逻辑异或
    assign out_xor = a ^ b;
    
    // NAND 逻辑与非
    assign out_nand = ~(a & b);
    
    // NOR 逻辑或非
    assign out_nor = ~(a | b);
    
    // XNOR 逻辑同或
    assign out_xnor = ~(a ^ b);
    
    // A AND NOT B
    assign out_anotb = a & (~b);
endmodule

结果

3.2 9-to-1 multiplexer

题目

该题目要求设计一个 16位宽度的9选1数据选择器(Multiplexer) ,根据4位选择信号 sel 的值,从9个16位输入数据(ai)中选择一个输出到 out。具体要求如下:

  1. 输入信号

    9个16位数据输入:a[15:0], b[15:0], ..., i[15:0]

    4位选择信号:sel[3:0](可表示范围 0-15,但仅使用 0-8)。

  2. 输出信号

    16位输出:out[15:0],其值由 sel 决定。

代码实现

复制代码
module top_module(
    input [15:0] a, b, c, d, e, f, g, h, i,  // 16位输入信号a-i
    input [3:0] sel,                          // 4位选择信号
    output [15:0] out                         // 16位输出信号
);

always @(*) begin
    case(sel)
        4'd0: out = a;    // sel=0时选择输入a
        4'd1: out = b;    // sel=1时选择输入b
        4'd2: out = c;    // sel=2时选择输入c
        4'd3: out = d;    // sel=3时选择输入d
        4'd4: out = e;    // sel=4时选择输入e
        4'd5: out = f;    // sel=5时选择输入f
        4'd6: out = g;    // sel=6时选择输入g
        4'd7: out = h;    // sel=7时选择输入h
        4'd8: out = i;    // sel=8时选择输入i
        default: out = {16{1'b1}};  // sel=9-15时输出全1(16位均为1)
    endcase
end

endmodule

结果

3.3 Gates and vectors

题目

题目要求设计一个 Verilog 模块,对 4 位输入 in[3:0] 进行以下三种邻位关系检测:

  1. out_both[2:0] (3 位输出):检测当前位与其左侧高位 是否均为 1

    例:out_both[2] = in[2] & in[3]

    注:in[3] 无左侧高位,故不输出 out_both[3]

  2. out_any[3:1] (3 位输出):检测当前位与其右侧低位 是否存在至少一个 1

    例:out_any[2] = in[2] | in[1]

    注:in[0] 无右侧低位,故不输出 out_any[0]

  3. out_different[3:0] (4 位输出):检测当前位与其左侧高位 是否不同(循环左邻,即 in[3] 的左邻是 in[0])。

    例:out_different[2] = in[2] ^ in[3]

    特殊:out_different[3] = in[3] ^ in[0](循环检测)。

代码实现

复制代码
module top_module (
    input [3:0] in,
    output [2:0] out_both,
    output [3:1] out_any,
    output [3:0] out_different
);
    // out_both: 当前位与左侧高位是否均为1(不包含in[3]的左侧)
    assign out_both = in[3:1] & in[2:0];  // 直接按位与

    // out_any: 当前位或右侧低位是否为1(不包含in[0]的右侧)
    assign out_any = in[3:1] | in[2:0];   // 直接按位或

    // out_different: 当前位与左侧高位是否不同(循环左邻)
    assign out_different = in ^ {in[0], in[3:1]};  // 通过循环左移后异或
endmodule

结果

3.4Full adder

题目

该题需要设计一个 1位全加器 的 Verilog 模块,实现以下功能:

输入"

a:1 位加数

b:1 位加数

cin:来自低位的进位输入

输出:

sum:a + b + cin 的本位和(1 位)

cout:向高位的进位输出(1 位)

代码实现

复制代码
module top_module(
    input a,       // 输入位a
    input b,       // 输入位b 
    input cin,     // 进位输入
    output cout,   // 进位输出
    output sum     // 和输出
);

// 全加器逻辑实现
assign sum = a ^ b ^ cin;  // 计算本位和(异或运算)
assign cout = (a & b) | (a & cin) | (b & cin);  // 计算进位(或运算)

endmodule

结果

3.5 Signed addition overflow

题目

该题目需要设计一个 Verilog 模块,实现以下功能:

  1. 输入 :两个 8 位二进制补码形式的有符号数 a[7:0]b[7:0]

  2. 输出

    s[7:0]:a 和 b 的加法结果(8 位二进制补码形式)。

    overflow:1 位标志位,当加法结果发生有符号溢出时置 1,否则置 0。

代码实现

复制代码
module top_module (
    input [7:0] a,        // 8位有符号数输入a(补码表示)
    input [7:0] b,        // 8位有符号数输入b(补码表示)
    output [7:0] s,       // 8位和输出
    output overflow       // 溢出标志(1表示溢出)
);

    // 计算和
    assign s = a + b;
    
    // 检测有符号溢出
    assign overflow = 
        (~a[7] & ~b[7] & s[7]) |  // 正+正=负(上溢)
        (a[7] & b[7] & ~s[7]);     // 负+负=正(下溢)

endmodule

结果

相关推荐
不可思议迷宫3 小时前
状态机思想编程练习
fpga开发
迎风打盹儿3 小时前
FPGA同步复位、异步复位、异步复位同步释放仿真
verilog·fpga·vivado·复位
shock - shock6 小时前
基于高云fpga实现的fir串行滤波器
fpga开发
落笔太慌张~9 小时前
【FPGA基础学习】状态机思想实现流水灯
学习·fpga开发
一瓶勇闯天涯的雪花10 小时前
FPGA入门学习Day0——状态机相关内容解析HDLbits练习
fpga开发
2202_7544215410 小时前
设计一个UART接口的AXI_LITE_MASTER之一 总体介绍
fpga开发
Abcdsa10 小时前
labview RT FPGA学习心得
fpga开发·labview
乌恩大侠1 天前
【调研】YOLO算法在FPGA/ZYNQ上的部署与加速
yolo·fpga开发
Abcdsa1 天前
labview RT FPGA使用技巧 基础知识
fpga开发·labview