基于Verilog的FPGA数字逻辑程序设计(第一周 2024.7.15-2024.7.21)

一、FPGA开发流程

1、基本内容

        verilog语法+设计思想(状态机、线性序列机)+学习仿真

2、做事逻辑

        写一套硬件描述语言,能够在指定的硬件平台上实现功能。

        设计定义(如,LED一秒闪烁一次)

        设计输入(编写逻辑)

        综合工具(专业的EDA工具对逻辑分析并得到门级电路,如vivado)

        功能仿真(数字电路仿真接近真实情况)

        布局布线(硬件电路实现)

        

总结:

(1)设计定义

(2)设计输入

(3)分析综合(EDA、Vivado)

(4)功能仿真

(5)布局布线

(6)分析性能

(7)板级调试

二、二选一多路选择器

1、模块逻辑设计:

module mux2(
    a,
    b,
    sel,
    out
    );
    input a;
    input b;
    input sel;
    output out;
    
    assign out = (sel == 0)?a:b;
    
endmodule

2、功能仿真验证:

`timescale 1ns / 1ns

module mux2_tb();

reg S0;
reg S1;
reg S2;
wire mux2_out;

mux2 mux2_inst0(
    .a(S0),
    .b(S1),
    .sel(S2),
    .out(mux2_out)
    );

initial begin
    S0 = 0; S1 = 0; S2 = 0;
    #20;
    S0 = 0; S1 = 0; S2 = 1;
    #20;
    S0 = 0; S1 = 1; S2 = 0;
    #20;
    S0 = 0; S1 = 1; S2 = 1;
    #20;
    S0 = 1; S1 = 0; S2 = 0;
    #20;
    S0 = 1; S1 = 0; S2 = 1;
    #20;
    S0 = 1; S1 = 1; S2 = 0;
    #20;
    S0 = 1; S1 = 1; S2 = 1;
    #20;
end

endmodule

 三、38译码器

1、基本原理

2、模块逻辑设计:

module decoder_3_8(
    A0,
    A1,
    A2,
    Y0,
    Y1,
    Y2,
    Y3,
    Y4,
    Y5,
    Y6,
    Y7
);

    input A0;
    input A1;
    input A2;
    output reg Y0;
    output reg Y1;
    output reg Y2;
    output reg Y3;
    output reg Y4;
    output reg Y5;
    output reg Y6;
    output reg Y7;
    
always@(*)
    case({A2,A1,A0})
        3'b000:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0001;
        3'b001:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0010;
        3'b010:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0100;
        3'b011:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_1000;
        3'd4:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0001_0000;
        3'd5:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0010_0000;
        3'd6:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0100_0000;
        3'd7:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b1000_0000;
        default:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0000;
    endcase

endmodule

        这里的case语句中用了位拼接语句,用{ }表示。case语句一定要加default。

        常用数值的表示:3'b001(3代表位宽,表示3位宽的二进制数1),3'd5(3位宽的十进制数5),8'h5e(8位宽的十六进制数5e)。

        时序逻辑过程中要赋值的output需要定义为reg类型,直接用于assign输出的可以用wire类型。

3、功能仿真验证:

`timescale 1ns/1ns

module decoder_3_8_tb();

    reg A0;
    reg A1;
    reg A2;
    
    wire Y0;
    wire Y1;
    wire Y2;
    wire Y3;
    wire Y4;
    wire Y5;
    wire Y6;
    wire Y7;

    decoder_3_8 decoder_3_8_inst0(
        .A0(A0),
        .A1(A1),
        .A2(A2),
        .Y0(Y0),
        .Y1(Y1),
        .Y2(Y2),
        .Y3(Y3),
        .Y4(Y4),
        .Y5(Y5),
        .Y6(Y6),
        .Y7(Y7)
    );
    
    initial begin
        A2 = 0; A1 = 0; A0 = 0;
        #20;
        A2 = 0; A1 = 0; A0 = 1;
        #20;
        A2 = 0; A1 = 1; A0 = 0;
        #20;
        A2 = 0; A1 = 1; A0 = 1;
        #20;
        A2 = 1; A1 = 0; A0 = 0;
        #20;
        A2 = 1; A1 = 0; A0 = 1;
        #20;
        A2 = 1; A1 = 1; A0 = 0;
        #20;
        A2 = 1; A1 = 1; A0 = 1;
        #20;
        $stop;    
    end

endmodule

        `timescale 1ns / 1ns定义了最小的时间单位。如果是1ns/1ps,那说明最小的时间整数单位是1ns(也就是#1表示的值),但是小数可以最小表示到1ps。

四、时序逻辑:D触发器和计数器

1、基本原理        

D触发器:

计数器:

2、模块逻辑设计


//做一个T=0.5s翻转闪烁一次的led灯
//芯片晶振一个周期Tclk是20ns,那么计数器cnt_max=T/Tclk=500*1000*1000/20=25_000_000

module led_twinkle(
    Clk,
    Reset_n,
    Led
);

    input Clk;
    input Reset_n;
    output reg Led;
    
    reg [24:0] counter;
    
    //计数器
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter <= 0;
    else if(counter == 25_000_000 - 1)
        counter <= 0;
    else
        counter <= counter + 1'd1;
        
    //led翻转
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Led <= 1'b0;
    else if(counter == 25_000_000 - 1)
        Led <= ~Led;

endmodule

        注意,counter == 25_000_000 - 1,这里需要减1,是因为counter计满归0(counter <= 0)也需算一次计数时间周期。

        计数器的逻辑和LED的逻辑分开写。由于是并行处理,所以每个逻辑都可单独写,比较清晰。

3、功能仿真验证

`timescale 1ns/1ns

module led_twinkle_tb();

    reg Clk;
    reg Reset_n;
    wire Led;
    
    led_twinkle led_twinkle(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Led(Led)
    );
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial begin
        Reset_n = 0;
        #201;
        Reset_n = 1;
        # 2000_000_000;  //延时2s
        $stop;
    end

endmodule

        仿真中规中矩,没啥特别的。先initial时钟,再进行10ns的翻转;然后initial复位端并进行延时观察。

五、使用移位和38译码器实现LED流水灯

1、移位实现流水灯

module led_run(
    Clk,
    Reset_n,
    Led
);

    input Clk;
    input Reset_n;
    output reg [7:0] Led;
    
    reg [24:0] counter;
    
    //计数器逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter <= 0;
    else if(counter == 25_000_000 - 1)    //0.5s为单位,仿真时候调成25000
        counter <= 0;
    else
        counter <= counter + 1'd1;
    
    //led逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Led <= 8'b0000_0001;
    else if(counter == 25_000_000 - 1)begin
        Led <= {Led[6:0], Led[7]};
    end

endmodule

        这里的Led <= {Led[6:0], Led[7]}又用到了位拼接的方法,也可以用Led <= Led<<1 来表示左移1位,但是这样的话还需要添加左移溢出后清零的操作。

2、38译码器实现流水灯

模块逻辑设计

module led_run3(
    Clk,
    Reset_n,
    Led
);

    input Clk;
    input Reset_n;
    output wire [7:0] Led;    //这里的led因为是38译码器的输出,是下层连线到上层,所以要用wire
    
    reg [24:0] counter;    //总计数器
    
    parameter MCNT = 25_000_000 - 1;    //用参数表示值了
    
    //计数器1逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter <= 0;
    else if(counter == MCNT)    //0.5s为单位,计数25_000_000
        counter <= 0;
    else
        counter <= counter + 1'd1;
        
    reg [2:0] counter2;    //计数器2,作为38译码器的输入,8位
    //38译码器计数器逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter2 <= 0;
    else if(counter == MCNT)
        counter2 <= counter2 + 1'b1;
        
    //38译码器实现流水灯
    decoder_3_8 decoder_3_8_inst0(
        .A0(counter2[0]),
        .A1(counter2[1]),
        .A2(counter2[2]),
        .Y0(Led[0]),
        .Y1(Led[1]),
        .Y2(Led[2]),
        .Y3(Led[3]),
        .Y4(Led[4]),
        .Y5(Led[5]),
        .Y6(Led[6]),
        .Y7(Led[7])
    );

endmodule

        这里需要用到2个计数器了,counter1负责0.5s计数,counter2负责移位计数。

        调用了之前的38译码器模块进行例化。

功能仿真验证:

`timescale 1ns/1ns

module led_run3_tb();

    reg Clk;
    reg Reset_n;
    wire [7:0] Led;

//  也可以这样例化
//    led_run3
//    #(
//        .MCNT(25_000 - 1)
//    )
//    led_run3_inst0(
//            .Clk(Clk),
//            .Reset_n(Reset_n),
//            .Led(Led)
//        );
        
    led_run3 led_run3_inst0(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Led(Led)
    );
    
    defparam led_run3_inst0.MCNT = 25_000 - 1;    //重新定义
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial begin
        Reset_n = 0;
        #201;
        Reset_n = 1;
        //#2000_000_000;    //run 4s
        #40_000_000;
        
        $stop;
        
    end

endmodule

         语句:defparam led_run3_inst0.MCNT = 25_000 - 1 对例化模块中参数的重定义,和逻辑设计代码的语句:parameter MCNT = 25_000_000 - 1 相对应。这里用于节约仿真时间。

六、参数化设计实现模块的重用

1、模块逻辑设计

        首先要在source里添加led_twinkle的模块。记得实现手动copy或者添加的时候点copy选项(二者选其一即可)。

module led_twinkle_4(
    Clk,
    Reset_n,
    Led
);
    input Clk;
    input Reset_n;
    output [3:0] Led;
    
    parameter MCNT_0 = 50_000_000 - 1;
    parameter MCNT_1 = 25_000_000 - 1;
    parameter MCNT_2 = 12_500_000 - 1;
    parameter MCNT_3 = 6_250_000 - 1;
    
    led_twinkle led_twinkle_4_inst0(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Led(Led[0])
    );
    defparam led_twinkle_4_inst0.MCNT = MCNT_0;
    
    led_twinkle led_twinkle_4_inst1(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Led(Led[1])
    );
    defparam led_twinkle_4_inst1.MCNT = MCNT_1;
    
    led_twinkle led_twinkle_4_inst2(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Led(Led[2])
    );
    defparam led_twinkle_4_inst2.MCNT = MCNT_2;
    
    led_twinkle led_twinkle_4_inst3(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Led(Led[3])
    );
    defparam led_twinkle_4_inst3.MCNT = MCNT_3;
    
endmodule

2、阻塞赋值和非阻塞赋值

七、线性序列机的原理和应用

1、实现目标

任务3:

        这里直接实现任务4的代码了。

要点分析:

        普通情况下,间隔时间不一定是1s,因此需要考虑间隔要求不是1s的情况。

        如果空闲状态和动态变化状态作为一个整体处理,则计数值不太好确定。

解决方案:

        将两个状态分开处理,各自使用一个计数器实现。

        空闲态间隔时间用计数器counter2实现。

        变化态的最小时间段是0.25s时间,用一个计数器counter0实现

        8个状态,再用一个3位计数器counter1实现

        counter0和counter2由于存在计数的停止和发生,因此需要两个使能信号对其控制。

2、模块逻辑设计

module led_ctrl_4(
    Clk,
    Reset_n,
    SW,
    Led
);
    input Clk;
    input Reset_n;
    input [7:0] SW;
    output reg Led;
    
    reg [26:0] counter0;    //0.25s进位
    reg [2:0] counter1;    //8个counter2进1位
    reg [26:0] counter2;    //1s进位
    
    reg en_counter0;    // 为1时,counter0才能计数
    reg en_counter2;    // 为1时,counter2才能计数
    
    parameter TIME_UNIT_MS = 1000;
    parameter MCNT0 = 125_00_000 - 1;    // 0.25s
    parameter MCNT1 = 8-1;    // 8个状态
    parameter MCNT2 = 50_000_000 - 1;    // 1s
    
    // counter0逻辑 计数控制LED的切换时间
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter0 <= 0;
    else if(en_counter0)
        begin
            if(counter0 == MCNT0)
                counter0 <= 0;
            else
                counter0 <= counter0 + 1'd1;
        end
    
    // counter1逻辑 计数当前LED状态是第几个状态
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter1 <= 0;
    else if(counter0 == MCNT0)
        counter1 <= counter1 + 1'd1;
        
    // counter2逻辑 计数空闲状态时间
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter2 <= 0;
    else if(en_counter2)
        begin
            if(counter2 == MCNT2)
                counter2 <= 0;
            else
                counter2 <= counter2 + 1'd1;
        end
    
    // en_counter0逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        en_counter0 <= 1'd0;
    else if((counter1 == 7)&&(counter0 == MCNT0))
        en_counter0 <= 1'd0;
    else if(counter2 == MCNT2)
        en_counter0 <= 1'd1;
          
    // en_counter2逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        en_counter2 <= 1'd1;
    else if((counter1 == 7)&&(counter0 == MCNT0))
        en_counter2 <= 1'd1;
    else if(counter2 == MCNT2)
        en_counter2 <= 1'd0;
    
    // Led逻辑    
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Led <= 1'd0;
    else if(en_counter2)
        Led <= 1'd0;
    else
        begin
            case(counter1)
                0:Led <= SW[0];
                1:Led <= SW[1];
                2:Led <= SW[2];
                3:Led <= SW[3];
                4:Led <= SW[4];
                5:Led <= SW[5];
                6:Led <= SW[6];
                7:Led <= SW[7];
                default: Led <= Led;
            endcase
        end
        
endmodule

3、功能仿真验证

`timescale 1ns/1ns

module led_ctrl_4_tb();
    
    reg Clk;
    reg Reset_n;
    reg [7:0] SW;
    wire Led;
    
    led_ctrl_4 led_ctrl_4_inst0(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .SW(SW),
        .Led(Led)
    );
    
    defparam led_ctrl_4_inst0.TIME_UNIT_MS = 1;
    defparam led_ctrl_4_inst0.MCNT0 = 125_00 - 1;
    defparam led_ctrl_4_inst0.MCNT2 = 50_000 - 1;
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    initial begin
        Reset_n = 0;
        SW = 8'b1010_1001;
        #201;
        Reset_n = 1;
        #40_000_000;
        SW = 8'b1000_0001;
        #40_000_000;
        $stop;
    end

endmodule

仿真结果图:

八、串口通信发送

1、基本原理

        UART:通用异步收发器。是一种串行异步通信协议,规定了传输数据时数据的传输方式和所使用的信号。

        不同接口:

波特率:

        9600:每秒钟传输9600个码元,每个码元占用时间为1/9600秒

        115200:同理

2、模块逻辑设计

设计要求:

模块:

要求分析:

程序设计:

由于考虑到模块的复用性,该模块代码不加入LED翻转,仅考虑UART串口数据发送。

module uart_byte_tx(
    Clk,
    Data,
    Reset_n,
    Send_Go,
    uart_tx,
    Tx_Done
);
    input Clk;
    input [7:0] Data;
    input Reset_n;
    input Send_Go;    //评判外界是否需要发送数据,高电平则启动发送
    output reg uart_tx;
    output reg Tx_Done;
    
    parameter BAUD = 9600;
    parameter CLOCK_FREQ = 50_000_000;
    
    parameter MCNT_BAUD = CLOCK_FREQ / BAUD - 1;    //通过波特率算出 波特率计数器 最大计数值
    parameter MCNT_BIT = 10 - 1;
    
    reg [29:0] baud_div_cnt;    //波特率计数器,计数到1/9600 * 1_000_000_000 / 20 - 1时清零
    reg [3:0] bit_count;    //位计数器
    
    reg en_baud_cnt;    //波特率使能
    
    reg [7:0] r_Data;    //寄存器存储数据
    
    wire w_Tx_Done;    //让Tx_Done可以时序输出
    
    //波特率计数器逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        baud_div_cnt <= 0;
    else if(en_baud_cnt)
        begin
            if(baud_div_cnt == MCNT_BAUD)
                baud_div_cnt <= 0;
            else
                baud_div_cnt <= baud_div_cnt + 1'd1;
        end
    else
        baud_div_cnt <= 0;
        
    //波特率计数器使能逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        en_baud_cnt <= 1'd0;
    else if(w_Tx_Done)
        en_baud_cnt <= 1'd0;
    else if(Send_Go)
        en_baud_cnt <= 1'd1;
        
    //位计数器逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        bit_count <= 0;
    else if(baud_div_cnt == MCNT_BAUD)
        begin
            if(bit_count == MCNT_BIT)    
                bit_count <= 0;
            else
                bit_count <= bit_count + 1'd1;
        end
        
    
    //寄存器逻辑(用于位发送)
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        r_Data <= 0;
    else if(Send_Go)    //延时结束那一刻,对寄存器写入Data
        r_Data <= Data;
    else
        r_Data <= r_Data;
    
    //位发送逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        uart_tx <= 1'd1;
    else if(en_baud_cnt == 0)
        uart_tx <= 1'd1;
    else
        begin
            case(bit_count)
                0: uart_tx <= 1'd0;
                1: uart_tx <= r_Data[0];
                2: uart_tx <= r_Data[1];
                3: uart_tx <= r_Data[2];
                4: uart_tx <= r_Data[3];
                5: uart_tx <= r_Data[4];
                6: uart_tx <= r_Data[5];
                7: uart_tx <= r_Data[6];
                8: uart_tx <= r_Data[7];
                9: uart_tx <= 1'd1;
                default: uart_tx <= uart_tx;
            endcase
        end


    // Tx_Done逻辑,判断数据是否发送完毕,时序逻辑
    assign w_Tx_Done = ((bit_count == MCNT_BIT)&&(baud_div_cnt == MCNT_BAUD));    //数据发送完后,Tx_Done置1
    always @(posedge Clk)
        Tx_Done <= w_Tx_Done;

