赞
踩
参考书籍:《Verilog HDL 数字设计与综合》第二版,本文档为第7章的学习笔记。
对于行为级建模可以与数据流建模认为其是一个黑箱建模,不需要考虑内部结构,即从项目的功能考虑模块的行为,由点自上而下的建模,而像之前的开关级、门级建模更像自下而上建模。
行为级建模通常会和数据流建模一同使用,共称为RTL(寄存器传输级)。
数据流建模:
虽然行为级建模方式与C语言功能有些类似,但是Verilog再本质上是并发而非顺序的。
两个结构化过程语句:initial和always语句,两者执行过程都从仿真事件0开始执行,两种语句不可以嵌套使用。
仿真从0时刻开始执行,整个仿真过程只执行一次,如果由多个initial时,则所有initial语句的仿真均从0时刻开始并发执行,且每个块的执行独立并行。如果块中包含多条语句,那么需要begin-end将这些语句组合称为一个语句块,如果只有1条则不需要,类似c语言中的{}。
- module stimuls();
- reg a, b, c, d;
- initial
- a = 1'b0;
- initial beign
- b = 1'b1;
- #10 d = 1'b0;
- end
- initial
- #20 c = 1'b1;
- initial begin
- #20 a = 1'b1;
- #10 d = 1'b1;
- end
- endmodule
上面例子中,三条initial语句并发从0时刻执行。如果再某一条语句之前存在延迟#<delay>,那么对这条initial语句的仿真会停顿下来,在经过指定的延迟事件后执行。
时间: 0 10 20 30
执行语句:a=0,b=1 d=0 c=1,a=1 d=1
由于initial语句在仿真时只执行一次,因此一般被用于仿真文件中的初始化、信号监视、生成仿真波形等。其他变量初始化方法:
always语句从仿真0时刻开始按顺序执行其中的行为语句;在最后一条执行完成够,再次从头开始执行,如此循环往复,直至整个仿真结束。
- moudle clock_gen(output reg clock);
- initial
- clock = 1'b0;
- always
- #10 clock = ~clock;
- initial
- #1000 $finish;
- endmodule
always语句只有在断电($finish)和终端($stop)语句作用下才能停止。
过程赋值语句的更新对象是寄存器、整数、实数和时间变量。与连续赋值语句不同,连续赋值语句总是处于活跃状态,只要操作数发生变化就会重新计算重新赋值,但是过程赋值语句只有在执行到的时候才会起作用。语法如下:
- assignment ::= variable_lvalue = [ delay_or_event_control ]
- expression
过程赋值语句的左侧值的类型可以是:
阻塞赋值语句的赋值符号为:“ = ”,在串行块语句中的阻塞语句按照顺序执行,即会对后面的赋值语句阻塞执行,但是不会阻塞其后的并行块中的语句的执行。这里将在串行块和并行块中进行讨论。
注意,在对寄存器类型变量进行过程赋值时,如果赋值符两侧的位宽不相等,则采用以下原则:
非阻塞赋值语句赋值符号为:“<=”,允许赋值调度,不会阻塞位于同一个顺序块中其后语句的执行。
为区分阻塞赋值与非阻塞赋值语句作用,将对以下例子进行分析说明:
- reg x, y, z;
- reg [15:0] reg_a, reg_b;
- integer count; //整型变量
- initial begin
- x=0; y=1; z=1;
- count = 0;
- reg_a = 16'b0;
- #10 reg_b = reg_a;
-
- reg_a[2] <= #15 1'b1;
- reg_b[15:13] <= #10 {x, y, z};
- count = count + 1;
- end
首先x=0到reg_b=reg_a之间语句是从仿真0时刻开始顺序执行。由于阻塞赋值10时刻执行reg_b = reg_a,之后执行同时执行
reg_a[2] <= #15 1'b1;
reg_b[15:13] <= #10 {x, y, z};
count = count + 1;
上面的reg_a[2] <= #15 1'b1;非阻塞赋值对后面的语句执行时间上没影响。
非阻塞赋值可以用来为常见的硬件电路行为建立模型,例如一个时间发生后,多个数据并发传输行为。为区分阻塞与非阻塞,这里用上升沿交换两个寄存器的值进行说明:
- //阻塞赋值的两个并行的always块
- always @(posedge clock)
- a = b;
- always @(posedge clock)
- b = a;
-
- //非阻塞赋值两个并行的always块
- always @(posedge clock)
- a <= b;
- always @(posedge clock)
- b <= a;
阻塞赋值中:由于其是顺序执行,此时谁先执行就产生了竞争的情况,具体执行的先后顺序由使用的仿真器决定。因此这段代码不能达到a和b值交换的目的,而是使得两者具有相同的值(在时钟上升沿到来之前的值),具体哪一个与仿真器有关。
非阻塞赋值中:其是并行执行的,其赋值机制可以避免竞争:当上升沿到来时,仿真器首先读取右侧的值,并保存在临时变量中;在进行赋值时,仿真器将这些临时变量中保存的值赋值给左侧变量。这样就将读和写操作分开了,达到了交换数据的目的。
上面由于阻塞赋值时(与C语言数据交换类似),并没有将变量本身的原值进行保存,这样在顺序执行的时候变量中的原值就会被冲掉,从而无法获取原值,导致无法进行交换。这是需要将变量的原值进行保存,这样阻塞赋值就可以达到非阻塞赋值的目的了。
- always @(posedge clock)
- begin
- tempa = a;
- tempb = b;
- a = tempb ;
- b = tempa ;
- end
综上,在数字电路设计中,如果某事件发生后将产生多个数据的并发传输,最好使用非阻塞赋值来描述这种情况。非阻塞赋值由于并发执行及其执行特性可以很好的避免竞争风险。
非阻塞赋值的典型应用:流水线建模和多个互斥数据传输的建模
非阻塞赋值由于操作步骤分为了两步,在仿真时占用的内存量将会增加,仿真速度将会降低。
基于延时的时序控制出现在表达式中,它指定了语句开始执行到执行完成之间的时间间隔。定义:
- delay3 ::= # delay_value | #(delay_value[,delay_value[,delay_value]])
- delay2 ::= # delay_value | #(delay_value[,delay_value])
- delay_value ::= unsigned_number | parameter_identifier (数值)
- | specparam_identifier(标识符)| mintypmax_expression (表达式)
常规延迟控制位于赋值语句的左边,如:#10 y=10; #y x=y;
延迟控制嵌入了赋值语句,处于赋值符的右侧,如:y= #10 x+1;
内嵌赋值和常规延迟控制的区别:常规延迟是推迟整个语句延迟;内嵌赋值延迟是仿真器首先立即执行计算右侧表达式的值,并将其值保存在临时变量中,在指定推迟时间后再将值赋给左侧变量。
尽量不要使用延迟控制。再同一仿真时刻,不用的always和initial块中的过程语句均有可能被同时计算,但执行顺序是不确定的,与使用的仿真器类型有关。此时零延迟控制可以保证带零延迟控制的语句将再执行时刻相同的多条语句中最后执行,从而避免发生竞争。但是都带有零延迟时,其先后执行顺序也将是不确定的。
再Verilog中,事件是指某一个寄存器或者线网变量的值发生变化。事件可以用来触发声明语句或者块语句执行。
控制符号使用 @ 来说明,语句继续执行的条件:全部获知指定信号值发生变化、上升沿、下降沿。 * 表示块内的全部变量;posedge表示上升沿; negedge表示下降沿。
@(clock)、@(posedge clock)、@(negedge clock)
用户可以声明 event(事件)类型的变量,触发该变量,并且识别该事件是否已经发生。
命名事件关键词 event声明,它不能保存任何值。事件的触发用符号 -> 表示;判断事件是否发生使用符号@来识别事件变量。
- //本例描述再最后一组数据到达以后,把数据存储再缓存器中的行为
- event received_data; //定义一个事件控制变量
- always @(posedge clock)
- begin
- if(last_data_packet) -> received_data; //最后一组数据到达,且触发事件控制变量
- end
- always @(received_data)
- data_buf = {data_pkt[0], data_pkt[1], data_pkt[2], data_pkt[3]}
-
当有多个信号或事件发生任意一个变化都能够触发语句或者语句块的执行。如:
always @(reset or clock or d)
有时or也可以使用“ ,” 来代替,如:
always @(reset, clock, d)
当所有变量任意一个值发生变化时都会触发,or和“ ,”将会十分繁琐,故用@*或者@(*),表示对语句块中的所有变量均敏感。如:
always @(*)
电平敏感时序控制,后面的语句和语句块需要等待某个条件为真时才能执行。使用关键字wait表示等待电平敏感的条件为真。如:
always
wait(count_enable) #20 count = count+1;
上面语句表示只有当count_enable=1时,没20个时间单位进行一次自加。
if-else,使用基本和c类似。但是要注意不能省略else,否则将会产生Latch,相关资料请参考:
再多路分支语句中条件语句中的 if-else if也可以实现其功能,但是其是从头开始判断条件是否成立,并非并发判断。而case语句可以实现多条语句并行判断。
关键字为:case、endcase、default
case语句中不允许嵌套,其功能实现也与C中相似。其也会产生Latch故请参考上面的链接
除了上面case以外,还有两个变形:
- //casex用法
- reg [3:0] encoding;
- integer state;
- casex(encoding)
- 4'b1xxx : next_state = 3;
- 4'bx1xx : next_state = 2;
- 4'bxx1x : next_state = 1;
- 4'bxxx1 : next_state = 0;
- default : next_state = 0;
- endcase
如果encoding的值为4’吧0xz,则执行next_state = 3。
循环语句有:while、for、reoeat、forever,这些语法与C语言中的类似。但是循环语句为行为级建模,因此只能再always或initial块中使用,且可包含延迟表达式。
循环执行,判断条件值为假时结束。如:
while (count < 128) 如果含有多条语句时使用begin-end组合成块,当count>=128时结束。
有三部分组成:for(初识条件;终止条件;控制变量过程赋值)
for(count=0; count<128; count=count+1),多条语句依然用begin-end。
for(i=0; i<32; i=i+1) state[i]=i; 对数组或存储器循环赋值。
不想while通过判断逻辑式是否为真来确定是否继续执行,repeat是通过给定一个常量、一个变量或者一个信号,执行固定次数的循环。语法如:
repeat(10) 说明以下语句执行10次,多条语句使用begin-end。
表示永久循环,不需要判断表达式,会一直执行下去,除非遇到系统任务 $finish为止。类似于while(1)循环语句。如果需要从forever循环中退出,可以使用disable语句。
通常使用时为使循环并非一直执行下去,需要与时序控制结构结合使用。如:
forever #10 clock = ~clock;
块语句的作用是将多条语句合并成一组,使其可以像一条语句一样调用。
关键字begin-end声明,用于多条语句组成顺序块,前面已经经常用到,具有的特点如下:
关键字fork-join声明,具有以下特点:
我们可以将fork看成将一个执行流分成多个并发执行独立的执行流,而join是将并发执行流合成一个执行流。
在并行块中,如果两条语句在同一时刻对同一变量产生影响,那么将会引起隐含的竞争,这种情况需要避免。
顺序块与并行块的区别,这里使用一个简单的例子:
- //顺序块
- begin
- x = 1'b1; //0时刻执行
- #10 y = 1'b1; //10时刻执行
- #15 y = 1'b1; //25时刻执行
- end
- //并行块
- fork
- x = 1'b1; //0时刻执行
- #10 y = 1'b1; //10时刻执行
- #15 y = 1'b1; //15时刻执行
- join
顺序块和并行块是不能自己嵌套自己的,但是顺序块可以和并行块混合使用。如:
begin ... fork ... join ... end 顺序执行改并发执行。
可以对块进行命名,命名的块称为命名块
- //命名块
- module top;
-
- initial
- begin : block1
- integer i; //整形变量i是block1命名块的静态本地变量
- //可以通过层次名top.block1.i被其他模块访问
- end
- initial
- fork : block2
- reg i; //寄存器变量i是block2命名块的静态本地变量
- //可以通过层次名top.block2.i被其他模块访问
- join
Verilog通过关键字disable提供了一种终止命名块的方法。disable可以用来从循环中推出、处理错误条件以及根据控制某些代码段是否被执行。对块语句的禁用导致紧接在块后面的那条语句也被禁用。优点类似于C中的break推出循环,但break只能跳出当前循环,disable可以禁用任意一个命名模块。
- //功能从向量地位开始查找第一个值为1的位数
- reg [15:0] flag;
- integer i;
- initial
- begin
- flag = 16'b 0010_0000_0000_0000;
- i = 0;
- begin : block1
- while(i<16)
- begin
- if(flag[i])
- begin
- $display("number = %d", i);
- disable block1; //当找到标志位时,禁用命名块block1
- end
- i = i+1;
- end
- end
- end
对于向量中的多个位进行操作时,或者当进行多个模块的实例引用的重复操作时,或者在根据参数的定义来确定程序中是否应该包括某段Verilog代码的时候,使用生成语句能够大大简化程序的编写过程。
生成语句能够控制变量的声明、任务、函数的调用,还能对实例引用进行全面的控制。生成代码必须在模块中声明声明的实例范围,使用的关键generate - endgenerate来指定作用范围。
生成实例可以是以下的一种或多种类型:
如果生成实例有标识名,则可以通过层次命名规则引用,允许在生成范围内有以下列数据类型:
如果生成数据类型具有唯一标识名,可以被层次引用,还可以使用defparam声明的参数重新定义。
任务和函数的声明也可以初选在生成范围内,但是不能出现在循环生成中。生成任务和函数有唯一标识符名称,可以被层次引用。
不允许出现在生成范围中的模块项声明:
生成块的主要用法主要有三种:循环、条件、分支生成
循环生成语句允许使用者对下面的模块及模块项进行多次实例引用:
- //生成两条N位总线变量的按位异或
- module bitwise_xor(out, i0, i1);
- paramter N = 32;
- output [N-1:0] out;
- input [N-1:0] i0, i1;
- genvar j; //声明一个临时循环变量,只能用于生成块的循环计算,在设计块中并不存在
- generate for(j=0; j<N; j=j+1)
- begin : xor_loop
- xor g1(out[j], i0[j], i1[j]); //可以层次引用,如:bitwise_xor.xor_loop[5].g1
- //always @(i0[j] or i1[j]) out[j]=i0[j]^i1[j];
- end
- endgenerate
- endmodule
条件生成语句允许使用者对下面的模块及模块项进行多次实例引用:
- generate
- if (a0 < 8)||(a1 < 8)
- cla #(a0,a1) m0(p, a0, a1);
- else
- tree #(a0,a1) m1(p, a0, a1);
- endgenerate
case生成语句允许使用者对下面的模块及模块项进行多次实例引用:
用法和if-else类似。
接下来将用下面三个例子对上面知识点进行复习与应用
- //四选一多路选择器
- module mux4_1 (out, i0, i1, i2, i3, s1, s0);
- output out;
- input i0, i1, i2, i3;
- input s1, s0;
- reg out;
- always @(*)
- begin
- case({s1, s0})
- 2'b00 : out = i0;
- 2'b01 : out = i1;
- 2'b10 : out = i2;
- 2'b11 : out = i3;
- default : out = 1'bx;
- endcase
- end
- endmodule
- //四位二进制计数器
- module counter(Q, clock, clear);
- output [3:0] Q;
- input clock, clear;
- reg [3:0] Q;
- always @(posedge clear or negedge clock)
- begin
- if(clear)
- Q <= 4'd0;
- else
- Q <= Q+1;
- end
- endmodule
该交通信号控制器用于控制一条主干道与一条乡村公路的交叉楼的交通,具体功能为:
输入输出信号描述 | |||
信号 | 位宽 | 类型 | 信号描述 |
clock | 1bit | 输入信号 | 工作时钟信号,频率50MHz |
x | 1bit | 输入信号 | 触发信号,支路有车时触发置1 |
clear | 1bit | 输入信号 | 清楚信号灯状态信号 |
hwy | 2bit | 输出信号 | 主路信号灯(G/Y/R)三种状态 |
cntry | 2bit | 输出信号 | 支路信号灯(G/Y/R)三种状态 |
hwy、cntry | 00 | RED |
01 | YELLOW | |
10 | GREEN |
【FPGA】实践项目:十字路口交通信号灯控制-智慧交通文档类资源-CSDN下载由于主干道上来往的车辆很多,因此控制主干道的交通信号具有最高优先级,在默认情况下主干道的绿灯点亮;更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/ARM_qiao/85242874这里给除本人的RTL视图及仿真的时序图供大家进行参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。