深圳市航顺芯片技术研发有限公司
直播中

番茄番茄

12年用户 637经验值
私信 关注
[问答]

如何去实现时序逻辑电路和组合逻辑电路的设计呢

Verilog程序模块的结构是由哪些部分组成的?
如何去实现时序逻辑电路和组合逻辑电路的设计呢?

回帖(1)

张玉珍

2021-11-3 14:28:41
一.基本语法

1.模块的结构

Verilog程序的基本设计单元是“模块”,模块完全定义在module 和endmodule关键字之间。 每个模块包含四个主要部分:模块声明,端口定义,数据类型说明和 逻辑功能描述。
module  <模块名> (                   <端口>                   <端口> ......);                 <端口定义>                 <数据类型说明>                 <逻辑功能描述>endmodule 举例是一个32位加法器的模块。
module add32(in1 ,in2,out);   //模块声明    input in1,in2;               // 端口定义    output out;    wire[31: 0] in1,in2,out;      // 数据类型说明    assign out = in1 + in2;        // 逻辑功能描述endmodule  
1.模块声明

模块声明包括 模块名,以及输入、输出端口列表。 格式如下:
module 模块名 (端口1,端口2,端口3 ....);
2.端口定义

说明模块端口的方向 (输入、输出、双向等),其格式如下:
input 端口1,端口2,端口3 ....;   //输入端口
output 端口1,端口2,端口3 ....;  //输出端口
inout 端口1,端口2,端口3 ....;  //双向端口
3.数据类型说明

对模块中所有用到的信号(包括端口信号、节点信号等)都必须进行数据类型的定义。
reg a ;wire[31:0] out;//对于端口,可以将数据类型说明与端口定义放在一条语句中完成。 于是上文代码module add32(in1 ,in2,out);   //模块声明    input wire[31: 0] in1,in2;               // 端口定义,数据类型说明在一条语句中完成    output wire[31: 0] out;    assign out = in1 + in2;        // 逻辑功能描述endmodule 对于端口,还可以将端口定义、数据类型说明都放在模块声明语句中,而不再放在模块内部,更为简便。
//将端口定义、数据类型说明放在模块声明中module add32(   input wire[31: 0] in1 ,                input wire[31: 0] in2,                output wire[31: 0] out);       assign out = in1 + in2;        // 逻辑功能描述endmodule 4.逻辑功能描述

模块中最核心的部分是逻辑功能描述,可以有多种方法在模块中描述和定义逻辑功能。 几种基本方法如下:

   用assign 连续赋值语句定义
  用always过程快定义
  调用元件(也称为元件例化)


2.语言要素及数据类型

2.1语言要素


1.空白符
包括 空格(b)、制表符(t)、换行符(n)和换页符
如果 空白符不出现在字符串中,则空白符被忽略。使得代码结构层次清晰,方便阅读与修改。

2. 注释符
有两种形式
(1)单行注释符:以 // 开始到本行结束,不允许续行
(2)多行注释符:以 / * 到 */ 结束
注意: 多行注释不允许嵌套,但单行注释可以在多行注释中使用。

3.关键字(keyword)
也称保留字 ,是专用词,用户不能随便使用,所有关键字都是小写的。
例如:initial

4. 标识符(identifier)
程序代码中对象的名字。用户自己设计的,区分大小写的,可以是任意字母,数字,“_”(下划线)和$(美元符号)的组合。
但是第一个字符必须是字母或者下划线,不能是数字或$
(以$开始的标识符是为系统任务和系统函数保留的)
例如:count ,  sum  ,a

5.转义标识符(escaped indentifier)
转义标识符以反斜线符号“”开头,以空白符结尾,可以包含任意字符
2021
#res*~
反斜线和结束空白符并不是转义标识符的一部分,也就是说,标识符din 和标识符din是相同的。
转义标识符与关键字并不完全相同。标识符initial 与关键字initial是不同的。

2.2 常量

程序运行过程中,其值不能被改变的量称为常量。(constant)
Verilog HDL 中主要有3中类型:整数、实数和字符串。(其中只有整数型常量是可以综合的。)

为了对电路精确建模,增加了两种逻辑状态(不区分大小写) z和x
Verilog HDL 中的4中逻辑值状态:
0:低电平,逻辑假
1:高电平,逻辑真
z:高阻态
x:不确定或未知的逻辑状态。

1 整数的表示:
+/-'
即 +/-<位宽>'<进制><数字>
size 为二进制数的位宽,base为进制,value是基于进制的数字序列。
进制的4中表示形式:二进制(b或B),八进制(O,o),十进制(d,D,或默认),十六进制(h,H)
十六进制中的a~f和z和x一样,不区分大小写。