endmodule

九、串口通信接收

1、模块逻辑设计

设计要求:

模块:

要点:

        (1)对于串口接收,如何从串行数据中准确获取每一位数据?

        在计数中点采样。

        (2)如何检测串口接收数据的起始位的下降沿?

        通过D触发器存储上一个时钟输入的数据,与现在时钟输入的数据比较,判断是上升还是下降。

        (3)如何设计波特率分频计数器使能逻辑?

        当检测到下降沿时,使能端关闭打开,开始计数;计数完结束位时,使能端关闭,停止计数。

        特殊情况,当检测到下降沿后,立刻又检测到上升沿时,则视为毛刺信号干扰,仍需关闭使能。

        (4)如何知道当前读取的信号位于UART信号的哪一位?

        定义一个位计数器。

        (5)如何实现位接收逻辑?

        用一个8位寄存器的接收

        (6)产生一个标志信号,在每次接收完毕后通知外界接收完成

        (7)uart_rx作为输入是异步信号,如果恰好在Clk上升沿附近发生变化的时候,就会无法满足D触发器存储数据的必要条件,导致D触发器无法准确判断该时刻uart_rx信号状态,导致D触发器输出振荡,不稳定,(亚稳态)如何解决?

        在100MHz的时序逻辑电路中,使用两级D触发器进行同步(打拍),将纯外部输入信号同步为时序信号,可以有效避免亚稳态。

        (8)一个优化:不同系统在UART通信时,时钟频率不一样,有误差,存在发送所需要的位时间小于标准时间的情况,那么对于接收器来说,接收器接收一个数据的时间就会比发送方发送数据要耗时长一点。如果发送方连续发数据时,可能会导致某次数据接收还在上一个数据,但发送数据起始位已经到达了,接收端错过该数据的起始位,从而导致数据传输错误。

        解决方案:接收方在接收停止位的时候,只使用0.5位的时间。为发送器提供了0.5位的容错时间。

程序设计:

module uart_byte_rx(
    Clk,
    Reset_n,
    uart_rx,
    Rx_Done,
    Rx_Data
);
    input Clk;
    input Reset_n;
    input uart_rx;
    output reg Rx_Done;
    output reg [7:0] Rx_Data;
    
    parameter CLOCK_FREQ = 50_000_000;    //时钟频率
    parameter BAUD = 9600;    //波特率
    parameter MCNT_BAUD = CLOCK_FREQ / BAUD - 1;    //波特计数最大值
    
    
    reg [29:0] baud_div_cnt;    //波特率计数器
    reg en_baud_cnt;    //波特率使能标志
    reg [3:0] bit_cnt;    //位计数器
    reg r_uart_rx;    //边沿检测逻辑,D触发器输出信号
    reg [7:0] r_Rx_Data;    //存储uart_rx信号,齐了后一起输出给Rx_Data
    
    wire w_Rx_Done;    //信号接收完成标志
    wire nege_uart_rx;    //下降沿判断标志
    
    reg dff0_uart_rx;    //D触发器
    reg dff1_uart_rx;
    
    //波特率计数逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        baud_div_cnt <= 0;
    else if(en_baud_cnt)
        begin
            if(baud_div_cnt == MCNT_BAUD)
                baud_div_cnt <= 0;
            else
                baud_div_cnt <= baud_div_cnt + 1'd1;
        end
    else
        baud_div_cnt <= 0;
    
    //UART信号边沿检测逻辑
    always @(posedge Clk)
    dff0_uart_rx <= uart_rx;
    
    always @(posedge Clk)
    dff1_uart_rx <= dff0_uart_rx;
    
    always @(posedge Clk)
    r_uart_rx <= dff1_uart_rx;
    
    assign nege_uart_rx = ((dff1_uart_rx == 0) && (r_uart_rx == 1));
    
    //波特率计数器使能逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        en_baud_cnt <= 0;
    else if(nege_uart_rx)    //下降沿到来
        en_baud_cnt <= 1;
    else if((baud_div_cnt == MCNT_BAUD/2) && (bit_cnt == 0) && (dff1_uart_rx == 1))    //排除毛刺导致的下降沿
        en_baud_cnt <= 0;
    else if((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD/2))    //位计数器计满,uart_rx接收完毕
        en_baud_cnt <= 0;
    
    //位计数器逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        bit_cnt <= 0;
    else if((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD/2))
        bit_cnt <= 0;
    else if(baud_div_cnt == MCNT_BAUD)
        bit_cnt <= bit_cnt + 1'd1;
        
    
    //位接收逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        r_Rx_Data <= 0;
    else if(baud_div_cnt == MCNT_BAUD/2)
        begin
            case(bit_cnt)
                1: r_Rx_Data[0] <= dff1_uart_rx;
                2: r_Rx_Data[1] <= dff1_uart_rx;
                3: r_Rx_Data[2] <= dff1_uart_rx;
                4: r_Rx_Data[3] <= dff1_uart_rx;
                5: r_Rx_Data[4] <= dff1_uart_rx;
                6: r_Rx_Data[5] <= dff1_uart_rx;
                7: r_Rx_Data[6] <= dff1_uart_rx;
                8: r_Rx_Data[7] <= dff1_uart_rx;
                default: r_Rx_Data <= r_Rx_Data;
            endcase
        end
    
    //接收完成标志信号逻辑
    assign w_Rx_Done = ((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD/2));
    
    always @(posedge Clk)
        Rx_Done <= w_Rx_Done;
    
    //r_Rx_Data逻辑,寄存器存储后再输出给Rx_Data
    always @(posedge Clk)
    if(Rx_Done)
        Rx_Data <= r_Rx_Data;
    
