Verilog

 逻辑值

  • 0:逻辑低电平,条件为假
  • 1:逻辑高电平,条件为真
  • z:高阻态,无驱动
  • x:未知逻辑电平

常用关键字

  1. module example  //模块的开始 模块名
  2. input  //输入信号
  3. output  //输出信号
  4. wire [0:0] flag;  //线网型变量
  5. reg [7:0] cnt;  //寄存器型变量
  6. parameter或使用localparam 进行参数的定义
  7. 常量:基数表示法[换算为二进制后位宽的总长度]['][数值进制符号][与数值进制符号对应的数值]例8'd171代表位宽是8bit,十进制的171
  8. 进制:十六(h)、八(0)、二(b)
  9. 直接写参数会默认表示为位宽为32bit的十进制数
  10. 阻塞赋值“=”,顺序执行,应用于begin-end串行语句
  11. 非阻塞赋值“<=”,并行执行,应用于寄存器变量initial和always赋值,不允许应用于连续赋值assign。右面的值发生变化,不会立即变化,会在下一个上升沿统一变化,相对于右面信号延迟一个时钟周期
  12. assign po_flag=(cnt==CNT_MAX)?1'b1 :1'b0;
  13. \n换行符

归约运算符、按位运算符

  1. &作为一元运算符时,得到的结果为1bit
  2. &作为二元运算符时,运算应保证二者的比特数相等,按位相与,最后结果位数与其中一个相等
  3. ~&,^,~^,|,~|用法同理

逻辑运算符

  1. &&,两边相同返回结果为1,不同为0
  2. ||(或),==(逻辑相等),!=(逻辑不等)

移位运算符

  1. 左移符号“<<”例a<<2将a的值左移两位,用零补齐

条件语句

  1. if(<条件表达式1>)语句或语块1;else if(<条件表达式2>) 语块或语句2;
  2. case分支控制语句
    case(<控制表达式>)
        <分支语句1>:语块1;
        <分支语句2>:语块2;
        <分支语句3>:语块3;
        <分支语句n>:语块n;
    default :语句块n+1;
    endcase
    

系统函数

  1. verilog语言中预先定义了一些任务和函数,用于完成一些特殊的功能,被称为系统任务和系统函数
  • timecale lns/lns  //时间尺度预编译指令 时间单位/时间精度
  • 时间单位和时间精度由值1、10、100以及单位s、ms、us、ps、fs
  • 时间单位:定义仿真过程中所有与时间相关量的单位
  • 仿真中使用“#数字”表示延时相应时间单位的时间
  • 时间精度:决定时间相关量的精度及仿真显示的最小刻度

仿真中常用的函数

  • $display  //打印信息自动换行
  • $write  //打印信息
  • $strobe  //打印信息,自动换行,最后执行
  • $monitor  //监测变量
  • $stop  //停止仿真
  • $finish  //结束仿真
  • $time  //时间函数
  • $random  //随机函数
  • $readmemb  //读文件函数

1、$display("%b+%b=%d",a,b,c);//格式“%b+%b=%d”格式控制,未指定默认为十进制

'timescale lns/lns

module tb_test(); //模块开始 模块名

reg [3:0] a;
reg [3:0] b;
reg [3:0] c;  //三个reg型变量

initial begin  //只执行一次begin之间的程序
    $display("Hello");  //打印hello
    $display("world");
    a=4'd5;  
    b=4'd6;  //进行赋值
    c=a+b;
    #100;  //延时100个时间单位
    $display("%b+%b=%d",a,b,c);
end

endmodule

2、时间函数以及随机数的产生(每十秒产生一个随机数)

'timescale lns/lns
module tb_test();
reg [3:0] a;
always #10 a=$random;
initial $monitor("a=%d @time %d",a,$time);
endmodule

组合逻辑电路(数据选择器)

1、从电路的本质上来讲,组合逻辑电路的特点是输出信号只是当前时刻输入信号的函数,与其他时刻状态无关,无存储电路,也无反馈电路

2、多路选择器:是数据选择器的别称,在多路数据传送过程中,能够根据需要将其中任意一路选出来的电路,叫数据选择器,也称多路选择器或电路开关

module mux2_1    //模块名尽量与文件名保持一致
(
    input  wire  [0:0]  in_1,
    input  wire  [0:0]  in_2,//进行输入信号的编写,一位宽默认值,也可不写
    input  wire  [0:0]  sel, //选通信号
    output reg   [0:0]  out  //输出信号
);  //端口列表
always@(*)  //*为通配符,只要产生电平的变化都会引起函数的调用
    if(sel == 1'b1)  //如果选通信号输入为高电平
        out == in_1; //输出信号1
    else
        out == in_2; //输出信号2
endmodule

3、编译文件的编写

'timescale 1nm/1nm  //时间尺度设置
module tb_mux2_1();
reg  in_1;
reg  in_2;  
reg  sel;
wire out;  //设置变量
initial  //系统函数
    begin
        in_1 <= 1'b0;
        in_2 <= 1'b0;
        sel  <= 1'b0;  //对输入变量进行统一的赋初值值操作,赋值间不相互影响使用阻塞赋值
    end                //这一块相当于括号里对信号赋值
always #10 in_1 <= {$random} %2; //每10nm对信号1进行一次随机的赋值,值为1或0
always #10 in_2 <= {$random} %2;
always #10 sel  <= {$random} %2;

initial  //监测部分
    begin
        $timeformat(-9,0,"ns",6);  //-9代表的是单位为10的-9次方,也就是ns,0表示小数点后的位数
        $monitor("$time %t:in_1=%b in_2=%b sel=%b out=%b",$time,in_1,in_2,sel,out);//将变化的数值动态打印出来
    end

mux2_1  mux2_1_inst      //模块名 实例化名 进行模块的实例化
(
    · wire in_1(in_1),
    · wire in_2(in_2),
    · wire sel(sel), 
    · reg  out(out)  //将变量与信号进行链接
);
endmodule

三八译码器

1、使用if语句存在优先级的顺序,而case不存在优先级的问题

module decoder
(
    input wire in_1,
    input wire in_2,
    input wire in_3,
    output reg [7:0] out  //设置输入输出信号,位宽为8bit
);

always@(*)
    if({in_1,in_2,in_3} == 3'b000)  //将输入的值进行拼接判断
        out = 8'b0000_0001;
    else if({in_1,in_2,in_3} == 3'b001)
        out = 8'b0000_0010;
    else if({in_1,in_2,in_3} == 3'b010)
        out = 8'b0000_0100;
    else if({in_1,in_2,in_3} == 3'b011)
        out = 8'b0000_1000;
    else if({in_1,in_2,in_3} == 3'b100)
        out = 8'b0001_0000;
    else if({in_1,in_2,in_3} == 3'b101)
        out = 8'b0010_0000;
    else if({in_1,in_2,in_3} == 3'b110)
        out = 8'b0100_0000;
    else if({in_1,in_2,in_3} == 3'b111)
        out = 8'b1000_0000;
    else 
        out=8'0000_0001;
/*case({in_1,in_2,in_3})  //使用case语句的编写
    3'b000:out = 8'b0000_0001;
    3'b001:out = 8'b0000_0010;
    3'b010:out = 8'b0000_0100;
    3'b011:out = 8'b0000_1000;
    3'b100:out = 8'b0001_0000;
    3'b101:out = 8'b0010_0000;
    3'b110:out = 8'b0100_0000;
    3'b111:out = 8'b1000_0000;
    default out = 8'b0000_0001;
endcase*/
endmodule

2、仿真文件的编写(产生随机数值,与编写好的功能器件的输入输出进行连接)

'timescale 1nm/1nm
module tb_decoder ();

reg in_1;
reg in_2;
reg in_3;
wire [7:0] out;

initial
    begin
        in_1 <= 1'b0;
        in_2 <= 1'b0;
        in_3 <= 1'b0;
    end

always #10 in_1 <= {$random}%2;
always #10 in_2 <= {$random}%2;
always #10 in_3 <= {$random}%2;

decoder decoder_inst
(
    ·in_1(in_1),
    ·in_2(in_2),
    ·in_3(in_3),
    ·out (out)
);
endmodule

半加器

1、加法器分为半加器和全加器。半加器电路是指对两个输入数据相加,输出结果位和进位。全加器,除了两个加数外还要加上进位信号。

module half_adder
(
    input wire in_1,
    input wire in_2,
    output wire sum,
    output wire count
);

assign {count,sum} = in_1 + in_2;
endmodule

2、仿真文件的编写

'timescale 1nm/1nm
module tb_half_adder();

reg in_1;
reg in_2;
wire sum;
wire count;

initial
    begin
        in_1 <= 1'b0;
        in_2 <= 1'b0;
    end

always #10 in_1 <= {$random}%2;
always #10 in_2 <= {$random}%2;

 half_adder half_adder_inst
(
    ·in_1(in_1),
    ·in_2(in_2),
    ·sum(sum),
    ·count(count)
);
endmodule

全加器

1、层次化设计:数字电路中根据模块层次不同,有两种基本的结构设计方法:自底向上(bottom=up)和自顶向下(top-down)的设计方法

2、全加器使用两个半加器进行实,前一个半加器实现两个加数求和的运算得到结果位和进位位,后一个半加器的输入为前一个半加器的结果位和从上一级加法器输出的进位位。将两个半加器的进位信号进行或运算得到的输出为全加器的进位位

module full_adder
(
    input wire in_1,
    input wire in_2,
    input wire cin,
    output wire sum,
    output wire count
);

wire h0_sum;
wire h0_count;
wire h1_count;

half_adder half_adder_inst0  //对半加器进行两次实例化
(
    ·in_1(in_1),
    ·in_2(in_2),  //将输入信号in_1与in_2输入到第一个半加器
    ·sum(h0_sum),
    ·count(h0_count)
);

half_adder half_adder_inst1
(
    ·in_1(h0_sum),
    ·in_2(cin),
    ·sum(sum),
    ·count(h1_count)
);

assign count = (h0_count | h1_count);
endmodule

实例化

'timescale 1nm/1nm
module tb_full_adder();

reg in_1;
reg in_2;
reg cin;
wire sum;
wire count;

initial
    begin
        in_1 <= 1'b0;
        in_2 <= 1'b0;
        cin <= 1'b0;
    end

always #10 in_1 <= {$random}%2;
always #10 in_2 <= {$random}%2;
always #10 cin <= {$random}%2;

 half_adder half_adder_inst
(
    ·in_1(in_1),
    ·in_2(in_2),
    ·cin(cin),
    ·sum(sum),
    ·count(count)
);
endmodule

避免latch的产生

1、latch其实就是锁存器,是一种在异步电路系统中,对输入信号电平敏感的单元,用来存储信息。在同步时序逻辑电路中应尽量避免

2、锁存器在数据未锁存时,输出端的信号随输入信号变化,就像信号经过一个缓冲器,一旦锁存信号有效,则数据将会被锁存,输入信号不起作用。因此,锁存器也被称为透明锁存器,指的是不锁存时输出对于输入是透明的

3、危害:对毛刺敏感,占用更多的逻辑资源,不能异步复位,额外的延时,复杂的静态时序分析

4、几种产生latch的情况

  • 组合逻辑中的if-else语句中缺少else语句
  • 组合逻辑中case条件分支语句条件并未完全列举,且缺少default语句
  • 组合逻辑中输出变量赋值给自己

时序逻辑电路

1、设计电路时存在竞争冒险的危害,多使用时序逻辑电路进行消除

2、输出信号会有一个时钟周期的延迟,复位为同步复位时会有延时会在下一个上升沿起作用,恢复也是要延迟半个时钟周期生效。异步复位会在出现低电平时立即拉低输出信号,但恢复还是要在下一个上升沿生效

module flip_flop
(
    input wire sys_clk,  //系统时钟信号50MHz
    input wire sys_rst_n,  //复位信号,_n表示低电平有效
    input wire key_in,  //板子输入信号
    output reg led_out
);

always@(posedge sys_clk or negedge sys_rst_n)  //检测到时钟周期的上升沿和复位周期的下降沿
    if(sys_rst_n == 1'b0)
        led_out <= 1'b0;
    else
        led_out <= key_in;
endmodule

实例化

'timescale 1nm/1nm
module tb_flip_flop();

reg sys_clk;
reg sys_rst_n;
reg key_in;
wire led_out;

initial
    begin
        sys_clk = 1'b1;
        sys_rst_n <= 1'b0;
        key_in <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
        #210
        sys_rst_n <= 1'b0;
        #40
        sys_rst_n <= 1'b1;  //将复位信号延迟一段时间后进行反转,观察输出现象
    end
        
always #10 sys_clk = ~sys_clk;  //每十个时间单位进行时钟周期的反转
always #20 key_in <= {$random}%2;

flip_flop flip_flop_inst
(
    ·sys_clk(sys_clk),  
    ·sys_rst_n(sys_rst_n), 
    ·key_in(key_in),  
    ·led_out(led_out)
);
endmodule

按键消抖

1、

module key_filter
#(
    parameter CNT_MAX = 20'd999_999
)

(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire key_in,
    output reg key_flag
);

reg [19:0] cnt;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 20'd0;
    if else(key_in == 1'b1)
        cnt <= 0;
    else if(cnt == CNT_MAX)
        cnt <= CNT_MAX;
    else 
        cnt <= cnt+20'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else if(cnt == CNT_MAX-20'd1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;
endmodule

 仿真文件

'timescale 1nm/1nm
module tb_key_filter();
reg sys_clk;
reg sys_rst_n;
reg key_in;
reg [7:0] tb_cnt;  //使用计数器模拟按键被按下
wire key_flag;

initial
    begin 
        sys_clk = 1'b1;
        sys_rst_n = 1'b0;
        #20
        sys_rst_n = 1'b1;
    end

always #10 sys_clk = ~sys_clk;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tb_cnt <= 8'd0;
    else if(tb_cnt == 8'd249)
        tb_cnt <= 8'd0
    else
        tb_cnt <= tb_cnt + 8'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_in <= 1'b1;
    else if(((tb_cnt >= 8'd19) && (tb_cnt <= 8'd49)) || 
         ((tb_cnt >= 8'd149) && (tb_cnt <= 8'd199)))
        key_in <= {$random}%2;  //模拟抖动
    else if((tb_cnt <= 8'd19) && (tb_cnt >= 8'd199))
        key_in <= 1'b1;
    else
        key_in <= 1'b0;

key_filter
#(
    ·CNT_MAX (20'd24)
)
key_filter_inst
(
    ·sys_clk(sys_clk),
    ·sys_rst_n(sys_rst_n),
    ·key_in(key_in),
    ·key_flag(key_flag)
);
rndmodule

触摸按键控制LED

1、按键按下就会改变LED的开关状态

2、

module touch_ctrl_led
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire touch_key,
    output reg led
);

reg touch_key_1;
reg touch_key_2;
wire touch_flag;
//边沿检测
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            reg touch_key_1 <= 1'b1;
            reg touch_key_2 <= 1'b1;  //初始化两个寄存器的值
        end
    else
        begin
            reg touch_key_1 <= touch_key;  //将触发信号的波形存储在寄存器1
            reg touch_key_2 <= touch_key_1;//再将寄存器1的值赋给寄存器2,实现一个周期的时延
        end

assign touch_flag = ((touch_key_1 == 1'b0)&&(touch_key_2 == 1'b1));
//在寄存器1为低电平,2为高电平时对led的状态进行取反 
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led <= 1'b1;
    else if(touch_flag == 1'b1)
        led <= ~led;
    else
        led <= led;
endmodule

流水灯

1、

module water_led
#(
    parameter CNT_MAX = 25'd24_999_999
)

(
    input wire sys_clk,
    input wire sys_rst_n,
    output reg [3:0] led_out
);

reg [24:0] cnt;
reg cnt_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 25'd0;
    else if(cnt == CNT_MAX)
        cnt <= 25'd0;
    else
        cnt <= cnt+1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else if(cnt == CNT_MAX-1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out <= 4'b1110;
    else if((led_out == 4'b0111)&&(cnt_flag == 1'b1))
        led_out <= 4'b1110;
    else if(cnt_flag == 1'b1)
        led_out <= ~led_out;
        led_out <= <<led_out;
        led_out <= ~led_out;
    else 
        led_out <= led_out;
endmodule

呼吸灯

1、

均匀改变高低电平在一个周期内的占比不同来实现PWM波

module breath_led
#(
    parameter CNT_1US_MAX = 6'd49,
    parameter CNT_1MS_MAX = 10'd999,
    parameter CNT_1S_MAX = 10'd999
)

(
    input wire sys_clk,
    input wire sys_rst_n,
    output reg led_out
);

reg [9:0] cnt_1s;
reg [9:0] cnt_1ms;
reg [5:0] cnt_1us;  //三个计数器
reg cnt_en;  //使能变量用来调控是由亮到灭的过程还是逆过程

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_1us <= 6'd0;
    else if(cnt_1us == CNT_1US_MAX)
        cnt_1us <= 6'd0;
    else
        cnt_1us <= cnt_1us+6'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_1ms <= 10'd0;
    else if((cnt_1us == CNT_1US_MAX)&&(cnt_1ms == CNT_1MS_MAX))
        cnt_1ms <= 10'd0;
    else if(cnt_1us == CNT_1US_MAX)
        cnt_1ms <= cnt_1ms+10'd1;
    else
        cnt_1ms <= cnt_1ms

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_1s <= 10'd0;
    else if((cnt_1us == CNT_1US_MAX)&&(cnt_1s == CNT_1S_MAX)&&(cnt_1ms == CNT_1MS_MAX))
        cnt_1s <= 10'd0;
    else if((cnt_1us == CNT_1US_MAX)&&(cnt_1ms == CNT_1MS_MAX))
        cnt_1s <= cnt_1s+10'd1;
    else 
        cnt_1s <= cnt_1s;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_en <= 1'b0;
    else if((cnt_1us == CNT_1US_MAX)&&(cnt_1s == CNT_1S_MAX)&&(cnt_1ms == CNT_1MS_MAX))
        cnt_en <= ~cnt_en;
    else
        cnt_en <= cnt_en;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out <= 1'b1;
    else if((cnt_en == 1'b0)&&(cnt_1ms <= cnt_1s)) //改变输出高低电平的占比
        led_out <= 1'b0;
    else if((cnt_en == 1'b1)&&(cnt_1ms <= cnt_1s))
        led_out <= 1'b0;
    else
        led_out <= 1'b1;

endmodule

状态机

1、状态机简写为FSM,也称同步有限状态机,分为Moore型状态机、Mealy型状态机

2、可乐机,售价为三元,一次只能投一元

 独热码应用于高速系统,低速系统二进制用于小于四个状态,独热码用于四到二十四之间,以上的应用格雷码

module simple_fsm
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire po_money,
    output reg po_cola
);

parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;  //独热码
reg [2:0] state;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state <= IDLE;
    else case(state)
        IDLE:if(po_money == 1'b1)
                state <= ONE;
            else
                state <= state;
        ONE:if(po_money == 1'b1)
                state <= TWO;
            else
               state <= state;
        TWO:if(po_money == 1'b1)
                state <= IDLE;
            else
               state <= state;
        default:state <= IDLE;
    endcase

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else if((state == TWO)&&(po_money == 1'b1))
        po_cola <= 1'b1;
    else
        po_cola <= 1'b0;

endmodule

3、售价为2.5元,一次可以投一元或0.5元,可以找零

module complex_fsm
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire pi_money_half,
    input wire pi_money_one,
    output reg po_cola,
    output reg po_money
);

parameter IDLE = 5'b00001;
parameter HALF = 5'b00010;
parameter ONE = 5'b00100;
parameter ONE_HALF = 5'b01000;
parameter TWO = 5'b10000;
reg [4:0] state;
wire [1:0] pi_money;

assign pi_money = {pi_money_one,pi_money_half};

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state <= IDLE;
    else case(state)
            IDLE:if(pi_money == 2'b01)
                    state <= HALF;
                else if(pi_money == 2'b10)
                    state <= ONE;
                else
                    state <= IDLE;
            HALF:if(pi_money == 2'b01)
                    state <= ONE;
                else if(pi_money == 2'b10)
                    state <= ONE_HALF;
                else 
                    state <= state;
            ONE:if(pi_money == 2'b01)
                    state <= ONE_HALF;
                else if(pi_money == 2'b10)
                    state <= TWO;
                else 
                    state <= state;
            ONE_HALF:if(pi_money == 2'b01)
                    state <= TWO;
                else if(pi_money == 2'b10)
                    state <= IDLE;
                else 
                    state <= state;
            TWO:if((pi_money == 2'b01) || (pi_money == 2'b10))
                    state <= IDLE;
                else
                    state <= state;
            default:state <= IDLE;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else if((state == ONE_HALF)&&(pi_money == 2'b10) ||
            (state == TWO)&&(pi_money == 2'b01) ||
            (state == TWO)&&(pi_money == 2'b10))
        po_cola <= 1'b1;
    else
        po_cola <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_money <= 1'b0;
    else if((state == TWO)&&(pi_money == 2'b10))
        po_money <= 1'b1;
    else
        po_money<= 1'b0;

endmodule

蜂鸣器

1、分为电磁式蜂鸣器和电压式蜂鸣器两种。有源蜂鸣器内有集成电路,不需要音频驱动电路,只需要接通直流电源就能发出声音;无源蜂鸣器只有外加音频驱动信号才能发出声响,输入不同频率和占空比的PWM方波发出的声音是不同的。

2、占空比为50%,当信号小于占空比数值保持低电平,大于占空比数值的时候保持高电平

3、

module beef
#(
    parameter CNT_MAX = 25'd24_999_999,
    parameter DO = 18'd190839,
    parameter RE = 18'd170067,
    parameter MI = 18'd151514,
    parameter FA = 18'd143265,
    parameter SO = 18'd127550,
    parameter LA = 18'd113635,
    parameter XI = 18'd101213
)
(
    input wire sys_clk,
    input wire sys_rst_n,
    output reg beep
);

reg [24:0] cnt;  //0.5s的计数器
reg [2:0] cnt_500ms;  //没过0.5s记一次数
reg [17:0] freq_cnt;  //输出信号计数值
reg [17:0] freq_data;  //输出信号的时间值
wire [16:0] duty_data;  //占空比的值

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 25'd0;
    else if(cnt == CNT_MAX)
        cnt <= 25'd0;
    else
        cnt <= cnt+25'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_500ms <= 3'd0;
    else if((cnt == CNT_MAX)&&(cnt_500ms == 3'd6))
        cnt_500ms <= 3'd0;
    else if(cnt == CNT_MAX)
        cnt_500ms <= cnt_500ms+3'd1;
    else
        cnt_500ms <= cnt_500ms;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        freq_cnt <= 18'd0;
    else if((freq_cnt == freq_data)||(cnt == CNT_MAX))
        freq_cnt <= 18'd0;
    else
        freq_cnt <= freq_cnt + 1'b1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        freq_data <= DO;
    else case(cnt_500ms)
        3'd0:freq_data <= DO;
        3'd1:freq_data <= RE;
        3'd2:freq_data <= MI;
        3'd3:freq_data <= FA;
        3'd4:freq_data <= SO;
        3'd5:freq_data <= LA;
        3'd6:freq_data <= XI;
    default:freq_data <= DO;
endcase

assign duty_data == freq_data >> 1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        beep <= 1'b0;
    else if(freq_cnt >= duty_data)
        beep <= 1'b1;
    else
        beep <= 1'b0;

endmodule

数码管的静态显示

1、

  1. MR为移位寄存器的清零端,低电平有效
  2. DS串行数据的输入端
  3. SHCP为时钟输入,在上升沿时将数据转移到芯片,下一个上升沿到来时会将数据进行往下移位操作
  4. STCP存储寄存器时钟在上升沿时会将移位寄存器中的内容移到存储器当中
  5. OE为输出使能信号,为低电平时,将数据寄存器的内容从八个端口中传输出去
  6. Q7S将超过八位的数据输出

 

 1、静态数码管

使用两个74HC595进行级联,第一个模块输出位选信号和段选信号,输入到第二模块。第二片的输出对应的是位选信号的输出Q0对应的为[5],

module seg_satstic
#(
    parameter CNT_MAX = 25'd24_999_999
)
(
    input wire sys_clk,
    input wire sys_rst_n,
    output reg [5:0] sel, //选通信号,控制选择的数码管
    output reg [7:0] seg  //段选信号,控制点亮的数字图形
);

reg [24:0] cnt;  //0.5s计数器
reg [3:0] data;  //每0.5s进行一次计数,切换数码管的状态,一共有十五种状态,十六进制的1~F
reg cnt_flag;  //字符的切换信号

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 25'd0;
    else if(cnt == CNT_MAX)
        cnt <= 25'd0;
    else
        cnt <= cnt+25'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data <= 4'd0;
    else if((cnt_flag <= 1'b1;)&&(data == 4'd15))
        data <= 4'd0;
    else if(cnt == CNT_MAX)
        data <= data+4'd1;
    else
        data <= data;  //两个计数器的实现

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else if(cnt == CNT_MAX-25'd1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sel <= 6'b000_000;
    else
        sel <= 6'b111_111;  //使六个数码管同步显示,同时显示相同的字符或同时熄灭

always@(posedge sys_clk or negedge sys_rst_n)  //根据计数器的状态选择输出的字符
    if(sys_rst_n == 1'b0)
        seg <= 8'hff;
    else case(data)
        4'd0:seg <= 8'hc0;
        4'd1:seg <= 8'hf9;
        4'd2:seg <= 8'ha4;
        4'd3:seg <= 8'hb0;
        4'd4:seg <= 8'h99;
        4'd5:seg <= 8'h92;
        4'd6:seg <= 8'h82;
        4'd7:seg <= 8'hf8;
        4'd8:seg <= 8'h80;
        4'd9:seg <= 8'h90;
        4'd10:seg <= 8'h88;
        4'd11:seg <= 8'h83;
        4'd12:seg <= 8'hc6;
        4'd13:seg <= 8'ha1;
        4'd14:seg <= 8'h86;
        4'd15:seg <= 8'h8e;
     default:seg <= 8'hff;
endcase
endmodule

主模块的编写

module hc595_ctrl  //主模块
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire [5:0] sel,
    input wire [7:0] seg,
    output reg ds,    //数据传入控制
    output reg shcp,  //移位时钟
    output reg stcp,  //存储器时钟
    output wire oe     //输出使能信号
);

wire [13:0] state;  //用来将选通信号进行反转存储(因存在移位,先读入的数据会放在低位输出),在与片选信号拼接,还有寄存器数组存放数据的方式)得到十四位的数据
reg [1:0] cnt;      //四位计数器,控制每次信号显示的时长
reg [3:0] cnt_bit;  //用来遍历state中的数据

assign data = {seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel};

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 2'd0;
    else if(cnt == 2'd3)
        cnt <= 2'd0;
    else
        cnt <= cnt+2'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <= 4'd0;
    else if(cnt == 2'd3)&&(cnt_bit == 4'd13)
        cnt_bit <= 4'd0;
    else if(cnt == 2'd3)
        cnt_bit <= cnt_bit + 4'd1;
    else
        cnt_bit <= cnt_bit;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        ds <= 2'b0;
    else if(cnt == 2'd0)
        ds <= data[cnt_bit];  //每个周期的开始进行信号的逐位读入
    else
        ds <= ds;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        shcp <= 1'b0;
    else if(cnt == 2'd2)  //中间时刻进行采样,进行数据的左移
        shcp <= 1'b1;
    else if(cnt == 2'b0)
        shcp <= 1'b0;
    else
        shcp <= shcp;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        stcp <= 1'b0;
    else if((cnt_bit == 4'd0)&&(cnt == 2'd0))  //在十四位数据读取完成后,将数据输出到寄存器
        stcp <= 1'b1;
    else if((cnt_bit == 4'd0)&&(cnt == 2'd2))
        stcp <= 1'b0;
    else
        stcp <= stcp;

assign oe = 1'b0;  //使能信号始终有效,将寄存器中的数据进行传输
endmodule
module seg_559_stastic  //进行两个模块之间的关联
(
    input wire sys_clk,
    input wire sys_rst_n,
    output wire ds,
    output wire shcp,
    output wire stcp,
    output wire oe
);

wire [5:0] sel;
wire [7:0] seg;  //两个中间变量用来联系主从模块

seg_stastic
#(
    ·CNT_MAX (25'd24)
)
seg_stastic_inst
(
    ·sys_clk(sys_clk),
    ·sys_rst_n(sys_rst_n),
    ·sel(sel),
    ·seg(seg)
);

hc595_ctrl hc595_ctrl_inst
(
    ·sel(sel),
    ·seg(seg),
    ·sys_clk(sys_clk),
    ·sys_rst_n(sys_rst_n),
    ·ds(ds),
    ·shcp(shcp),
    ·stcp(stcp),
    ·oe(oe)   
);
endmodule

数码管的动态显示

1、根据人眼的视觉暂留现象,和晶体管的余晖效应(断电不会立即熄灭),将不同的段选信号进行快速的切换,以达到同时显示不同的数值的假象

 2、数据生成模块

module  data_gen
#(
    parameter CNT_MAX = 23'd4_999_999,
    parameter DATA_MAX = 20'd999_999,
)
(
    input wire sys_clk,
    input wire sys_rst_n,
    output reg [19:0] data,  //数据信号
    output wire [5:0] point,  //小数点位
    output wire sign,  //负号
    output reg seg_en  //使能信号
);

reg [22:0] cnt_100ms;  //0.1s计数器变量
reg cnt_flag;  //状态改变脉冲信号

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_100ms <= 23'd0;
    else if(cnt_100ms == CNT_MAX)
        cnt_100ms <= 23'd0;
    else
        cnt_100ms <= cnt_100ms + 23'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else if(cnt_100ms == CNT_MAX - 23'd1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data <= 20'd0;
    else if((data == DATA_MAX)&&(cnt_flag == 1'b1))
        data <= 20'd0;
    else if(cnt_flag == 1'b1)
        data <= data + 20'd1;
    else
        data <= data;

assign point = 6'b000_000;
assign sign = 1'b0;  //小数点位以及符号位不应用

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        seg_en <= 1'b0;
    else
        seg_en <= 1'b1;

endmodule

3、BCD码转换模块

将二进制数转换为十进制数,对输入的数据与初始的全0BCD码进行拼接,然后进行左移操作,BCD码中存在有位数转化为对应的十进制大于4的,就将对应的那一位BCD码进行加三操作,直到将二进制数据全部移入到BCD码区域。

module bcd_8421
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire [19:0] data,
    output reg [3:0] unit,
    output reg [3:0] ten,
    output reg [3:0] hun,
    output reg [3:0] tho,
    output reg [3:0] t_tho,
    output reg [3:0] h_hun
);

reg [4:0] cnt_shift;  //移位计数器
reg [44:0] data_shift;  //移位所用的数据,全0的BCD码与输入的二进制码进行的拼接
reg shift_flag;  //移位标志信号

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_shift <= 5'd0;
    else if((cnt_shift == 5'd21)&&(shift_flag ==1'b1))
        cnt_shift <= 5'd0;
    else if(shift_flag == 1'b1)
        cnt_shift <= cnt_shift + 1'b1;
    else
        cnt_shift <= cnt_shift

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_shift <= 44'd0;
    else if(cnt_shift == 5'd0)
        data_shift <= {24'b0,data};  //在移位计数器归0的时候进行数据的拼接
    else if((cnt_shift <= 20)&&(shift_flag == 1'b0))  //在移位标志信号低电平进行判断数据是否进行加四操作
      begin
        data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift [23:20] + 2'd3) :
        (data_shift [23:20]);
        data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift [27:24] + 2'd3) :
        (data_shift [27:24]);
        data_shift[31:28] <= (data_shift[31:28] > 4) ? (data_shift [31:28] + 2'd3) :
        (data_shift [31:28]);
        data_shift[35:32] <= (data_shift[35:32] > 4) ? (data_shift [35:32] + 2'd3) :
        (data_shift [35:32]);
        data_shift[39:36] <= (data_shift[39:36] > 4) ? (data_shift [39:36] + 2'd3) :
        (data_shift [39:36]);
        data_shift[43:40] <= (data_shift[43:40] > 4) ? (data_shift [43:40] + 2'd3) :
        (data_shift [43:40]);
      end
    else if((cnt_shift <= 20)&&(shift_flag == 1'b1)) //移位信号拉高时进行数据的移位操作
        data_shift <= data_shift << 1;
    else
        data_shift <= data_shift;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        shift_flag <= 1'b1;
    else
        shift_flag <= ~shift_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            unit <= 4'd0;
            ten <= 4'd0;
            hun <= 4'd0;
            tho <= 4'd0;
            t_tho <= 4'd0;
            t_hun <= 4'd0;
        end
    else
        begin
            unit <= data_shift[23:20];
            ten <= data_shift[27:24];
            hun <= data_shift[31:28];
            tho <= data_shift[35:32];
            t_tho <= data_shift[39:36];
            t_hun <= data_shift[43:40];
        end
endmodule

4、动态显示驱动模块

module seg_dynamic
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire [19:0] data,  //输入数据
    input wire [5:0] point,  //数码位小数点的显示
    input wire sign,  //符号位,为高电平的时候为负号
    input wire seg_en,  //使能信号
    output reg [5:0] sel,  //片选信号
    output reg [7:0] seg  //控制数码管输出状态(1~F)
);

parameter CNT_MAX = 16'd49_999;

wire [3:0] unit;
wire [3:0] ten;
wire [3:0] hun;
wire [3:0] tho;
wire [3:0] t_tho;
wire [3:0] t_hun;
reg [23:0] data_reg;  //整个需要显示的数据寄存
reg [15:0] cnt_1ms;  //1ms计数器,为晶体管进行切换的周期,每1ms进行切换显示
reg flag_1ms;
reg [2:0] cnt_sel;  //六个晶体管切换标志信号,0~5对应六个晶体管
reg [5:0] sel_reg;  //位选信号寄存器
reg [3:0] data_disp;  //六个数码管对应显示的,个位十位····(0~9)的值
reg dot_disp;  //小数点位

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        data_reg <= 24'd0;
    else if((h_hun) || (point[5]))
        data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
    else if(((t_tho)||(point[4]))&&(sign == 1'b1))
        data_reg <= {4'd10,t_tho,tho,hun,ten,unit};
    else if(((t_tho)||(point[4]))&&(sign == 1'b0))
        data_reg <= {4'd11,t_tho,tho,hun,ten,unit};  //11代表最高位不使用
    else if(((tho)||(point[3]))&&(sign == 1'b1))
        data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
    else if(((tho)||(point[3]))&&(sign == 1'b0))
        data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
    else if(((hun)||(point[2]))&&(sign == 1'b1))
        data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
    else if(((hun)||(point[2]))&&(sign == 1'b0))
        data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
    else if(((ten)||(point[1]))&&(sign == 1'b1))
        data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
    else if(((ten)||(point[1]))&&(sign == 1'b0))
        data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
    else if(((unit)||(point[0]))&&(sign == 1'b1))
        data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
    else if(((unit)||(point[0]))&&(sign == 1'b0))
        data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        cnt_1ms <= 16'd0;
    else if(cnt_1ms == CNT_MAX)
        cnt_1ms <= 16'd0;
    else
        cnt_1ms <= cnt_1ms + 16'd1;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        flag_1ms <= 1'b0;
    else if(cnt_1ms == CNT_MAX - 1'b1)
        flag_1ms <= 1'b1;
    else 
        flag_1ms <= 1'b0;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        cnt_sel <= 3'd0;
    else if((cnt_sel == 3'd5)&&(flag_1ms == 1'b1))
        cnt_sel <= 3'd0;
    else if(flag_1ms == 1'b1)
        cnt_sel <= cnt_sel + 1'b1;
    else
        cnt_sel <= cnt_sel;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        sel_reg <= 6'b000_000;
    else if((cnt_sel == 3'd0)&&(flag_1ms == 1'b1))
        sel_reg <= 6'b000_001;
    else if(flag_1ms == 1'b1)
        sel_reg <= sel_reg << 1;
    else
        sel_reg <= sel_reg;
        
always@(posedge sys_clk or negedge sys_rst_n)  //将灯与对应的显示数据进行关联
    if(sys_clk == 1'b0)
        data_disp <= 4'd0;
    else if ((seg_en == 1'b1)&&(flag_1ms == 1'b1))
        case(cnt_sel)
            3'd0:data_disp <= data_reg[3:0];
            3'd1:data_disp <= data_reg[7:4];
            3'd2:data_disp <= data_reg[11:8];
            3'd3:data_disp <= data_reg[15:12];
            3'd4:data_disp <= data_reg[19:16];
            3'd5:data_disp <= data_reg[23:20];
            default:data_disp <= 4'd0;
        endcase
    else
        data_disp <= data_disp;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        dot_disp <= 1'b1;
    else if(flag_1ms == 1'b1)
        dot_disp <= ~point[cnt_sel];
    else
        dot_disp <= dot_disp;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        seg <= 8'b111_111; //将所有的数码管至于熄灭状态
    else case(data_disp)  //将需要显示的数值用晶体管显示出来
        4'd0:seg <= {dot_disp,7'b100_0000};
        4'd1:seg <= {dot_disp,7'b111_1001};
        4'd2:seg <= {dot_disp,7'b010_0100};
        4'd3:seg <= {dot_disp,7'b011_0000};
        4'd4:seg <= {dot_disp,7'b001_1001};
        4'd5:seg <= {dot_disp,7'b001_0010};
        4'd6:seg <= {dot_disp,7'b000_0010};
        4'd7:seg <= {dot_disp,7'b111_1000};
        4'd8:seg <= {dot_disp,7'b000_0000};
        4'd9:seg <= {dot_disp,7'b001_0000};
        4'd10:seg <= 8'b1011_1111;  //10代表负号位使用
        4'd11:seg <= 8'b1111_1111;  //11代表最高位不使用
        default:seg <= 8'b1100_0000;
    endcase

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_clk == 1'b0)
        sel <= 6'b000_000;
    else
        sel <= sel_reg;
        
bcd_8421  bcd_8421_inst  //将8421码转换器进行实例化
(
    ·sys_clk(sys_clk),
    ·sys_rst_n(sys_rst_n),
    ·data(data),
    ·unit(unit),
    ·ten(ten),
    ·hun(hun),
    ·tho(tho),
    ·t_tho(t_tho),
    ·t_hun(t_hun)
);
endmodule

5、595数据处理模块整合

module seg_595_dynamic
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire [19:0] data,
    input wire [5:0] point,
    input wire sign,
    input wire seg_en,
    
    output reg ds,
    output reg oe,
    output reg shcp,
    output reg stcp
);

wire [5:0] sel;
wire [7:0] seg;

//实例化模块
seg_dynamic  seg_dynamic_inst
(
    ·sys_clk(sys_clk),
    ·sys_rst_n(sys_rst_n),
    ·data(data),  
    ·point(point),
    ·sign(sign),  
    ·seg_en(seg_en),  
    ·sel(sel),  
    ·seg(seg) 
);

hc595_ctrl  hc595_ctrl_inst
(
    ·sys_clk(sys_clk),
    ·sys_rst_n(sys_rst_n),
    ·sel(sel),
    ·seg(seg),
    ·ds(ds),  
    ·shcp(shcp),
    ·stcp(stcp),
    ·oe(oe)
);

endmodule

6、整体模块的整合

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值