书写和使用时注意:
(1)在较长的数字之间可以用下划线分开: 12‘b1111_1011_1010.
(2)当数字不说明位宽和进制时,默认为32位的十进制数。
17  //表示 十进制17
-9 //十进制 -9        
(3)如果没有定义位宽,位宽就是相应值中定义的位数
’h0111      //16位十六进制数16’h0000_0001_0001_0001
‘o321        //9位八进制数 9'o011_010_001
(4)x或z在二进制中代表1位x或z,八进制中代表3位x或z,以此类推 ,其代表的宽度取决于所用的进制。
12‘b0111xxxx          //等价于12'h7x
7’b101zzz               //等价于7'o5z
8'Bxxxx1111              //等价于8'HxF
(5)如果定义的位宽大于实际的位数,通常在左边填0补位,但如果最左边一位为z或x,就用z或x进行补位。
如果定义的位宽小于实际的位数,那左边的位被截掉。
2‘b0111                 //等价于 2’b11
(6)“?”是高阻态z的另一种表示符号。在数字的表示中“?”和z是完全等价,可以相互替代。
(7)整数可以带符号,而且正负号应放在最左边。负数通常表示为二进制补码的形式。
32‘d23            //表示十进制23
-6'd12             //表示十进制-12,用二进制表示为 110100,最高位为符号位。
(8)在位宽和“ ‘ “之间以及进制和数字之间允许出现空格,但在" ’ "和进制之间以及数字之间不允许出现空格。
3(b)‘h(b)101               /* 合法*/
3'(b) o101                 /* 不合法*/

2.实数
实数(real)就是浮点数,定义方式有两种:
(1)十进制格式
0.02   
2.3
(2)科学计数法
(e前面必须有数字,后面必须是整数)
4.2e3
2E-2
(3)转换方法
通过四舍五入,将实数转化为最接近的整数

3.字符串
字符串是由一对双引号括起来的字符序列。出现在双引号内的任意字符都将被作为字符串的一部分。

2.3 变量和数据类型

在程序运行中,其值可以变的量称为变量(variable)。变量应该由名字,占据一定的存储空间,在该存储空间内存放变量的值。
每种变量都有其在电路中的实际意义。数据类型被设计用来表述数字硬件电路中数据的存储和传输。
Verilog HDL语言中,根据赋值和对值的保持方式不同,将数据类型分为两大类:线网型(net)和变量型(wire)。
1.net型
该类型数据相当于硬件电路中的各种物理连接(节点和导线),用来连接各个模块以及输入输出。
其特点是输出值紧跟着输入值的变化而变化,没有电荷的保持作用(trireg除外)。
net型数据必须由驱动源驱动,有两种驱动方式
(1)在结构描述中将其连接到一个门元件或模块的输出端;
(2)用持续赋值语句assign对其进行赋值。

类型功能可综合性
wire,tri两种常见的net型变量可以
supply1,supply0
分别为电源逻辑(1)和地逻辑(0)可以
表格中展时的数据类型仅仅是可综合的,没有展时不可综合的,下同
2.variable型
variable型变量必须放在过程语句中(always,initial),通过过程赋值语句赋值。
被赋值的信号也必须定义成variable型
它并不意味着一定是对应硬件上的触发器或寄存器等存储单元,在综合器进行综合时,variable型变量会根据被赋值的具体情况来确定是映射为连线还是映射为存储单元(触发器或寄存器)
integer型变量
是整数寄存器,最常用的变量类型,这种寄存器中存储整数,常用于对循环控制变量的说明,例如用来表述循环次数等。

类型功能可综合性
reg常用的variable型变量可以
integer32位带符号整型变量可以
2.4 参数

