【Verilog】 FPGA程序设计---Verilog基础知识

news2024/11/25 12:29:20

目录

 Verilog 和 VHDL 区别

Verilog 和 C 的区别

Verilog 基础知识

1  Verilog 的逻辑值

2  Verilog 的标识符

3  Verilog 的数字进制格式

4  Verilog 的数据类型

        1)     寄存器类型

        2)    线网类型

        3)    参数类型

5  Verilog 的运算符

        1)    算术运算符

        2)    关系运算符

        3)    逻辑运算符

        4)    条件运算符

        5)    位运算符

        6)    移位运算符

        7)    拼接运算符

        8)    运算符的优先级

Verilog 程序框架

1   注释

2   关键字

3   程序框架

Verilog 高级知识点

1   阻塞赋值(Blocking)

2   非阻塞赋值(Non-Blocking)

3   assign 和 always 区别

4   带时钟和不带时钟的 always

5    什么是 latch

6   状态机

        1)    Mealy 状态机

        2)    Moore 状态机

        3)    三段式状态机


 Verilog 和 VHDL 区别

        这两种语言都是用于数字电路系统设计的硬件描述语言,而且都已经是 IEEE 的标准。 VHDL 1987  成为标准,  Verilog  1995 才成为标准的。这是因为 VHDL 是美国军方组织开发的,而 Verilog 是由  一个公司的私有财产转化而来。为什么 Verilog 能成为 IEEE 标准呢?它一定有其独特的优越性才行, 所以  Verilog 有更强的生命力。

这两者有其共同的特点:

        1. 能形式化的抽象表示电路的行为和结构;

        2. 支持逻辑设计中层次与范围的描述;

        3. 可借用高级语言的精巧结构来简化电路行为和结构;

        4. 支持电路描述由高层到低层的综合转换;

        5. 硬件描述和实现工艺无关。

        但是两者也各有特点。Verilog 推出已经有 20 年了,拥有广泛的设计群体,成熟的资源,且 Verilog  易掌握,只要有 C 语言的编程基础,通过比较短的时间,经过一些实际的操作,可以在 1 个月左右掌握这种语言。而 VHDL 设计相对要难一点, 这个是因为 VHDL 不是很直观, 一般认为至少要半年以上的专业 培训才能掌握。

        近 10 年来,EDA 界一直在对数字逻辑设计中究竟用哪一种硬件描述语言争论不休,目前在美国,高 层次数字系统设计领域中,应用 Verilog  VHDL 的比率是 80%和 20%; 日本与中国台湾和美国差不多; 而在欧洲 VHDL 发展的比较好;在中国很多集成电路设计公司都采用 Verilog。我们推荐大家学习

 

Verilog 和 C 的区别

        Verilog 是硬件描述语言,在编译下载到 FPGA 之后, FPGA 会生成电路,所以 Verilog 全部是并行处理 与运行的; C 语言是软件语言, 编译下载到单片机/CPU 之后, 还是软件指令,而不会根据你的代码生成相 应的硬件电路,而单片机/CPU 处理软件指令需要取址、译码、执行, 是串行执行的。

        Verilog 和 C 的区别也是 FPGA 和单片机/CPU 的区别,由于 FPGA 全部并行处理, 所以处理速度非常 快,这个是 FPGA 的最大优势, 这一点是单片机/CPU 替代不了的。

Verilog 基础知识

1  Verilog 的逻辑值

        逻辑电路中有四种值,即四种状态:

        逻辑 0:表示低电平,也就是对应我们电路的 GND

        逻辑 1:表示高电平,也就是对应我们电路的 VCC

        逻辑 X:表示未知,有可能是高电平,也有可能是低电平;

        逻辑 Z:表示高阻态,外部没有激励信号是一个悬空状态。

        如下图所示:

              ​​​​    

2  Verilog 的标识符

        定义

        标识符(identifier)用于定义模块名、端口名和信号名等。 Verilog 的标识符可以是任意一组字母、数  字、 $_(下划线)符号的组合,但标识符的第一个字符必须是字母或者下划线。另外,标识符是区分大小 写的。以下是标识符的几个例子:

        Count

        COUNT //与 Count 不同。

        R56_68

        FIVE$

        虽然标识符写法很多,但是要简洁、清晰、易懂,推荐写法如下:

        count

        fifo_wr

        不建议大小写混合使用,普通内部信号建议全部小写,参数定义建议大写,另外信号命名最好体现信 号的含义。

        规范建议

        以下是一些书写规范的要求:

        1、用有意义的有效的名字如 sumcpu_addr 等。

        2、用下划线区分词语组合, 如 cpu_addr

        3、采用一些前缀或后缀,比如:时钟采用 clk 前缀: clk_50m clk_cpu;低电平采用_n 后缀: enable_n

        4、统一缩写,如全局复位信号 rst

        5、同一信号在不同层次保持一致性,如同一时钟信号必须在各模块保持一致。

        6、自定义的标识符不能与保留字(关键词)同名。

        7、参数统一采用大写,如定义参数使用 SIZE

3  Verilog 的数字进制格式

        Verilog 数字进制格式包括二进制、八进制、十进制和十六进制,一般常用的为二进制、 十进制和十六 进制。

        二进制表示如下: 4’b0101 表示 4 位二进制数字 0101

        十进制表示如下: 4’d2 表示 4 位十进制数字 2(二进制 0010);

        十六进制表示如下: 4’ha 表示 4 位十六进制数字 a(二进制 1010),十六进制的计数方式为 01 2…9 a b c d e f,最大计数为 ff:十进制表示 15)。

        当代码中没有指定数字的位宽与进制时,默认为 32 位的十进制,比如 100,实际上表示的值为 32’d100

