作为上一篇的后续文章。我是跟着蔡觉平老师的视频走的。以下概念都用他的说法来讲述。
数据流建模
只有一个语句:连续赋值语句。
赋值目标只能是标量线网和向量线网两种。可以描述所有组合电路,用来表达逻辑表达式。
显式连续赋值语句:
<net_declaration><range><name>;
assign#<delay><name>=Assignment expression;
隐式连续赋值语句:
<net_declaration><drive_strength><range>#<delay><name>=Assignment expression;
连续赋值语句不能出现在过程块里面,原因请看verilog HDL里面连续赋值语句为什么不可以出现在过程块里面以及驱动强度和充电强度-CSDN博客
多个连续赋值语句是并行的,与位置无关。
只要赋值语句右边表达式任何一个变量有变化,表达式立即被计算,这就是为什么是用来描述组电路。计算的结果立即给左边信号,除非定义延迟。这个延迟具有硬件电路中惯性延迟的特性,任何小于其延迟的信号变化会被滤掉,不会出现在输出端口上。
行为级建模
主要用赋值语句和条件表达式语句。
类别 | 语句 | 可综合性 |
过程语句 | initial | |
always | 有 | |
语句块 | 串行语句块begin—end,顺序 | 有 |
并行语句块fork—join,同时 | ||
条件语句 | if_else | 有 |
case,casex、casez后面两个没有综合,多用于仿真 | 有case | |
循环语句 | forever | |
repeat | ||
赋值语句 | 过程连续赋值assign | |
过程赋值=,<= | 有 |
这里的可综合性指的是可以形成电路。
always语句块
相对于initial语句,always语句块的触发状态是一直存在的,只要满足后面的敏感事件表就执行过程。
always@(<敏感事件表>)
语句块;
如@(posedge clk or negedge resel)//当clk的上升沿到来或者reset信号的下降沿到来。
敏感事件表里不会有与;
在过程语句里,被赋值信号必须是reg类型。
组合电路,所有输入信号写入敏感事件表
时序电路,所有关心的时间变量写入敏感事件表
过程赋值语句
有阻塞性过程赋值语句“=”和非阻塞性过程赋值语句“<=”。
阻塞语句在串行语句块里,先后执行,并行语句块,同时执行,没有先后之分。执行的顺序是,先计算右端表达式的值,然后等到延时时间结束,将值赋值给左边的变量。
非阻塞性过程赋值语句,在串行语句块里面,各条非阻塞性过程赋值语句没有先后顺序之分。不阻塞。执行非阻塞性过程赋值语句的顺序是先计算右边的值,立刻将值赋值给左边的变量,与仿真时间无关。
过程连续赋值语句
赋值,重新赋值(assign,deassign)
assign <寄存器变量>=<赋值表达式>;
deassign <寄存器变量>;
强制,释放语句(force,release)优先级高一些,都是临时分配。语法类型与上面的一样。
条件语句
if语句和c语言其实差不多,但是类似于符号表达式,相当于数据选择器,所以来讲可综合性比较好。这里不再解释。
case语句多用于多条件译码电路设计。也可用于描述状态转换表和真值表。语法格式:
case(控制表达式)
值1:语句块一
值2,值3,……值n-1:语句二
值n:语句块三
default:语句块四
endcase
default是除了之前所有的情况,如果之前的语句将所有的可能值都列出来,则不需要default。有default的地方代码不会被覆盖掉,所以代码覆盖率一定小于100%。很多时候必须加上default,避免所描述硬件out连到input,出现错误。
casex是只比较01,相同为1,不相同为0,x和z和所有数值相比都是1
casez除了z,其余都比较。
循环语句
forever语句
语句格式:forever语句或语句块
表示永久循环,不包含任何条件表达式,只执行无限的循环,直到遇到系统任务$finish,如果需要退出,使用disable语句。
repeat语句
repeat(循环次数表达式)
语句块;
表示执行固定的次数。
结构化建模
将硬件电路描述成一个分级子模块系统,通过逐层调用这些模块构成功能复杂的数字逻辑电路和系统。根据所调用的子模块的不同抽象级别。可以分为三类:
模块级建模
类似于搭积木,自己定义一部分模块,然后用更高级的模块调用。
模块调用方式:
模块可以被任何其他模块调用,这种调用实际上是将模块所描述的电路复制并连接。用法格式:
模块名<参数列表>实例名(端口列表);
参数列表:用来传递参数。
实例名:是被调用的模块在当前模块里面的名称。通常是用U加数字或者L加数字。
当多次被调用时
模块名 <参数列表>实例名1(端口列表1);
……
<参数列表>实例名n(端口列表n);
端口列表指明了模块实例与外部信号的连接。这种也可以用阵列调用的方式来调用,格式如下:
<被调用模块名><实例阵列名>[阵列左边界:阵列右边界]<端口连接表>;
[阵列左边界:阵列右边界]:边界是常量表达式,用来指定调用后的模块实例阵列的大小。如[5:0]表明有六个实例,U0~5。
端口对应方式:
有两种方式,分别是:
1.位置对应方式:按照位置一一对应。
2.端口名对应方式:格式如下
模块名<参数列表>实例名(端口号1<信号名1>,端口号2<信号2>);端口号是被调用模块的。然后实例会把信号通过端口给被调用的模块。
不同端口位宽的匹配:
位宽匹配规则与连续赋值时使用的规则一样,但是这个语法不常用。位宽短的信号给长的,右对齐,高位用0补齐,位宽长的给短的给信号,高位舍弃,依旧是右对齐。
模块参数值:
当一个模块被另一个模块引用例化时,高层模块可以对低层模块的参数值进行改写。这样就允许在编译时将不同的参数传递给多个相同名字的模块,而不用单独为只有参数不同的多个模块再新建文件。
可以通过使用带有参数的模块实例语句和使用定义参数语句(defparam)修改参数。
在定义模块时,使用parameter定义参数。在后面高级的模块调用时。语句格式如下:
模块名<参数列表>调用名(端口名列表);参数列表又有位置对应和名称对应。
module para(C,D);
parameter a=1;
parameter b=1;
endmodule
module para2;
para #(4,3) U1(C1,D1);
para #(b(6),a(5)) U2(C2,D2);
endmodule
参数重定义格式:
defparam 参数名1=参数值1,
……
参数名2=参数值2;
参数名必须通过分级路径的形式,才可以锁定修改的参数是那个模块的。
门级建模
12个门,14个开关级元件。
类型 | 元件 | |
基本门 | 多输入门 | and,nand,or,nor,xor,xnor |
多输出门 | buf,not | |
三态门 | 允许定义驱动强度 | bufif0,bufif1,notif0,notif1 |
mos开关 | 无驱动强度 | nmos,pmos,cmos,rnmos,rpmos,rcmos, |
双向开关 | 无驱动强度 | |
无驱动强度 | ||
上拉,下拉电阻 | 允许定义驱动强度 |
多输入门:
门类型<实例名>(<输出端口><输入端口1><输入端口2>);最多十个。
多输出门:
元件名<实例名>(<输出端口1><输出端口2><输入端口>);
三态门:
元件名<实例名>(<输出端口><输入端口><控制输入端口>);
开关级建模
开关级分为两大类,MOS开关,双向开关,又可分为电阻型(前缀r)和非电阻型。
nmos 和pmos的实例化格式是:
nmos 或pmos 实例名 (out,data,control);
cmos的开关的实例化语句是:
cmos 实例名 (out,data,ncontrol,pcontrol);