参数型数据是被命名的常量,在仿真开始前对其赋值,在整个仿真过程中,其值保持不变,数据的具体类型是由被赋的值来决定的。
参数通常出现在模块的内部,用来定义状态机的状态,数据位宽,以及延时大小
用参数parameter 来定义符号常量,即用parameter 来定义一个标识符代表一个常量
parameter 参数名1  = 表达式1,参数名2 = 表达式2,参数名3 = 表达式3,.。。;
参数最大的特点是它可以在编译时被方便的修改,所以常用来对一些需要调整的数据建模,在模块实例化时根据需要进行配置。
parameter是在模块内部的局部定义,而   `define 是全局性的宏定义。
2.5 向量

1.标量与向量
位宽为1的变量称为标量,默认的位宽为1位

位宽大于1的变量称为向量 (包括net型和variable型)
向量通常通过位宽定义语法[m***:l***] 指令地址范围。
m***表示向量的最高有效位(most significant bit),l***表示向量的最低有效位(lease significant bit)
注意 m***和l***必须是常数值或者parameter,或者在编译时计算结果位常数的表达式,而且可以为任意的整数:整数,负数或0 (m***可以大于,等于甚至小于l***)
2.位选择和域选择
在向量中,可以指其中的某一位或者相邻若干位进行操作,这些指定的一位或者相邻位分别称为位选择和域选择

2.6 存储器

通过对reg型变量建立数组来实现寄存器建模。数组中每一个单元通过数组索引进行寻址。memory型数据是通过扩展reg型数据的地址范围来生成的。
memory型数据的定义格式如下:
reg[n-1:0]  存储器名[m-1,0];
或      reg[n-1:0]  存储器名[m,0];
其中[n-1:0]表示存储器的字长,定义了存储器中每一个存储单元的大小
[m,0]位存储器的容量,定义了该存储器由多少个寄存器。
reg data[7:0];             //8个1位寄存器组成的存储器data
reg[0:3] a;                  // a[0]是最高有效位 a[3]是最低有效位

注意:
reg[7:0] rega;            // 1个8位的寄存器
reg amem[7:0];              //8个1位寄存器的存储器
这是不同的
rega = 0;       // 合法
amem = 0;        //不合法
一个n位的寄存器可以在一条赋值语句里进行赋值,一个完整的存储器不可以
对存储器赋值时,只能对某一个单元进行赋值。      amem[0 ] = 1;
还有一种方法对存储器赋值,采用系统任务  (仅限于电路仿真)
$ readmemb(加载二进制值)
$readmemh (加载十六进制值)

2.7 运算符

1.1 算术运算符

+-
*/
%求余**乘方
1.2 逻辑运算符

&&||
1.3 位运算符
按位运损,每一位进行逻辑运算
1.4 关系运算符

<
>
<=
>=
1.5 等式运算符

==等于!=
===全等!==不全等
1.6 缩位运算符

&  与   ~& 与非
|  ~|或非
^   异或 ~^同或
   reg[3:0] a;
y = &a ;           //      相当于 y= ((a[0]&a[1])&a[2])&a[3];

1.7 移位运算符
<<   左移
   >>  右移
1.8 条件运算符
y = ctrl ? a : b;  //   如果ctrl = 1,y=a,否则y = b
1.9 位拼接运算符
{a,b,c}
{a[1],b[2],c[2:3]}         // 等同于{a[1],b[2],c[2],c[3]}
3. 基本语句

3.1 综合性设计语句
综合是指所设计的代码和指令能转化为具体的电路网表结构,在基于FPGA/CPLD的设计中,综合就是将Verilog HDL描述的行为级或功能级电路模型转化为RTL级功能块或门级电路网表的过程。 所有硬件描述语言都可以用于仿真,但可综合的语句通常只是其中的一个优化子集,不同综合器支持的HDL语句集是不一样的。

VerilogHDL可综合的行为语句
类型语句可综合性
过程语句initial

always
块语句begin-end

fork-join
赋值语句持续赋值语句assign

过程赋值语句<=,=
条件语句if-else

case
循环语句for

repeat

while

forever
编译向导语句`define

`include

`ifdef,`else,`endif
3.2 时间控制语句
时间控制可以对过程块中各条语句的执行时间进行控制。
1.延时控制
位行为语句的执行指定一个延时时间,程序执行到该语句会暂停下来,等待这个值规定的若干个时间单位,再执行后面的语句。
三种方式: 语句前延时,单独延时和语句内延时。
符号#是延时控制标识符。延时时间量可以是一个立即数、变量或表达式。
注意:
如果延时的值位x或者z,与零值等效;如果延时时间计算结果位负值,那么负值的二进制补码将被视为正数并作为延时值。
(1)语句前延时
#延时时间   语句;
#10 b=a;
assign #(2,3,4) a= b&c;
表示上升延时为2个时间单位,下降延时为3个时间单位,关闭延时为4个时间单位,输出变为x的延时取2、3、4中最小的,即2个时间单位。
(2)单独延时
begin
#10;
#10 b=a;
end
(3)语句内延时
结果 = #延时时间表达式;
A = #2 c&d;(先计算表达式结果,然后再进入延时等待,再对左边目标赋值)

2.事件控制(通常和always语句联合使用)
(1)边沿敏感事件
@(posedge clk)dout = din;
(2)电平敏感事件
使用关键字wait定义:   wait(事件)语句;
wait(en)dout = din;
3.3 过程语句
1.initial过程语句
(没有触发条件)仅执行一次,常用于仿真中的初始化
如果只有一条语句,begin-end可以省略,否则必须用
initial begin
语句1;
语句2;
end
2.always过程语句
always块中的语句是不断重复执行的,重要满足条件,always语句就执行
如果always过程语句中省略了敏感信号列表,则认为触发条件时钟被满足,always过程语句将无条件地循环下去
3.4 块语句
语句块是两条或两条以上的语句组成语法结构相当于一条语句的结构。两种:
(1)顺序块 begin-end
顺序块中的语句是按顺序执行的。
1.当前面一条语句执行完毕后,下一条语句才能开始执行。
2.顺序块中每条语句中的延时控制都是相对于前一条语句结束时刻的。
3.整个顺序块的执行时间等于其内部各条语句执行时间的总和。
(2)并行块 fork-join
并行块中的所有语句是并发执行的
特点:
1.块内各条语句中指定的延时控制都是相对于程序流程控制进入并行块的时刻的延时,也就是相对于并行块开始执行时刻的延时。
2.整个并行块的执行时间等于其内部执行时间最长语句所需要的执行时间。
(3)顺序块语句与并行块语句的混合使用
1.当串行块和并行块属于不同过程块时,串行块和并行块时并行执行的。
2.可以嵌套使用,遵守之前语句块的规则。
3.5 赋值语句
1.持续赋值语句
2.过程赋值语句
1)阻塞赋值方式
2)非阻塞赋值方式
3)阻塞赋值与非阻塞赋值
3.6条件语句
1.if语句
1.2.if-else语句
2.case语句
2.2casez和casex语句
3.7循环语句
1.for循环语句
2.repeat循环语句
3.while循环语句
4.forever循环语句
3.8任务与函数
verilog 程序设计中,对大型系统进行设计时,可以调用底层模块进行设计,还可以通过调用任务和函数进行设计。
1.任务
任务的关键字是:task和endtask       格式如下:
task 任务名; 端口定义; 数据类型声明; 局部变量定义;   begin    语句1;    语句2;    endendtasktask add1;    input a,b;    output f;    begin    f = a&b;    endendtask 2.函数
3.任务和函数的区别
3.9编译预处理语句
1.宏定义`define
2.文件包含`include
3.条件编译`ifdef-`else-`endif
4.时间标尺定义 `timescale
二. 描述方式与层级设计

1.1 结构描述方式

结构描述方式通过调用VerilogHDL内部的基本元件或设计好的模块完成设计的功能,结构描述侧重设计的功能模块由哪些具体的门或模块构成,它们又是怎样的连接关系。
(1) 调用内置门元件
(2)调用开关级元件来说明硬件电路的结构,电路由开关级元件组成
(3)调用模块,多层级结构电路的设计中,顶层模块调用底层模块来 说明硬件电路结构。
(4)用户自定义原语(也在门级),用户自己定义元件的功能。
1.2行为描述方式

对电路的功能进行抽象描述,其抽象程度高于结构描述方式。侧重于电路的行为,不考虑功能模块是哪些具体的门或开关组成的。
行为描述方式的标志是过程语句(always过程语句和initial过程语句)
1.3 数据流描述方式

主要是使用持续赋值语句,即assign语句,多用于描述组合逻辑电路。
1.4 混合描述方式

可以将结构描述方式、行为描述方式和数据流描述方式的混合设计。即在一个模块中采用其中两种或三种描述方式来完成设计。
混合描述方式的1位全加器
module full_add_1(    input wire a,    input wire b,    input wire c,    output reg sum,    output reg cout);reg y1,y2,y3;wire w;xor xor_1(w,a,b);            // 结构描述assign sum = w ^ c;            //数据流描述always@(a or b or c) begin   //行为描述    y1 = a&b;    y2 = b&c;    y3 = a&c;    cout = y1|y2|y3;    endendmodule 2.1 进程

行为模型的本质是进程。它能够完成设计实体中的某部分逻辑行为,实现某一功能。
一个进程可以看作一个独立的运行单元,它可以很简单,也可以很复杂,可以将数字系统的行为看作很多有机结合的进程的集合。
描述进程的基本方式如下:
(1) always 过程块。
(2)initial 过程块。
(3)assign赋值语句。
(4)门级元件的例化,如 xor xor_1(w,a,b);.
(5) 模块例化
进程的特点:
1)进程只有两种状态,执行和等待状态,进程是否进入执行状态,取决于是否满足特定的条件,如敏感变量是否发生变化。 一旦满足条件,进程即进入执行状态。执行完毕或遇到停止语句后停止执行。自动返回起始语句,进入等待状态
(2)进程一般由敏感信号的变化来启动
(3)各个进程之间通过信号线进行通信。多个进程之所以能同步并发运行,一个很重要的原因是可以利用进程之间的信号线进行通信和协调。
(4)一个进程只允许描述对应于一个时钟信号的同步时序逻辑
(5)进程之间是允许并发执行的。两个或更多个assign连续赋值语句、always过程语句、门级元件例化、模块例化等操作都是可以同时执行的,与其所处的位置无关。
3.1 层次设计


三. 组合逻辑电路设计

3.1 编码器和译码器


3.2 数据选择器

3.3加法器

加法运算是最基本的算术运算,无论是乘法、除法或减法还是复杂的FFT(fast Fourier transformation,快速傅里叶变换)等运算,最终都可以分解为加法运算来实现。
加法器有半加器和全加器之分,区别在于是否存在初始进位:
半加器只有两个操作数,没有初始进位;
全加器有初始进位,这个初始进位始终是1位的。
对于多位加法器,实现的常用方法有级联加法器,并行加法器和超前进位加法器。
1.1 半加器

1.2 全加器

1位全加器的输出逻辑关系:
sum =












co = ab+ac+bc = ab+(a+b)ci
采用行为描述方式描述全加器
module full_add(    input wire a,    input wire b,    input wire ci,    output reg sum,    output reg co);always@(a or b or ci) begin    {co,sum} = a+b+ci;endendmodule 1.3级联加法器

多位加法器可以通过级联1位加法器的方法实现,本级的进位输出作为下一级的进位输入。
例:采用级联方式描述8位加法器
module add_jl(    input wire[7:0] a,    input wire[7:0] b,    input wire ci,    output reg[7:0] sum,    output reg co);full_add u0(a[0],b[0],ci,sum[0],co1);full_add u1(a[1],b[1],co1,sum[1],co2);full_add u2(a[2],b[2],co2,sum[2],co3);full_add u3(a[3],b[3],co3,sum[3],co4);full_add u4(a[4],b[4],co4,sum[4],co5);full_add u5(a[5],b[5],co5,sum[5],co6);full_add u6(a[6],b[6],co6,sum[6],co7);full_add u7(a[7],b[7],co7,sum[7],co);endmodule 1.4超前进位加法器

级联加法器进位是逐级传递的,因此延时随着级数的增加而增加。为了加快加法器的运算过程,必须减小进位延时,超前进位链能有效地减小进位的延时。
sum =












co = ab+ac+bc = ab+(a+b)ci
用g= ab,p = a+b,co = g+p *ci
8位超前进位链:
c0 = ci
c1 = g0 + p0*c0 = g0 +p0*ci
c2 = g1 +p1*c1 = g1+p1(g0+p0*ci) = g1+p1*g0+p1*p0*ci
以此类推 可以看出各个进位彼此独立产生,不存在进位逐级传递的问题,因此减小了进位产生的延时。同理推出sum:
sum =











= (ab)















(a+b)















ci = g














p















ci
采用行为描述方式描述全加器

3.4 乘法器

乘法器的应用也非常广泛,特别是信号处理的应用上,如:通信上的各种滤波器
1.1移位相加乘法器

功能可以表示成一连串的移位加法,一般的微处理器就是这样执行乘法运算的,比较耗费芯片面积。
在乘数已知的情况下,可以采用这种设计方式
首先检查乘数的最低位,如果最低位是1,就复制一份被乘数到累加器,接着向高位前进一个位。如果这个高位是1,就再复制一份被乘数,并向左移动,将移位后的数传至累加器累加;
如果这个高位是0 就不做任何操作,只是向高位再进一位。
移位相加乘法器
module mult 1.2并行乘法器

是纯组合逻辑的乘法器,完全可以由基本逻辑门实现。1位与1位相乘,只需要1个与门实现。对于多位的也可以通过其功能表得到相应的表达式,并化简为乘积项之和的形式,利用与门和或门来实现。Verilog HDL语言有乘法运算符 * ,因此并行乘法器主要通过运用乘法运算符来实现的。
4位并行乘法器
module mult(    input wire[3:0] a,    input wire[3:0] b,    output wire[7:0] out);assign out = a*b;endmodule 3.5 其他组合逻辑电路


四. 时序逻辑电路设计

4.1触发器

触发器是构成时序逻辑电路的基本单元。能存储1位二进制码的逻辑电路,输出状态不仅和输入有关,而且和原来的输出状态也有关。
不同触发器逻辑功能不同,电路结构和触发方式也不同。
1.1RS触发器

基本RS触发器是由两个与非门的输入、输出端交叉连接构成。
结构描述方式
module RSff1(    input wire r,    input wire s,    output reg q,    output reg qn);    nand u1(q,s,qn),          u2(qn,r,q);endmodule 行为描述方式   if-else语句 或case语句
module RSff2(    input wire r,    input wire s,    output reg q,    output reg qn);always@(r or s) case({r,s})    //rs各种组合    2'b10: begin q<=0;qn<=1; end    2'b01: begin q<=0;qn<=1; end     2'b11: begin q<=q;qn<=qn; end     2'b00:  begin q<=x;qn<=x; endendcaseendmodule

1.2JK触发器

为了防止空翻的住从电路结构,主从JK触发器,是主从RS 触发器基础上稍加改动,解决了R=S=1输出不定值状态的问题,这种情况时输出翻转之前的输出。
1.3D触发器

最简单也是最常用的触发器,各种时序电路的基础
带有同步复位和置数功能的D触发器
module Dff(    input wire clk,    input wire rst,    input wire d,    input wire load,    output reg q);always@(posedge clk) begin    if(rst) q<=0;    else if(load)    q<=1;    else q<=d;endendmodule 1.4T触发器

两个JK触发器的输入端口连接在一起作为触发器的输入就构成了T触发器
作用是T=0时保持前一个状态,否则翻转
module Tff(    input wire clk,    input wrie t,    output reg q);    always@(posedge clk) begin    if(t==0)    q<=q;    else q<=~q;endendmodule 4.2 锁存器和寄存器

4.2.1 锁存器

锁存器的功能同触发器是相似的,但也有本质的区别。
触发器是在有效时钟沿到来时才发生作用,而锁存器的电平是敏感的,只要时钟信号有效,锁存器就起作用。
电平敏感的1位数据锁存器
module latch1(    input wire clk,    input wire d,    output reg q);    assign q = clk? d:q;endmodule 2.采用的是assign连续赋值语句来描述
//带有置数和复位功能的电平敏感的1位数据锁存器module latch2(    input wire clk,    input wire d,    input wire reset,    input wire load,    output reg q);    assign q = reset? 0:(load? 1:(clk?d:q));endmodule  
4.2.2 寄存器

在数字电话中,寄存器就是一种在某一特定信号(通常是时钟信号)的控制下存储一组二进制数据的时序逻辑电路。
寄存器一般由多个触发器连接起来,采用一个公共信号进行控制,同时各个触发器的数据端口仍然各自独立地接收数据。
通常分为两大类:
普通寄存器和移位寄存器
带有清零功能的8位数据寄存器
module reg_8(    input wire clk,    input wire clr,    input wire[7:0] in,    output reg[7:0] out);always@(posedge clk or posedge clr) begin    if(clr) out <= 0;    else out <= in;    endendmodule 4.3 移位寄存器

除了具有存储二进制数据的功能之外还具有移位功能的触发器组。
二进制的乘法和除法都可以通过移位操作结合加法操作来完成。
module shiftl(    input wire clk,    input wire rst,    input wire d,    input wire e,    output reg[7:0] q);always@(posedge clk) begin    if(rst)    q<=8'b0;    else if(s)    q<={q[6:0],in};    else     q<=q;endendmodule 4.4 分频器

4.5 计数器

数字电路中,计数器是一个典型的时序电路,逻辑功能是记忆时钟脉冲的具体个数。还可以用于时钟分频,信号定时,地址发生器和进行数字运算等。
按照计数器中触发器是否同时翻转来分类,分为同步计数器和异步计数器两种。
按照数字编码方式来分类,二进制、十进制、循环码计数器等。
1.1同步计数器

同步计数器是在同一时钟信号的控制下,构成计数器的各个触发器的状态同时发生变化的计数器。
1.2异步计数器

异步计数器是将低位计数器的输出作为高位计数器的时钟信号,这样一级一级串行连接起来便构成了一个异步计数器。
1.3 加减计数器

加减计数器可以实现加1或减1操作。
module count32(    input wire clk,    input wire rst,    input wire s,    output reg[31:0] out);always@( posedge clk) begin    if(rst) begin    out <= 32'h0;        if(s)            out <= out + 32'h1;    end else begin    out <= 32'hffffffff;        if(s)            out <= out - 32'h1;    endendendmodule  

4.6 其他时序逻辑电路

五. 有限状态机的设计

5.1 有限状态机概述

finite state machine FSM
设计技术是数字系统设计的重要组成部分,也是时序电路设计中经常采用的一种设计方式,适用于实现高效率、高可靠的控制模块,
在一些需要控制高速器件的场合

在数字电路中可以用状态图描述电路的状态转移过程,也可以通过有限状态机的方式来表示状态的转移过程。
特别是在RTL级设计中,可以将设计分为数据部分(数据通道)和控制部分(控制单元)。控制部分通常可以用有限状态机来实现。
在执行速度方面,有限状态机要优越于cpu,所以前者在数字系统设计中更为重要。

1.1状态机的分类

状态机是事物存在状态的一种综合描述。
状态机由状态寄存器和组合逻辑电路组成,寄存器用于存储状态,组合逻辑电路用于状态译码和产生输出信号。
将状态机归纳为4个要素,即现态,条件,动作和次态。
(1)现态:当前所处的状态
(2)条件: 有称为事件。当一个条件被满足时,将会触发一个动作,或执行一次状态的迁移。
(3)动作:在条件满足后执行,动作执行完毕后,可以转移到新的动作,也可以保持原状态。动作不是必需的。也可以不执行动作,直接转移到新的状态。
(4)次态:下一个状态,是条件满足后要转移的新状态。次态时相对于现态而言的。次态一旦被激活,就转变成新的现态了。

状态机可以分为 有限状态机和无限状态机

根据输出信号产生机理的不同,分为摩尔(Moor)和米里(Mealy)型。
摩尔型状态机的输出只是当前状态的函数,米里型状态机的输出则是当前状态和当前输入的函数。