4  Verilog 的数据类型

        在 Verilog 语法中,主要有三大类数据类型,即寄存器类型、线网类型和参数类型。从名称中,我们可 以看出,真正在数字电路中起作用的数据类型应该是寄存器类型和线网类型。

        1)     寄存器类型

        寄存器类型表示一个抽象的数据存储单元,它只能在 always 语句和 initial 语句中被赋值,并且它的值 从一个赋值到另一个赋值过程中被保存下来。

        如果该过程语句描述的是时序逻辑  always 语句带有时钟 信号,则该寄存器变量对应为寄存器;

        如果该过程语句描述的是组合逻辑,即 always 语句不带有时钟信号,则该寄存器变量对应为硬件连线; 寄存器类型的缺省值是 x(未知状态)。

        寄存器数据类型有很多种,如 reg 、integer、real 等, 其中最常用的就是 reg 类型, 它的使用方法如 下:

//reg define
reg  [31:0]  delay_cnt;    //延时计数器
reg          key_flag ;    //按键标志

        2)    线网类型

        线网表示 Verilog 结构化元件间的物理连线。它的值由驱动元件的值决定, 例如连续赋值或门的输出。 如果没有驱动元件连接到线网,线网的缺省值为 z(高阻态)。线网类型同寄存器类型一样也是有很多种,  tri  wire 等,其中最常用的就是 wire 类型, 它的使用方法如下:

//wire define
wire         data_en;         //数据使能信号
wire  [7:0]  data   ;         //数据

        3)    参数类型

        我们再来看下参数类型,参数其实就是一个常量,常被用于定义状态机的状态、数据位宽和延迟大小 等,由于它可以在编译时修改参数的值,因此它又常被用于一些参数可调的模块中,使用户在实例化模块 时,可以根据需要配置参数。在定义参数时,我们可以一次定义多个参数,参数与参数之间需要用逗号隔 开。这里我们需要注意的是参数的定义是局部的,只在当前模块中有效。它的使用方法如下:

//parameter define
parameter    DATA_WIDTH = 8;  //数据位宽为8位

5  Verilog 的运算符

        Verilog 中的运算符按照功能可以 分为下述类型: 1 、算术运算符、 2、关系运算符、3、逻辑运算符、 4、条件运算符、 5、位运算符、 6  移位运算符、 7、拼接运算符。下面我们分别对这些运算符进行介绍。

        1)    算术运算符

        算术运算符,简单来说,就是数学运算里面的加减乘除,数字逻辑处理有时候也需要进行数字运算, 所以需要算术运算符。常用的算术运算符主要包括加减乘除和模除(模除运算也叫取余运算)如下表所 示:

        符号                                        使用方法                                 说明

        +

        a + b

        a 加上 b

        -

        a - b

        a 减去 b

        *

        a * b

        a 乘以 b

        /

        a / b

        a 除以 b

        %

        a % b

        a 模除 b

        要注意下, Verilog 实现乘除比较浪费组合逻辑资源,尤其是除法。一般 2 的指数次幂的乘除法使 用移位运算来完成运算,详情可以看移位运算符章节。非 2 的指数次幂的乘除法一般是调用现成的 IP

        Quartus/Vivado 等工具软件会有提供, 不过这些工具软件提供的 IP 也是由最底层的组合逻辑(与或非门等) 搭建而成的。

        2)    关系运算符

        关系运算符主要是用来做一些条件判断用的,在进行关系运算符时,如果声明的关系是假的,则返回 值是 0,如果声明的关系是真的,则返回值是 1;所有的关系运算符有着相同的优先级别, 关系运算符的  优先级别低于算术运算符的优先级别如下表所示。

        符号                                       使用方法                               说明

        >

        a > b

a 大于 b

        <

        a < b

a 小于 b

        >=

        a >= b

a 大于等于 b

        <=

        a <= b

a 小于等于 b

        ==

        a == b

a 等于 b

        !=

        a != b

a 不等于 b

        3)    逻辑运算符

        逻辑运算符是连接多个关系表达式用的,可实现更加复杂的判断,一般不单独使用,都需要配合具体 语句来实现完整的意思,如下表所示。

符号                  使用方法                         说明

        !

        !a

a的非,如果a0 ,那么a的非是1

        &&

        a && b

a 与上 b,如果ab都为1 a&&b结果才为1 ,表示真。

        ||

        a || b

a 或上 b,如果a或者b有一个为1 a||b结果为1 ,表示真。

        4)    条件运算符

条件操作符一般来构建从两个输入中选择一个作为输出的条件选择结构, 功能等同于 always 中的 if- else 语句,如下表所示。

符号                  使用方法                                 说明

? :

a ? b : c

如果 a 为真, 就选择 b,否则选择 c

        5)    位运算符

        位运算符是一类最基本的运算符,可以认为它们直接对应数字逻辑中的与、或、非门等逻辑门。 常用 的位运算符如下表所示。

符号          使用方法         说明

~

~a

 a 的每个位进行取反

&

a & b

 a 的每个位与 b 相同的位进行相与

|

a | b

 a 的每个位与 b 相同的位进行相或