endmodule

2、功能仿真验证

module uart_byte_rx_tb();

    reg Clk;
    reg Reset_n;
    reg uart_rx;
    wire Rx_Done;
    wire [7:0] Rx_Data;

    uart_byte_rx uart_byte_rx_inst0(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .uart_rx(uart_rx),
        .Rx_Done(Rx_Done),
        .Rx_Data(Rx_Data)
    );
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial
    begin
        Reset_n = 0;
        uart_rx = 1;
        #201;
        Reset_n = 1;
        #200;
        
        //开始传输
        //8'b0101_0101
        uart_rx = 0;  #(5208*20);    //起始位
        uart_rx = 1;  #(5208*20);    //bit0
        uart_rx = 0;  #(5208*20);    //bit1
        uart_rx = 1;  #(5208*20);    //bit2
        uart_rx = 0;  #(5208*20);    //bit3
        uart_rx = 1;  #(5208*20);    //bit4
        uart_rx = 0;  #(5208*20);    //bit5
        uart_rx = 1;  #(5208*20);    //bit6
        uart_rx = 0;  #(5208*20);    //bit7
        uart_rx = 1;  #(5208*20);    //停止位
        #(5208*20*10);
        
        //8'b1010_1010
        uart_rx = 0;  #(5208*20);    //起始位
        uart_rx = 0;  #(5208*20);    //bit0
        uart_rx = 1;  #(5208*20);    //bit1
        uart_rx = 0;  #(5208*20);    //bit2
        uart_rx = 1;  #(5208*20);    //bit3
        uart_rx = 0;  #(5208*20);    //bit4
        uart_rx = 1;  #(5208*20);    //bit5
        uart_rx = 0;  #(5208*20);    //bit6
        uart_rx = 1;  #(5208*20);    //bit7
        uart_rx = 1;  #(5208*20);    //停止位
        #(5208*20*10);
        
        //8'b1111_0000
        uart_rx = 0;  #(5208*20);    //起始位
        uart_rx = 0;  #(5208*20);    //bit0
        uart_rx = 0;  #(5208*20);    //bit1
        uart_rx = 0;  #(5208*20);    //bit2
        uart_rx = 0;  #(5208*20);    //bit3
        uart_rx = 1;  #(5208*20);    //bit4
        uart_rx = 1;  #(5208*20);    //bit5
        uart_rx = 1;  #(5208*20);    //bit6
        uart_rx = 1;  #(5208*20);    //bit7
        uart_rx = 1;  #(5208*20);    //停止位
        #(5208*20*10);
        
        //8'b0000_1111
        uart_rx = 0;  #(5208*20);    //起始位
        uart_rx = 1;  #(5208*20);    //bit0
        uart_rx = 1;  #(5208*20);    //bit1
        uart_rx = 1;  #(5208*20);    //bit2
        uart_rx = 1;  #(5208*20);    //bit3
        uart_rx = 0;  #(5208*20);    //bit4
        uart_rx = 0;  #(5208*20);    //bit5
        uart_rx = 0;  #(5208*20);    //bit6
        uart_rx = 0;  #(5208*20);    //bit7
        uart_rx = 1;  #(5208*20);    //停止位
        #(5208*20*10);
        
        $stop;
    end