米里型状态机的输出是输入变化后立即变化的,不依赖时钟信号的同步
摩尔型状态机在输入变化发生时,还必须等待时钟的到来,必须等状态发生变化时才导致输出的变化。
因此摩尔型状态机比米里型状态机多等待一个时钟周期。

根据状态机的转移是否受时钟的控制,又可分为同步状态及和异步状态机,实际应用中通常都设计成同步方式。
同步状态机是在时钟信号的触发下完成各个状态之间的转移,并产生相应的输出。

1.2有限状态机的状态转换图

状态转移图(state_Transition Graph ,STG)是一个有向图。图中带有标记的节点或顶点与时序状态机的状态一一对应。
1.3设计流程

(1)选择状态机的类型
实际相同功能的情况下,米里型状态机所需要的状态数要比摩尔型状态机少。
(2)画出状态转移图
(3)根据状态转移图,构建状态机的Verilog HDL模型。
这个过程要注意对敏感信号的选择,一般用case,if-else等语句来对状态机的转移进行描述。
(4)EDA工具仿真
5.2有限状态机的设计要点

1.1有限状态机的编码规则:

描述程序中必须包括一下几个方面:
(1)时钟信号:用于为有限状态机状态转换提供时钟信号
(2)状态复位:用于有限状态机任意状态复位转移
(3)状态变量:用于定义有限状态机描述的状态
(4)状态转换指定:用于有限状态机状态转换逻辑关系
(5)输出指定:用于有限状态机两状态转换结果

1.2 起始状态的选择

起始状态是电路复位后所处的状态

1.3状态编码

状态变量的编码主要是由顺序编码,格雷编码和一位热码编码等编码方式
(1)顺序编码:二进制编码(Binary coding)
用二进制来表示所有的状态,编码方式简单,使用的触发器数量少,剩余的非法状态也最少。
缺点是容易多个位同时发生变化,容易产生毛刺。引起逻辑错误。
000、001、010、011、100、101
(2)格雷编码(Gray code encoding)
很好的解决了顺序编码产生毛刺的问题。每次只有一位发生变化。
000、001、011、010、110、111
(3)一位热码编码(One-HOT encoding)
用n个触发器来实现n个状态的状态机。每一个状态都由一个触发器的状态来表示。
000001,000010,000100,001000,010000,100000   6个状态,需要6个触发器。