^

a ^ b

 a 的每个位与 b 相同的位进行异或

        位运算符的与、或、非与逻辑运算符逻辑与、逻辑或、逻辑非使用时候容易混淆, 逻辑运算符一般用 在条件判断上,位运算符一般用在信号赋值上。

        6)    移位运算符

        移位运算符包括左移位运算符和右移位运算符, 这两种移位运算符都用 0 来填补移出的空位。如下表 所示。

符号

使用方法

说明

<<

a << b

 a 左移 b

>>

a >> b

 a 右移 b

        假设 a  8bit 数据位宽,那么 a<<2,表示 a 左移 2bit a 还是 8bit 数据位宽,a 的最高 2bit 数据被移位 丢弃了,最低 2bit 数据固定补 0 。如果 a  3(二进制: 00000011),那么 3  2bit 3<<2 ,就是 12(二 进制: 00001100)。一般使用左移位运算代替乘法,右移位运算代替除法,但是这种也只能表示 2 的指数  次幂的乘除法。

        7)    拼接运算符

        Verilog 中有一个特殊的运算符是 C 语言中没有的,就是位拼接运算符。用这个运算符可以把两个或多 个信号的某些位拼接起来进行运算操作。如下表所示。

符号

使用方法

说明

{}

{a,b}

 a  b 拼接起来, 作为一个新信号

        8)    运算符的优先级

        介绍完了这么多运算符,大家可能会想到究竟哪个运算符高,哪个运算符低。为了便于大家查看这些 运算符的优先级,我们将它们制作成了表格,如下表所示。

运算符               优先级

! ~

最高

*  / %

次高

+  -

从上到下

依次降低

<<  >>

<  <= > >=

==  != === !==

&

^           ^~

|

&&

||

次低

?

最低

Verilog 程序框架

1   注释

        Verilog HDL 中有两种注释的方式, 一种是以“/*”符号开始,“*/”结束,在两个符号之间的语句都 是注释语句,因此可扩展到多行。如:

/* statement1 ,

statement2,

......

statementn */

        以上 n 个语句都是注释语句。

        另一种是以 // 开头的语句,它表示以//开始到本行结束都属于注释语句。如:

  //statement1

        建议的写法:使用 // 作为注释。

2   关键字

        Verilog 和 C 语言类似,都因编写需要定义了一系列保留字,叫做关键字(或关键词)。这些保留字是 识别语法的关键。我们给大家列出了 Verilog 中的关键字,如下表所示。

and

always

assign

begin

buf

bufif0

bufif1

case

casex

casez

cmos

deassign

default

defparam

disable

edge

else

end

endcase

endfunction

endprimitive

endmodule

endspecify

endtable

endtask

event

for

force

forever

fork

function

highz0

highz1

if

ifnone

initial

inout

input

integer

join

large

macromodule

medium

module

nand

negedge

nor

not

notif0

notif1

nmos

or

output

parameter

pmos

posedge

primitive

pulldown

pullup

pull0

pull1

rcmos

real

realtime

reg

release

repeat

rnmos

rpmos

rtran

rtranif0

rtranif1

scalared

small

specify

specparam

strength

strong0

strong1

supply0

supply1

table

task

tran

tranif0

tranif1

time

tri

triand

trior

trireg

tri0

tri1

vectored

wait

wand

weak0

weak1

while

wire

wor

xnor

xor

        虽然上表列了很多, 但是实际经常使用的不是很多, 实际经常使用的主要如下表所示。

关键字

含义

module

模块开始定义

input

输入端口定义

output

输出端口定义

inout

双向端口定义

parameter

信号的参数定义

wire

wire信号定义

reg

reg信号定义

always

产生reg信号语句的关键字

assign

产生wire信号语句的关键字

begin

语句的起始标志

end

语句的结束标志

posedge/negedge

时序电路的标志

case

Case语句起始标记

default

Case语句的默认分支标志

endcase

Case语句结束标记

if

if/else语句标记

else

if/else语句标记

for

for语句标记

endmodule

模块结束定义

        注意只有小写的关键字才是保留字。例如,标识符 always(这是个关键词)与标识符 ALWAYS(非关键 )是不同的。

3   程序框架

       以 LED 流水灯程序为例来给大家展示 Verilog 的程序框架,代码如下所示(注意:代码中前面的 行号只是为了方便大家阅读代码与快速定位到行号的位置,在实际编写代码时不可以添加行号,否则编译 代码时会报错)。

1 module led(
2 input sys_clk , //系统时钟
3 input sys_rst_n, //系统复位,低电平有效
4 output reg [3:0] led //4 位 LED 灯
5 );
6 
7 //parameter define
8 parameter WIDTH = 25 ;
9 parameter COUNT_MAX = 25_000_000; //板载 50M 时钟=20ns,0.5s/20ns=25000000,需要 25bit
10 //位宽
11
12 //reg define
13 reg [WIDTH-1:0] counter ;
14 reg [1:0] led_ctrl_cnt;
15
16 //wire define
17 wire counter_en ;
18
19 //***********************************************************************************
20 //** main code
21 //***********************************************************************************
22
23 //计数到最大值时产生高电平使能信号
24 assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0; 
25 
26 //用于产生 0.5 秒使能信号的计数器
27 always @(posedge sys_clk or negedge sys_rst_n) begin
28 if (sys_rst_n == 1'b0)
29 counter <= 1'b0;
30 else if (counter_en)
31 counter <= 1'b0;
32 else
33 counter <= counter + 1'b1;
34 end
35
36 //led 流水控制计数器
37 always @(posedge sys_clk or negedge sys_rst_n) begin
38 if (sys_rst_n == 1'b0)
39 led_ctrl_cnt <= 2'b0;
40 else if (counter_en)
41 led_ctrl_cnt <= led_ctrl_cnt + 2'b1;
42 end
43
44 //通过控制 IO 口的高低电平实现发光二极管的亮灭
45 always @(posedge sys_clk or negedge sys_rst_n) begin
46 if (sys_rst_n == 1'b0)
47 led <= 4'b0;
48 else begin
49 case (led_ctrl_cnt) 
50 2'd0 : led <= 4'b0001;
51 2'd1 : led <= 4'b0010;
52 2'd2 : led <= 4'b0100;
53 2'd3 : led <= 4'b1000;
54 default : ;
55 endcase
56 end
57 end
58
59 endmodule
        首先// 开头的都是注释,这个之前我们讲解过了。下面我们来看下具体的解释。
        第 1 行为模块定义,模块定义以 module 开始, endmodule 结束,如 59 行所示。
        其次 2 5 行为端口定义,需要定义 led 模块的输入信号和输出信号,此处输入信号为系统时钟和复 位信号,输出为 led 控制信号。
        7 到 9 行为参数 parameter 定义,语法如 7 9 行所示,定义 parameter 的好处是可以灵活改变参数数 字就能控制一些计数器最大计数值或者信号位宽的最大位宽。
        12 到 14 行为 reg 信号定义, reg 信号一般情况下代表寄存器,比如此处控制 0.5 秒使能信号的计数器 counter。
        16 到 17 行为 wire 信号定义, wire 信号就是硬件连线,比如此处的 counter_en ,代表计数到最大值时 产生高电平使能,本质上是一个硬件连线,其实代表的是一些计数器/ 寄存器做逻辑判断的结果。
        19 到 21 行为 moudle 开始的注释,不添加工具综合也不会报错,但是我们推荐添加,作为一个良好的 编程规范。
        23 到 24 行为 assign 语句的样式,条件成立选择 1 ,否则选择 0
        26 到 34 行是 always 语句的样式, 27 行代表在时钟上升沿或者复位的下降沿进行信号触发。 begin/end 代表语句的开始和结束。28 33 行为 if/else 语句,和 C 语言是比较类似的。 29 行的“ <=” 标记代表信号 是非阻塞赋值,信号赋值有非阻塞赋值和阻塞赋值两个方式,这个我们后面会详细解释。
        36 和 42 行也是一个 always 语句,和 26 34 行类似。
        44 和 57 行也是一个 always 语句,不过这个 always 语句中嵌入了一个 case 语句, case 语句的语法如
        49 到 55 行所示,需要一个 case 关键字开始, endcase 关键字结束, default 作为默认分支,和 C 语言也是类似的。当然 case 语句也可以用在不带时钟的 always 语句中,不过本例子的 always 都是带有时钟的。不带 时钟的 always 和带时钟的 always 语句的差异这个我们后面也会详细解释。
        59 行是 endmodule 标记,代表模块的结束。
        在这里需要补充一点的是,一些初学者可能会有这样一个疑问,在 always 语句中编写 if 语句或 else 语 句时,后面需要加 begin end 吗?其实这个主要看 if 条件后面跟着几条赋值语句,如果只有一条赋值语 句时,if 后面可以加 begin end ,也可以不加;如果超过一条赋值语句时,就必须加上 begin end
        if 条件只有一条赋值语句时,下面两种写法都是可以的,这里更推荐第一种写法,因为第二种写法会 占用更多的行号,代码如下所示:
if(en == 1'b1)
    a <= 1'b1;
        或者
if(en == 1'b1) begin
    a <= 1'b1;
end

        对于if条件超过一条赋值语句的情况, 必须添加begin和end,代码如下所示:

if(en == 1'b1) begin
    b <= 1'b1;
    c <= 1'b1;
end

Verilog 高级知识点

        前几节主要介绍了 Verilog一些基础的知识点和程序框架,本节给大家介绍一些高级的知识点。高级知 识点包括阻塞赋值和非阻塞赋值、 assign  always 语句差异、什么是锁存器、状态机、模块化设计等。

1   阻塞赋值(Blocking

        阻塞赋值, 顾名思义即在一个 always 块中,后面的语句会受到前语句的影响,具体来说就是在同一个 always 中, 一条阻塞赋值语句如果没有执行结束,那么该语句后面的语句就不能被执行,即被 阻塞 。也  就是说 always 块内的语句是一种顺序关系,这里和 C 语言很类似。符号“=”用于阻塞的赋值(如:b=a;),阻塞赋值“=”在 begin  end 间的语句是顺序执行,属于串行语句。

        在这里定义两个缩写:

        RHS:赋值等号右边的表达式或变量可以写作 RHS 表达式或 RHS 变量;

        LHS:赋值等号左边的表达式或变量可以写作 LHS 表达式或 LHS 变量;

        阻塞赋值的执行可以认为是只有一个步骤的操作,即计算 RHS 的值并更新 LHS,此时不允许任何其  他语句的干扰,所谓的阻塞的概念就是值在同一个 always 块中,其后面的赋值语句从概念上来讲是在前面 一条语句赋值完成后才执行的。

        为了方便大家理解阻塞赋值的概念以及阻塞赋值和非阻塞赋值的区别,我们这里以在时序逻辑下使用 阻塞赋值为例来实现这样一个功能:在复位的时候, a=1 b=2 c=3;而在没有复位的时候, a 的值清零, 同时将 a 的值赋值给 b b 的值赋值给 c,代码以及信号波形图如下图所示:

                

        代码中使用的是阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1b=2c=3;而 结束复位之后(波形图中的 0 时刻), clk 的上升沿到来时(波形图中的 2 时刻),a=0 b=0 c=0。这是 因为阻塞赋值是在当前语句执行完成之后,才会执行后面的赋值语句,因此首先执行的是 a=0,赋值完成 后将 a 的值赋值给 b,由于此时 a 的值已经为 0,所以 b=a=0,最后执行的是将 b 的值赋值给 c,而 b 的值  已经赋值为 0,所以 c 的值同样等于 0

2   非阻塞赋值(Non-Blocking

        符号“<=”用于非阻塞赋值(如:b <= a;),非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行 赋值语句右边,然后将 begin-end 之间的所有赋值语句同时赋值到赋值语句的左边,注意: 是 begin—end    之间的所有语句, 一起执行, 且一个时钟只执行一次,属于并行执行语句。这个是和 C 语言最大的一个差 异点,大家要逐步理解并行执行的概念。

        非阻塞赋值的操作过程可以看作两个步骤:

        (1)赋值开始的时候,计算 RHS

        (2)赋值结束的时候,更新 LHS

        所谓的非阻塞的概念是指,在计算非阻塞赋值的 RHS 以及 LHS 期间,允许其它的非阻塞赋值语句同 时计算 RHS 和更新 LHS

        我们下面使用非阻塞赋值同样来实现这样一个功能: 在复位的时候, a=1 b=2 c=3;而在没有复位 的时候, a 的值清零,同时将 a 的值赋值给 bb 的值赋值给 c,代码以及信号波形图如下图所示:

        代码中使用的是非阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1 b=2 c=3 而结束复位之后(波形图中的 0 时刻), clk 的上升沿到来时(波形图中的 2 时刻),a=0 b=1 c=2。这 是因为非阻塞赋值在计算 RHS 和更新 LHS 期间,允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS 在波形图中的 2 时刻,RHS 的表达是 0 a b,分别等于 0 1 2,这三条语句是同时更新 LHS,所以 a b c 的值分别等于 0 12

        在了解了阻塞赋值和非阻塞赋值的区别之后,有些朋友可能还是对什么时候使用阻塞赋值, 什么时候 使用非阻塞赋值有些疑惑,在这里给大家总结如下。

        在描述组合逻辑电路的时候, 使用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这 种电路结构只与输入电平的变化有关系,代码如下:

        示例1:assign赋值语句

assign  data  =  (data_en == 1'b1)  ?  8'd255  :  8'd0;

        示例2:不带时钟的always语句

always @(*) begin
    if (en) begin
        a = a0;
        b = b0;
    end
    else begin
        a = a1;
        b = b1;
    end
end

        在描述时序逻辑的时候,使用非阻塞赋值,综合成时序逻辑的电路结构, 比如带时钟的 always 语句; 这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化,代码如下:

        示例 3

always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        a <= 1'b0;
        b <= 1'b0;
    end
    else begin
        a <= c;
        b <= d;
    end
end

3   assign 和 always 区别

        assign 语句和 always 语句是 Verilog 中的两个基本语句,这两个都是经常使用的语句。

         assign 语句使用时不能带时钟。

        always 语句可以带时钟,也可以不带时钟。

        在 always 不带时钟时,逻辑功能和 assign 完全一致,都是 只产生组合逻辑。比较简单的组合逻辑推荐使用 assign 语句,比较复杂的组合逻辑推荐使用 always 语句。 示例如下:

  assign  counter_en = (counter == (COUNT_MAX - 1'b1))  ?  1'b1  :  1'b0;
  always @(*) begin
          case (led_ctrl_cnt)
              2'd0    : led = 4'b0001;
              2'd1    : led = 4'b0010;
              2'd2    : led = 4'b0100;
              2'd3    : led = 4'b1000;
              default : led = 4'b0000;
          endcase
  end

4   带时钟和不带时钟的 always

        always 语句可以带时钟,也可以不带时钟。在 always 不带时钟时,逻辑功能和 assign 完全一致,虽然 产生的信号定义还是 reg 类型,但是该语句产生的还是组合逻辑。

  reg   [3:0] led;
  always @(*) begin
          case (led_ctrl_cnt)
              2'd0    : led = 4'b0001;
              2'd1    : led = 4'b0010;
              2'd2    : led = 4'b0100;
              2'd3    : led = 4'b1000;
              default : led = 4'b0000;
          endcase
  end

        在 always 带时钟信号时,这个逻辑语句才能产生真正的寄存器, 如下示例 counter 就是真正的寄存 器。

  //用于产生 0.5 秒使能信号的计数器
  always @(posedge sys_clk or negedge sys_rst_n) begin
      if (sys_rst_n == 1'b0)
          counter <= 1'b0;
      else if (counter_en)
          counter <= 1'b0;
      else
          counter <= counter + 1'b1;
  end

5    什么是 latch

        latch 是指锁存器,是一种对脉冲电平敏感的存储单元电路。锁存器和寄存器都是基本存储单元,锁存 器是电平触发的存储器,寄存器是边沿触发的存储器。两者的基本功能是一样的, 都可以存储数据。锁存 器是组合逻辑产生的,而寄存器是在时序电路中使用,由时钟触发产生的。

        latch 的主要危害是会产生毛刺(glitch),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不 易查出。因此,在设计中,应尽量避免 latch 的使用。

        代码里面出现 latch 的两个原因是在组合逻辑中, if 或者 case 语句不完整的描述,比如 if 缺少 else   支, case 缺少 default 分支,导致代码在综合过程中出现了 latch。解决办法就是 if 必须带 else 分支, case  须带 default 分支。

        大家需要注意下,只有不带时钟的 always 语句 if 或者 case 语句不完整才会产 latch ,带时钟的语句 if 或者 case 语句不完整描述不会产生 latch

        下面为缺少 else 分支的带时钟的 always 语句和不带时钟的 always 语句, 通过实际产生的电路图可以 看到第二个是有一个 latch 的,第一个仍然是普通的带有时钟的寄存器。

6   状态机

        Verilog 是硬件描述语言,硬件电路是并行执行的, 当需要按照流程或者步骤来完成某个功能时,代码 中通常会使用很多个 if 嵌套语句来实现,这样就增加了代码的复杂度,以及降低了代码的可读性,这个时 候就可以使用状态机来编写代码。 状态机相当于一个控制器,它将一项功能的完成分解为若干步,每一步 对应于二进制的一个状态,通过预先设计的顺序在各状态之间进行转换, 状态转换的过程就是实现逻辑功 能的过程。

        状态机,全称是有限状态机(Finite State Machine,缩写为 FSM),是一种在有限个状态之间按一定规 律转换的时序电路, 可以认为是组合逻辑和时序逻辑的一种组合。状态机通过控制各个状态的跳转来控制 流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显,因此基本上都会用 到状态机,  SDRAM 控制器等。在本手册提供的例程中,会有多个用到状态机设计的例子,希望大家能 够慢慢体会和理解, 并且能够熟练掌握。

        根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒 (Mealy)型状态机。

            Mealy 状态机:组合逻辑的输出不仅取决于当前状态,还取决于输入状态。

            Moore 状态机:组合逻辑的输出只取决于当前状态。

        1)    Mealy 状态机

        米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F F 是当前状态和  输入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由 一组触发器组成,用来记忆状态机当前所处的状态, 状态的改变只发生在时钟的跳边沿;第三个框图是指 产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的, G 也是当前状态和输入信号的函数。

        2)    Moore 状态机

        摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当 前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。

        3)    三段式状态机

        根据状态机的实际写法,状态机还可以分为一段式、二段式和三段式状态机。

        一段式:整个状态机写到一个 always 模块里面, 在该模块中既描述状态转移,又描述状态的输入和输 出。不推荐采用这种状态机, 因为从代码风格方面来讲, 一般都会要求把组合逻辑和时序逻辑分开; 从代 码维护和升级来说, 组合逻辑和时序逻辑混合在一起不利于代码维护和修改,也不利于约束。

        二段式: 用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一  个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要 定义两个状态,现态和次态, 然后通过现态和次态的转换来实现时序逻辑。

        三段式:在两个 always 模块描述方法基础上,使用三个 always 模块, 一个 always 模块采用同步时序 描述状态转移, 一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律, 另一个 always 模块描 述状态输出(可以用组合电路输出, 也可以时序电路输出)

        实际应用中三段式状态机使用最多, 因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优 化以及程序的维护; 并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了 代码的可读性, 推荐大家使用三段式状态机, 本文也着重讲解三段式。

        三段式状态机的基本格式是:

        第一个 always 语句实现同步状态跳转;

        第二个 always 语句采用组合逻辑判断状态转移条件;

        第三个 always 语句描述状态输出(可以用组合电路输出,也可以时序电路输出)

        在开始编写状态机代码之前, 一般先画出状态跳转图,这样在编写代码时思路会比较清晰, 下面以一  7 分频为例(对于分频等较简单的功能,可以不使用状态机, 这里只是演示状态机编写的方法),状态  跳转图如下图所示:

        状态跳转图画完之后,接下来通过 parameter 来定义各个不同状态的参数,如下代码所示:

parameter S0 = 7'b0000001; //独热码定义方式
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;

        这里是使用独热码的方式来定义状态机,每个状态只有一位为 1,当然也可以直接定义成十进制的 0 1 2……7

        因为我们定义成独热码的方式,每一个状态的位宽为 7 ,接下来还需要定义两个 7 位的寄存器, 一 个用来表示当前状态,另一个用来表示下一个状态, 如下所示:

reg [6:0] curr_st ; //当前状态
reg [6:0] next_st ; //下一个状态

        接下来就可以使用三个 always 语句来开始编写状态机的代码,第一个 always 采用同步时序描述状态 转移,第二个 always 采用组合逻辑判断状态转移条件,第三个 always 是描述状态输出,一个完整的三段 式状态机的例子如下代码所示:

1 module divider7_fsm (
2 //系统时钟与复位
3 input sys_clk ,
4 input sys_rst_n ,
5 
6 //输出时钟
7 output reg clk_divide_7 
8 );
9 
10 //parameter define 
11 parameter S0 = 7'b0000001; //独热码定义方式
12 parameter S1 = 7'b0000010;
13 parameter S2 = 7'b0000100;
14 parameter S3 = 7'b0001000;
15 parameter S4 = 7'b0010000;
16 parameter S5 = 7'b0100000;
17 parameter S6 = 7'b1000000; 
18 
19 //reg define 
20 reg [6:0] curr_st ; //当前状态
21 reg [6:0] next_st ; //下一个状态
22
23 //*****************************************************
24 //** main code
25 //***************************************************** 
26
27 //状态机的第一段采用同步时序描述状态转移
28 always @(posedge sys_clk or negedge sys_rst_n) begin
29 if (!sys_rst_n)
30 curr_st <= S0;
31 else
32 curr_st <= next_st;
33 end
34
35 //状态机的第二段采用组合逻辑判断状态转移条件
36 always @(*) begin
37 case (curr_st)
38 S0: next_st = S1;
39 S1: next_st = S2;
40 S2: next_st = S3;
41 S3: next_st = S4;
42 S4: next_st = S5;
43 S5: next_st = S6;
44 S6: next_st = S0;
45 default: next_st = S0;
46 endcase
47 end
48
49 //状态机的第三段描述状态输出(这里采用时序电路输出)
50 always @(posedge sys_clk or negedge sys_rst_n) begin
51 if (!sys_rst_n)
52 clk_divide_7 <= 1'b0;
53 else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3))
54 clk_divide_7 <= 1'b0;
55 else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6))
56 clk_divide_7 <= 1'b1; 
57 else
58 ;
59 end
60
61 endmodule

        在编写状态机代码时首先要定义状态变量(代码中的参数 S0~S6)与状态寄存器(curr_st next_st), 如代码中第 10 行至第 21 行所示;

        接下来使用三个 always 语句来实现三段状态机, 第一个 always 语句实现 同步状态跳转(如代码的第 27 至第 33 行所示),在复位的时候,当前状态处在 S0 状态,否则将下一个状  态赋值给当前状态;

         第二个 always 采用组合逻辑判断状态转移条件(如代码的第 35 行至第 47 行代码所示),这里每一个状态只保持一个时钟周期,也就是直接跳转到下一个状态,在实际应用中,一般根据输入的条件来判断是否跳转到其它状态或者停留在当前转态,最后在 case 语句后面增加一个 default 语句, 来 防止状态机处在异常的状态;

        第三个 always 输出分频后的时钟(如代码的第 49 至第 59 行代码所示),状  态机的第三段可以使用组合逻辑电路输出,也可以使用时序逻辑电路输出,一般推荐使用时序电路输出,  因为状态机的设计和其它设计一样,最好使用同步时序方式设计,以提高设计的稳定性,消除毛刺。

        从代码中可以看出, 输出的分频时钟 clk_divide_7 只与当前状态(curr_st)有关,而与输入状态无关,所以属于摩尔型状态机。状态机的第一段对应摩尔状态机模型的状态寄存器, 用来记忆状态机当前所 处的状态; 状态机的第二段对应摩尔状态机模型产生下一状态的组合逻辑 F;状态机的第三段对应摩尔状 态机产生输出的组合逻辑 G,因为采用时序电路输出有很大的优势,所以这里第三段状态机是由时序电路 输出的。

        状态机采用时序逻辑输出的状态机模型如下图所示:

        采用这种描述方法虽然代码结构复杂了一些,但是这样做的好处是可以有效地滤去组合逻辑输出的毛 刺,同时也可以更好的进行时序计算与约束,另外对于总线形式的输出信号来说, 容易使总线数据对齐, 减小总线数据间的偏移,从而降低接收端数据采样出错的频率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1288710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GOLAND搭建GIN框架以及基础框架搭建

创建GO环境文件夹 终端输入安装GIN go get -u github.com/gin-gonic/gin如果遇到超时错误 package golang.org/x/net/html: unrecognized import path "golang.org/x/net/html": https fetch: Get "https://golang.org/x/net/html?go-get1": dial tcp …

设计模式之GoF23介绍

深入探讨设计模式&#xff1a;构建可维护、可扩展的软件架构 一、设计模式的背景1.1 什么是设计模式1.2 设计模式的历史 二、设计模式的分类2.1 创建型模式2.2 结构型模式2.3 行为型模式 三、七大设计原则四、设计模式关系结论 :rocket: :rocket: :rocket: 在软件开发领域&…

关于大模型在文本分类上的尝试

文章目录 前言所做的尝试总结前言 总共25个类别,在BERT上的效果是48%,数据存在不平衡的情况,训练数据分布如下: 训练数据不多,4000左右 所做的尝试 1、基于 Qwen-14b-base 做Lora SFT,Loss忘记记录 准确率在68%左右 Lora配置 class LoraArguments:lora_r: int = 64…

马尔科夫决策过程(Markov Decision Process)揭秘

RL基本框架、MDP概念 MDP是强化学习的基础。MDP能建模一系列真实世界的问题&#xff0c;它在形式上描述了强化学习的框架。RL的交互过程就是通过MDP表示的。RL中Agent对Environment做出一个动作&#xff08;Action&#xff09;&#xff0c;Environment给Agent一个反馈&#xff…

leetcode:LCR 122. 路径加密(python3解法)

难度&#xff1a;简单 假定一段路径记作字符串 path&#xff0c;其中以 "." 作为分隔符。现需将路径加密&#xff0c;加密方法为将 path 中的分隔符替换为空格 " "&#xff0c;请返回加密后的字符串。 示例 1&#xff1a; 输入&#xff1a;path "a.a…

「Verilog学习笔记」无占空比要求的奇数分频

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1nsmodule odd_div ( input wire rst ,input wire clk_in,output wire clk_out5 ); //*************code***********//reg [1:0] data ;reg […

在耳机心率血氧健康检测中应用的穿戴心率血氧接收芯片

时代快速发展的今天&#xff0c;随着生活节奏的加快&#xff0c;工作压力的加大&#xff0c;越来越多的消费者开始关注到自身身体健康&#xff0c;加班熬夜生活不规律&#xff0c;很容易让我们的身体处于亚健康的状态&#xff0c;而心率和体温的变化&#xff0c;就能反应我们身…

Python源码30:海龟画图turtle画紫色的小熊

turtle模块是一个Python的标准库之一&#xff0c;它提供了一个基于Turtle graphics的绘图库。Turtle graphics是一种流行的绘图方式&#xff0c;它通过控制一个小海龟在屏幕上移动来绘制图形。 turtle模块可以让您轻松地创建和控制海龟图形&#xff0c;从而帮助您学习Python编…

预览控制;预见控制;预测控制;预观控制(preview control)

预演控制&#xff08;preview control&#xff09;作为一种新兴的控制方法&#xff0c;首次在轮式车辆中被提出。 参考文献&#xff1a; https://www.sciencedirect.com/science/article/pii/S0016003219300390https://www.sciencedirect.com/science/article/pii/S0016003219…

企企通供应链专家:拥抱智能化趋势,企业如何打造智慧供应链形成竞争优势?

智能时代&#xff0c;数字生产力正在逐步成为推动经济发展和社会进步的核心引擎。随着互联网技术的不断创新与发展&#xff0c;以5G、云计算、人工智能等数字技术为代表的先进生产工具&#xff0c;其价值和潜力日益凸显&#xff0c;智能化也逐渐成为企业未来竞争高地之一。 近日…

数字化转型如何落地?_光点科技

数字化转型是现代企业发展的关键环节&#xff0c;它不仅仅是技术的升级&#xff0c;更是企业文化、运营模式和市场战略的全面革新。一个成功的数字化转型能够为企业带来更高效率、更好的客户体验和更强的市场竞争力。那么&#xff0c;数字化转型如何落地呢&#xff1f; 确定转型…

医院预约挂号平台的设计与实现

摘 要 网络的空前发展给人们的工作和生活带来了极大的便利&#xff0c;信息技术已成为节约运营成本、提高工作效率的首选。相比之下&#xff0c;国内相当多的中小医院在医院预约工作中的手工工作比较保守&#xff0c;数据查询和存储成本都很高&#xff0c;但效率很低。为了使医…

组合总和II(回溯、去重)

40. 组合总和 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a…

git submodule 用法

子仓库启蒙 在根目录执行&#xff1a; git submodule add gitgitee.com:liaosp/dcat-admin-basic-interface.git想要子模块指定的分支&#xff1a; git submodule add -b dev <仓库地址> <子模块路径>这样相当于在根目录上添加了 .gitmodules 信息&#xff0c;相…

SystemWeaver—电子电气系统协同研发平台

背景概述 当前电子电气系统在汽车领域应用广泛&#xff0c;其设计整合了多门工程学科&#xff0c;也因系统的复杂性、关联性日益提升&#xff0c;需要其提供面向软件、硬件、网络、电气等多领域交织而导致的复杂系统解决方案。并且随着功能安全、AUTOSAR、SOA、以太网通讯等新要…

【23真题】211题质量极高!押题卷有4道!

今天分享的是23年陕西师范大学831的信号与系统试题及解析。 本套试卷难度分析&#xff1a;22年陕西师范大学831考研真题&#xff0c;我也发布过&#xff0c;若有需要&#xff0c;戳这里自取&#xff01;本套试题难度中等&#xff0c;考察的题目道道经典&#xff0c;第三题是课…

【C++】三大特性 --- 继承的详细讲解

目录 1.继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1定义格式 1.2.2继承关系和访问限定符 1.2.3继承基类成员访问方式的变化 2.基类和派生类对象赋值转换 3.继承中的作用域 4.派生类的默认成员函数 5.继承与友元 6.继承与静态成员 7.复杂的菱形继承及菱形虚…

python中各式各样的字典操作

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;字典&#xff08;Dictionary&#xff09;是一种强大而灵活的数据结构&#xff0c;它允许你存储和检索键值对。本文将深入探讨Python中各式各样的字典操作&#xff0c;包括基本操作、高级操…

Octave安装与教程

Octave是一种编程语言&#xff0c;旨在解决线性和非线性的数值计算问题。Octave为GNU项目下的开源软件&#xff0c;早期版本为命令行交互方式&#xff0c;4.0.0版本发布基于QT编写的GUI交互界面。Octave语法与Matlab语法非常接近&#xff0c;可以很容易的将matlab程序移植到Oct…

区块链optimism主网节点搭建

文章目录 官方参考资料编译环境搭建编译Optimism Monorepo编译op-geth 执行下载数据快照生成op-geth和op-node通信密钥op-geth执行脚本 op-node执行脚本 启动日志op-gethop-node 本文是按照官方参考资料基于源码的方式成功搭建optimism主网节点。 官方参考资料 源码&#xff1…