endmodule

3、亚稳态

十、基于状态机的按键消抖

1、基本原理

按键工作状态:

        (1)按键未按下,空闲态,电路输出高电平

        (2)按键按下抖动,电路输出在高低电平之间跳转

        (3)按下抖动停止,静止态,电路输出低电平

        (4)按键松开抖动,电路输出在高低电平之间跳转

        (5)松开抖动停止,电路输出高电平

2、模块逻辑设计

要点:

(1)如何判断抖动已经停止了?

思路:按键按下时,在检测到下降沿后,判断低电平的持续时间,如果低于20ms,则认为是抖动;如果超过20ms,则认为按键按下。

        按键释放时,在检测到上升沿后,判断高电平持续时间,如果低于20ms,则认为是抖动;如果超过20ms,则认为按键释放。

(2)状态转移图分析

程序设计:

module key_filter(
    Clk,
    Reset_n,
    Key,
    Key_P_Flag,
    Key_R_Flag,
    Key_State
);
    input Clk;
    input Reset_n;
    input Key;
    output reg Key_P_Flag;
    output reg Key_R_Flag;
    output reg Key_State;
    
    reg [1:0] state;
    
    localparam IDLE = 0;    //状态机定义
    localparam P_FILTER = 1;
    localparam WAIT_R = 2;
    localparam R_FILTER = 3;
    
    parameter MCNT = 1_000_000;
    reg [29:0] cnt;
    
    reg sync_d0_Key;
    reg sync_d1_Key;
    reg r_Key;
    
    wire pedge_key;
    wire nedge_key;
    
    wire time_20ms_reached;
    
    assign time_20ms_reached = (cnt >= MCNT);
    
    //同步D触发器逻辑
    always @(posedge Clk)
        sync_d0_Key <= Key;
    always @(posedge Clk)
        sync_d1_Key <= sync_d0_Key;
    always @(posedge Clk)
        r_Key <= sync_d1_Key;
        
    //上升沿下降沿逻辑
    assign pedge_key = ((r_Key == 0) && (sync_d1_Key == 1));
    assign nedge_key = ((r_Key == 1) && (sync_d1_Key == 0));
    
    //状态机逻辑
    always @(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        begin
        state <= IDLE;
        Key_P_Flag <= 1'd0;
        Key_R_Flag <= 1'd0;
        cnt <= 0;
        Key_State <= 1'd1;
        end
    else
        begin
            case(state)
                IDLE:
                    begin
                    Key_R_Flag <= 1'd0;
                    if(nedge_key)
                        state <= P_FILTER;    
                    end
                P_FILTER:
                    begin
                    if(time_20ms_reached)
                        begin
                        state <= WAIT_R;
                        Key_P_Flag <= 1'd1;
                        Key_State <= 1'd0;
                        cnt <= 0;
                        end
                    else if(pedge_key)
                        begin
                        state <= IDLE;
                        cnt <= 0;
                        end
                    else
                        begin
                        state <= state;
                        cnt <= cnt + 1'd1;
                        end
                    end
                
                WAIT_R:
                    begin
                    Key_P_Flag <= 1'd0;
                    if(pedge_key)
                        state <= R_FILTER;
                    end                
                
                R_FILTER:
                    begin
                    if(time_20ms_reached)
                        begin
                        state <= IDLE;
                        Key_R_Flag <= 1'd1;
                        Key_State <= 1'd1;
                        cnt <= 0;
                        end
                    else if(nedge_key)
                        begin
                        state <= WAIT_R;
                        cnt <= 0;
                        end
                    else
                        begin
                        state <= state;
                        cnt <= cnt + 1'd1;
                        end
                    end
            endcase                
        end
        
    //20ms计数器逻辑 (不用了,有小错误,由于D触发器会有延迟一个时钟)
//    always @(posedge Clk or negedge Reset_n)
//    if(!Reset_n)
//        cnt <= 0;
//    else if((state == P_FILTER) || (state == R_FILTER))
//        cnt <= cnt + 1'd1;
//    else
//        cnt <= 0; 

endmodule

3、功能仿真验证

module key_filter_tb();

    reg Clk;
    reg Reset_n;
    reg Key;
    wire Key_P_Flag;
    wire Key_R_Flag;
    wire Key_State;

    key_filter key_filter_inst(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Key(Key),
        .Key_P_Flag(Key_P_Flag),
        .Key_R_Flag(Key_R_Flag),
        .Key_State(Key_State)
    );
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial
        begin
        Reset_n = 0;
        Key = 1;
        #201;
        Reset_n = 1;
        
        //第一次按下测试
        //空闲稳定
        Key = 1;  #100_000_000;    //空闲 稳定 100ms
        
        //按下抖动
         Key = 0;  #18_000_000;    //按下 抖动 低18ms
         Key = 1;  #2_000_000;    //按下 抖动 高2ms
         Key = 0;  #1_000_000;    //按下 抖动 低1ms
         Key = 1;  #200_000;    //按下 抖动 高0.2ms
         Key = 0;  #20_000_000;    //按下 稳定 低20ms
        
        //按下稳定
        Key = 0;  #50_000_000;    //按下 稳定 50ms
        
        //释放抖动
        Key = 1;  #2_000_000;    //释放 抖动 高2ms
        Key = 0;  #1_000_000;    //释放 抖动 低1ms
        Key = 1;  #20_000_000;    //释放 稳定 高20ms
        
        //释放稳定
        Key = 1;  #50_000_000;    //释放 稳定 50ms
        
        
        
        //第二次按下尝试
        //空闲稳定
        Key = 1;  #100_000_000;    //空闲 稳定 100ms
        
        //按下抖动
        Key = 0;  #18_000_000;    //按下 抖动 低18ms
        Key = 1;  #2_000_000;    //按下 抖动 高2ms
        Key = 0;  #1_000_000;    //按下 抖动 低1ms
        Key = 1;  #200_000;    //按下 抖动 高0.2ms
        Key = 0;  #20_000_000;    //按下 稳定 低20ms
        
        //按下稳定
        Key = 0;  #50_000_000;    //按下 稳定 50ms
        
        //释放抖动
        Key = 1;  #2_000_000;    //释放 抖动 高2ms
        Key = 0;  #1_000_000;    //释放 抖动 低1ms
        Key = 1;  #20_000_000;    //释放 稳定 高20ms
        
        //释放稳定
        Key = 1;  #50_000_000;    //释放 稳定 50ms        
        
       $stop;
        end

endmodule

这里要注意,如果单独写计数器逻辑的话,用到的state的值是上一个时钟周期存储的state,会有一个时钟的延时性,造成误差。因此精准的计数器逻辑需要写在状态机的case中,跟随状态变换而变化。

仿真结果:

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于FPGA的数字钟万年历第二部分:clock_verilog.part2.rar是一个基于Verilog语言的FPGA设计文件,用于实现数字钟和万年历功能。这部分的设计文件包括了时钟模块、日历模块、显示模块等,通过FPGA芯片上的逻辑单元和时钟资源,实现了数字钟和万年历的准确显示和计时功能。 时钟模块负责生成系统的时钟信号,并且能够实现时钟的调整和同步功能,保证数字钟的准确性和稳定性。日历模块则包括了年、月、日、星期等时间信息的计算和存储功能,能够精确地显示当前的日期信息,并且可以根据闰年等特殊情况进行调整。显示模块能够将数字钟和日历的信息通过FPGA芯片上的数码管或者液晶显示屏进行显示,提供直观的时间信息输出。 这部分的设计文件能够通过FPGA开发工具进行综合、布线和生成比特流文件,然后通过下载到FPGA芯片中进行验证和使用。在实际的应用中,可以将这一设计文件与外部的时钟源结合,搭建一个完整的数字钟与万年历系统,用于各种计时和显示应用场景中。 通过这一设计文件,可以了解到基于FPGA的数字钟和万年历的实现方法,为数字钟和日历的相关研究和开发提供了有益的参考。同时,这一设计文件也为基于Verilog语言的FPGA设计提供了一个实际的案例,对于想要学习和掌握FPGA设计方法的人们来说,具有很好的参考价值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值