本文主要记录在Verilog学习的过程中容易出错、容易遗忘的知识点,会不定期更新
组合逻辑易错点
1.always块语句
在Verilog中可以使用两种方法描述组合逻辑:使用always组合块或assign语句
- 当使用always组合块描述时,变量需声明为reg类型(此时必须用
=
赋值,否则综合后可能会出错) - 当使用assign语句描述时,变量需声明为wire类型
- 当使用always组合块描述时,变量需声明为reg类型(此时必须用
在always块内部开始时为变量分配默认值,可以确保综合后不会生成锁存器
以if_else为例:
1
2
3
4
5
6
7
8
9
10
11
12
13reg [1 : 0] cnter, cnter_nxt;
always @(*) begin
//put all default values here
cnter_nxt = cnter;
//describe the equation in an algorithmic manner
if(reset_cnter)
cnter_nxt = 'd0;
else if(cnter == 2'b11)
cnter_nxt = 'd0;
else
cnter_nxt = cnter + 1'b1;
end以case为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24always @(*) begin
state_nxt = state; //declare the default value in the beginning
case(state)
IDLE: begin
if(start_write)
start_nxt = WRITE;
else if(start_read)
start_nxt = READ;
//else start_nxt = state; already covered in beginning
end
WRITE: begin
if(end_write)
state_nxt = IDLE;
//else start_nxt = state; already covered in beginning
end
READ: begin
if(end_read)
state_nxt = IDLE;
//else start_nxt = state; already covered in beginning
end
default: begin
end
endcase
end
2.移位操作符
算数左移
<<
:1
2wire [7 : 0] a, b, c;
assign c = a << b; //将a向左移b位,低位用0填充- 在不发生溢出时,左移可以用来进行某些乘法运算,这里的溢出,指的是a最大能表示的范围,如果a左移后的值超过a能表示的最大范围,还用左移来代替乘法运算,就会出错!!!
- 例如a = 8’b11000011(195),将a左移2位后(就是乘以4),c = 8’b00001100,最高两位就丢失了(因为195*4后是780,而8bit无符号数最多表示255,故会发生溢出)
算数右移
>>
:1
2wire [7 : 0] a, b, c;
assign c = a >> b; //将a向右移b位,高位用0填充
函数与任务
- 函数
function
:- 函数可以用来生成可综合的组合逻辑。当同一逻辑功能在多处使用时,可以采用Verilog函数来描述其功能,上层电路可以调用该函数,这样有利于提高代码的可读性和电路的可重用性
- 函数内部所描述的逻辑功能是可综合的,综合后得到的是组合逻辑,其内部不能出现与时间控制相关的语句,如wait,@或 #;
Verilog中的`ifdef
在模块内部,设计者可以在ifdef和对应的else后面插入一些语句。综合工具进行RTL代码编译处理时,将首先判断是否对其后面的字符串进行了定义,如果进行了定义,那么将其后面的语句插入电路代码中,否则它将else后面的语句插入电路代码中。有时ifdef和else后面没有需要插入的语句
下面是一个具体的例子:
创建一个含有`define定义的头文件:chiptop_defines.vh
1
2//`define CHIP_XYZ_SEL1
//`define CHIP_XYZ_SEL2使用`include语句包含.vh文件
1
ifdef用法举例
1
2
3
4
5
6
7
8
9
10
assign d_nxt = a + b;
assign e_nxt = a + c;
assign d_nxt = a - b;
assign e_nxt = a - c;
assign d_nxt = a * b;
assign e_nxt = a * c;- 上面是一个具有多个条件编译分支的例子。如果在chiptop_defines.vh文件中对CHIP_XYZ_SEL1进行了定义,编译后将在电路代码中插入d_nxt = a + b;e_nxt = a + c;否则将判断是否定义了CHIP_XYZ_SEL2,如果没有定义,那么编译器将进入最后一个分支。
- 注意:是``elsif
,不是
else if(这么写会报错)【《Verilog高级数字系统设计计数与实例分析》这本书咋这么多错误,还得自己敲敲代码】
仿真结果如下:
`ifdef的用法与mux语句的区别:
- 在一个mux语句中,不同的分支都会出现在综合后的网表中,根据select信号(选择控制信号)的值决定执行其中的一条路路径
- 在`ifdef中,只有某一个分支对应的代码会在网表中实际出现
- 如果设计者需要在芯片中保留两条路径,并且可以根据需要进行选择,那么应使用mux而不是`ifdef
用于验证的Verilog语法
1.$random
$random可以产生一个有符号的32bit随机整数,一般的用法为:
1
num = $random % b;
- num的范围在-(b-1)~(b-1)的随机数
产生随机正整数:
1
num = {$random} % b;
- num的范围在0~(b-1)中的随机数
产生一个在min,max之间随机数
1
num = min + {$random} % (max - min + 1);
2.while循环
while循环语句在执行时,只要其后的表达式为真,则循环执行后面对应的语句。当表达式的值为假时,退出该循环
在while循环内,建议加入与定时控制有关的语句,以免形成定时环路,造成死循环
下面是一个while循环的例子,在这段代码中,需要从某个基地址开始,按照一定的地址增量产生新的地址对存储器进行访问,当地址大于预先设定的最大地址时,跳出while循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27module while_tb;
parameter INIT_ADDR = 32'h0000_0020,
MAX_ADDR = 32'h0000_003C,
ADDR_INCR = 8'h04;
reg clk;
reg [32 : 0] mem_addr;
always #5 clk = ~clk;
initial begin
clk = 1;
end
initial begin
mem_addr = INIT_ADDR;
while(mem_addr <= MAX_ADDR) begin
@(posedge clk);
//---do the task here
//---end of task
$display("memory address = %h", mem_addr);
mem_addr = mem_addr + ADDR_INCR;
end
end
endmodule仿真结果:
多个if综合效果
(20240604)附:第一次知道原来一个always中多个if并行,编译器不会编译成多个mux并行(针对在多个if中对同一个变量赋值的情况),还是级联的mux,最后一个if的优先级最高(越靠近输出)(在我另一篇文章中也有记录FPGA数字信号处理之高速度结构设计 | ssy的小天地 (ssy1938010014.github.io))
可参考:Verilog中单if语句、多if语句和case语句与优先级的关系_verilog if else if 执行顺序-CSDN博客
我当时是测试了这样一段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28always @(posedge clk) begin
begin
if(cfar_flag[0]) begin
object <= 1'b1;
end
if(cfar_flag[1]) begin
object <= 1'b1;
end
if(cfar_flag[2]) begin
object <= 1'b1;
end
if(cfar_flag[3]) begin
object <= 1'b1;
end
if(cfar_flag[4]) begin
object <= 1'b1;
end
if(cfar_flag[5]) begin
object <= 1'b1;
end
if(cfar_flag[6]) begin
object <= 1'b1;
end
if(cfar_flag[7]) begin
object <= 1'b1;
end
end
end综合结果:(圈起来的优先级最高cfar_flag[7])
如果把if放在不同always中,那么就会综合出并行的效果:
1
2
3
4
5
6
7
8
9
10genvar i;
generate
for(i = 0; i < 8; i = i + 1) begin
always @(posedge clk) begin
if(cfar_flag[i]) begin
object <= 2'd2;
end
end
end
endgenerate综合结果:
但这种方式综合会报CW(critical warning),布线会报错
ILA与总线模式
若测试接口用总线模式输出,此时若ila单独一根一根线拉出去观察,就会把之前总线覆盖,所以此时ila也要用总线模式
错误的:
正确的: