引言
重新拾起Verilog吧,从各种渠道,补充自己不知道的知识,加深对Verilog的认识。本文将在不断学习过程中,加深对Verilog的认识,并在使用过程中,不断加深对语法的理解程度和灵活应用
参考资料: 志芯科技bilibili视频
参考资料:verilog夏宇闻
1 基础
文件名最好和模块的顶层名字一致。
模块声明的时候输入必须是wire变量,输出可以是wire变量,也可以是reg变量
而测试模块,处于真正的顶层,所以没有输入输出接口,因此只需要写module 模块名字 ;
case对应的是译码器,可以产生比if else 更快的效果。
2 task与function
学会task和function可以简化程序的结构,是编写大型模块的基本功底
推荐blog
2.1 task
task <task name>;
<port and data type declare>;
<statement 1>;
<statement 2>;
<statement 3>;
...
endtask
调用task
<任务名>(端口1,端口2。。。端口n)
(1)在第一行task语句中不能列出端口名称; (2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及 双向端口。
(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频繁 的就是延迟控制语句) ,但这样会造成该任务不可综合。
(4)在任务中可以调用其他的任务或函数,也可以调用自身。 (5)在任务定义结构内不能出现initial和 always过程块。
(6)在任务定义中可以出现“disable 中止语句” ,将中断正在执行的任务,但其是不
可综合的。当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
任务的调用: 任务中不能出现initial 语句和always 语句语句, 但任务调用语句可以在initial语句和 always
语句中使用。 任务调用语句中,参数列表的顺序必须与任务定义中的端口声明顺序相同。
任务调用语句是过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。
在写testbench时,用task来产生数据非常方便
task send_data(len); //任务的声明
integer len,i;//变量声明
begin
for(i=9;i<len;i=i+1)
begin
@(posedge sclk); //每次在时钟的上升沿发生变化
i_addr<=i[7:0];
i_data<=i[7:0];
end
endtask
module test
(
input clk,
output reg [4:0] c
);
reg [3:0]q=2;
reg [3:0]p=3;
task data_add;
begin
c=p+q;
end
endtask
always@(*)
data_add;
endmodule
特别要值得注意的是,调用task可以在always里面调用,也可以在initial里面调用。但不能直接写一个task的名字。
3、拼接与移位操作
//实现循环右移
reg [7:0] shifter;
always@(posedge clk )
begin
shifter<={shifter[0],shifter[7:1]};
end
//解释:每一次都是把原来的最低位,往最高位搬移一次。
//实现循环左移
reg [7:0] shifter;
always@(posedge clk)
begin
shifter<={shifter[6:0],shifter[7]};
end
//每一次都是把原来的最高位,往最低位搬移一次
//由串转并,并每次从低位插入a的单bit值
reg a;
reg [7:0] bus;
always@ (posedge clk)
begin
bus={bus[6:0]],a};
end
//由串转并,并每次从高位插入a的单bit值
reg a;
reg [7:0] bus;
always@ (posedge clk)
begin
bus={a,bus[7:1]};
end
4、文件的读写操作
----将数据从txt读取-----
仿真时经常需要从文件中读取测试激励,甚至需要将FPGA仿真的结果保存下来,比如用于matlab进行频谱分析。
integer i; //第几个数
reg [9:0] stimulus[1:data_num]; //定义一个数组,数据位宽为10bit,数据个数为data_num个
initial
begin
$readmemb("./sin.txt",stimulus); //将当前路径下的txt文件数据存储到数组中
i=0;
repeat(data_num)
begin
i=i+1;
din=stimulus[i];
#clk_period; //每个时钟读取一次
end
end
读取二进制格式的文件是用系统任务readmemb;读取十六进制格式文件使用readmemb;读取十六进制格式文件使用readmemh。其命令为$readmemb(“filename”, mem_name),将filename中的内容读取到mem_name中。
注意filename文件路径中应该用反斜杠“/”,与windows系统中的文件路径使用的“\”不同。如果不指定路径,向上面程序一样直接写文件名字,那么该文件必须和testbench文件在同一路径下
示例代码:
integer file_out
intial
begin
file_out=$fopen("mix.txt");
if(!file_out) begin
$display("can't open file");
$finish;
end
end
wire signed [19:0] dout_s=dout;
wire rst_write=clk&rst_n; //复位期间不应写入数据
always @(posedge rst_write)
$fdisplay(file_out,"%d",dout_s);
Verilog默认是无符号的,如果要用有符号的要用integer 或者用 reg signed
写入文件需要先用$fopen系统任务打开文件,这个系统任务在打开文件的同时会清空文件,并返回一个句柄,如果句柄为0则表示打开文件失败。
如果原来不存在该文件,则会自动创建该文件。
打开文件之后便可以用得到的句柄和$fdisplay系统任务向文件中写入数据。这个系统任务和我们在C++中常用的fprintf函数的用法很像。
上面的程序中是将数据转换为带符号数signed后再写入,必须说明转换与否是有差别的,如果按默认的unsigned的格式写入txt的是无符号数。
另外,每使用一次$fdisplay,都会在数据后插入一个换行符。
系统时间函数 $time与 $realtime
$ time是个取整的时间函数,而$ realtime是不需要取整的时间函数
参考知乎:https://zhuanlan.zhihu.com/p/72085819