状态编码的方式:
(1)参数定义方式:
parameter s0 = 3'b000,  s1 = 3'b001  , s2 = 3'b011
case (state)
s0:...;
s1: ...;
...
(2)编译向导语句(`define)定义方式:
`define s0 3'b000               //定义 ,      结尾不加分号
`define s1 3'b001
`define s2 3'b011
case(state)
`s0: ...;
`s1: ...;                 / /调用,必须加“  ` ”
1.4状态转移的描述

用if-else 语句实现 或者 case语句
if-else语句最后需要一个单独的else语句结尾
case语句最后必须加上default分支语句,避免锁存器的产生。(后者相对更清晰一些)
5.3 有限状态机设计实例

1.1摩尔型状态机

5进制计数器
module count5_moor(        input wire clk,        input wire reset,        output reg cout,        output reg[2:0] out);reg[2:0] current;        parameter s0 = 3'b000,s1 = 3'b001,s2 = 3'b010,s3 = 3'b011,s4 = 3'b100;//????always@(posedge clk or negedge reset) begin        if(!reset) begin        out <= 0;        current <= s0;        cout <= 0;        end else         case(current)        s0: begin                out <= 1;                current <= s1;                cout <= 0;            end        s1: begin                out <= 2;                current <= s2;                cout <= 0;            end        s2: begin                out <= 3;                current <= s3;                cout <= 0;            end        s3: begin                out <= 4;                current <= s4;                cout <= 10;            end        s4: begin                out <= 0;                current <= s0;                cout <= 0;            end        default :current = s0;        endcase    endendmodule module count5_moor_tb;        reg clk;        reg reset;        wire cout;        wire[2:0] out;        integer i;initial begin        clk = 0;reset = 0;#5  clk = 1;#5  clk = 0;#5  clk = 1;#5  clk = 0;         #20 reset = 1;#5  clk = 1;#5  clk = 0;#5  clk = 1;#5  clk = 0;                #5  clk = 1;#5  clk = 0;        #5  clk = 1;#5  clk = 0;        endcount5_moor count5_moor0( .clk(clk),        .reset(reset),  .count(count), .out(out) );endmodule 110序列检测器
module detector_110_moor(        input wire clk,        input wire reset,        input wire in,        output reg out);        reg[1:0] current;        parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b10,s3 = 2'b11;always@(posedge clk or posedge reset) begin        if(reset) begin        current <= s0; end         else case(current)         s0: begin                if(in == 1'b1) current <=s1;                        else  current <= s0;            end        s1: begin                if(in == 1'b1) current <=s2;                        else  current <= s0;            end        s2: begin                if(in == 1'b1) current <=s2;                        else  current <= s3;            end        s3: begin                if(in == 1'b1) current <=s1;                        else  current <= s0;            end        default: current <= s0;        endcase        endalways@(current) begin        if (current == s3)                out <= 1'b1;        else out <= 1'b0;        endendmodule module detector_110_moor_tb;        reg clk;        reg reset;        reg in;        wire out;initial begin        clk = 0;        reset = 1;        #20 reset = 0;        #5 clk = 1; in = 1;        #5 clk = 0; in = 0;        #5 clk = 1; in = 1;        #5 clk = 0; in = 1;        #5 clk = 1; in = 0;        #5 clk = 0; in = 1;        #5 clk = 1; in = 1;        #5 clk = 0; in = 1;end        detector_110_moor detector_110_moor0( .clk(clk), .reset(reset), .in(in), .out(out));endmodule 1.2米里型状态机

110序列检测器
module detector_110_dealy(        input wire clk,        input wire reset,        input wire in,        output reg out);        reg[1:0] current;        parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b11;always@(posedge clk or posedge reset) begin        if(reset) begin        current <= s0; end         else case(current)         s0: begin                if(in == 1'b1) current <=s1;                        else  begin current <= s0;out <=0; end            end        s1: begin                if(in == 1'b1) current <=s2;                        else begin current <= s0;out <=0;end            end        s2: begin                if(in == 1'b1) current <=s2;                        else begin current <= s0;out <=1;end            end        default: current <= s0;        endcase        endendmodule

1.3状态机的描述方式

单进程描述方式
整个状态机写到一个always进程语句里,使用一个进程语句来描述有限状态机中的次态逻辑、状态寄存器、和输出逻辑
二进程描述方式

两个always进程语句来描述状态机。其中一个always进程语句来描述有限状态机的次态逻辑和状态寄存器和输出逻辑中的任意两个,另外一个always进程语句则用来描述有限状态机剩余的功能。
程序中用parameter和二进制数的方式定义状态。第一个always进程语句实现的是一个寄存器,第二个always语句是一个纯粹的组合快。
module detector_110_mealy(        input wire clk,        input wire reset,        input wire in,        output reg out);        reg[1:0] current;        parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b11;        always@(posedge clk or posedge reset) begin                if(reset) begin                current <= s0; end                 else case(current)                 s0: begin                        if(in == 1'b1) current <=s1;                                else  current <= s0;                      end                s1: begin                        if(in == 1'b1) current <=s2;                                else begin current <= s0;end                        end                s2: begin                        if(in == 1'b1) current <=s2;                                else begin current <= s0;end                        end                default: current <= s0;                endcase        end        always@(current or in) begin                if((current == s2)&(in == 1'b0))                        out <= 1'b1;                else out <= 1'b0;        endendmodule module detector_110_mealy(        input wire clk,        input wire reset,        input wire in,        output reg out);        reg[1:0] p_state,n_state;        parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b11;        always@(posedge clk) begin                if(reset)                        p_state <=s0;                else p_state <= n_state;        end        always@(p_state or in ) begin                 case(p_state)                 s0: begin                        if(in == 1'b1) n_state <=s1;                                else begin n_state <= s0;out = 1'b0; end                      end                s1: begin                        if(in == 1'b1) n_state <=s2;                                else begin n_state <= s0;out = 1'b0;end                        end                s2: begin                        if(in == 1'b1) n_state <=s2;                                else begin n_state <= s0;out = 1'b1;end                        end                default: n_state <= s0;                endcase        end        endmodule 第一个always语句用来描述有限状态机的次态逻辑。第二个always语句用来描述状态寄存器和输出逻辑。

三进程描述方式
三个进程语句来描述有限状态机的功能,
分别描述状态机的
次态逻辑
状态寄存器
和输出逻辑(可以组合逻辑输出,也可以时序逻辑输出)
module detector_110_three_Mealy(        input wire clk,        input wire reset,        input wire in,        output reg out);        reg[1:0] p_state,n_state;        parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b11;        always@(posedge clk) begin                if(reset)                        p_state <=s0;                else p_state <= n_state;        end        always@(p_state or in ) begin                 case(p_state)                 s0: begin                        if(in == 1'b1) n_state <=s1;                                else begin n_state <= s0; end                      end                s1: begin                        if(in == 1'b1) n_state <=s2;                                else begin n_state <= s0;end                        end                s2: begin                        if(in == 1'b1) n_state <=s2;                                else begin n_state <= s0;end                        end                default: n_state <= s0;                endcase        end        always@(p_state or in) begin                case (p_state)                        s0: out = 1'b0;                        s1: out = 1'b0;                        s2: if(in == 1'b0) out = 1'b1;                          else out = 1'b0;                        default: out =1'b0;                endcase        endendmodule  
举报

更多回帖

发帖
×
20
完善资料,
赚取积分