task——Verilog的任务
之前的文章中记录了使用 function
函数等功能:function—— Verilog的函数。这一次,记录使用task
任务的功能。
事出有因
设计中需要使用到IIC salve module
,那么在testbench
中就需要有对应的master module
,而且需要一个可以进行读写功能的模型。
如果还使用Verilog进行可综合的编写,未免有些琐碎,还需要严格的控制逻辑。因此,希望有一个功能可以实在进行IIC的读写控制时序操作。
之前的function
只有函数功能,函数的操作中均为组合逻辑,无法完成本次需要的时序逻辑的需求,于是就选用了task
功能。
Talk is cheap
整个IIC master model
的文件如下:
/**
* Filename : iic_master_model.v
* Date : Friday, May 21, 2021 4:50 PM
*
* Version : V 0.1
* Author : ljacki@163.com
* Modification history
* : Friday, May 21, 2021 4:50 PM Create the demo
* : Friday, May 28, 2021 2:37 PM Add the task of write pkg.
*/
`timescale 1ns / 100ps
`define DEBUG_INFO 1'b0
`define DELAY 2
module iic_master_model (
// system
input clk,
input rst_n,
input req,
input rd_valid_i,
input [7:0] rd_data_i,
output reg start,
output reg stop,
output reg [7:0] dvice_id,
output reg wr_en_o,
output reg [7:0] wr_data_o,
output reg rd_en_o,
output reg [7:0] rd_addr_o
);
parameter DEVICE_ID = 7'h70;
parameter PKG_LEN = 8'h10;
// Declarations
reg [7:0] dout;
// initial
initial begin
start = 1'b0;
stop = 1'b0; // stop信号需要复位,否则master里面req不会assert
dvice_id = 8'b0;
wr_en_o = 1'b0;
wr_data_o = 8'b0;
rd_en_o = 1'b0;
rd_addr_o = 8'h0;
dout = 8'h0;
if ( `DEBUG_INFO ) begin
$display("\nINFO: IIC MASTER MODEL INSTANTIATED (%m)");
end
end
task m_start;
input wr_rd; //1'b0: write, 1'b1:read;
begin
// @(posedge clk);
#`DELAY
start = 1'b1;
dvice_id = {DEVICE_ID, wr_rd};
@(posedge clk);
#`DELAY
start = 1'b0;
dvice_id = 8'h0;
// if ( wr_rd == 1'b0 ) begin
// $display("status: %t master START WRITE,\tDEVICE ID : %X", $time, DEVICE_ID);
// end else begin
// $display("status: %t master START READ, \tDEVICE ID : %X", $time, DEVICE_ID);
// end
end
endtask
task m_wr_data;
input [7:0] wr_addr;
input [7:0] wr_data;
input stop_flag;
begin
while(~req) @(posedge clk);
#`DELAY
wr_en_o = 1'b1;
wr_data_o = wr_addr;
@(posedge clk);
#`DELAY
wr_en_o = 1'b0;
wr_data_o = 8'b0;
if ( stop_flag) begin
while(~req) @(posedge clk);
#`DELAY
stop = 1'b1;
wr_en_o = 1'b1;
wr_data_o = wr_data;
@(posedge clk);
#`DELAY
stop = 1'b0;
wr_en_o = 1'b0;
wr_data_o = 8'b0;
end else begin
while(~req) @(posedge clk);
#`DELAY
stop = 1'b0;
wr_en_o = 1'b1;
wr_data_o = wr_data;
@(posedge clk);
#`DELAY
stop = 1'b0;
wr_en_o = 1'b0;
wr_data_o = 8'b0;
end
if ( `DEBUG_INFO) begin
$display("status: %t master WRITE ADDR:%X,\tDATA:%X\n", $time, wr_addr, wr_data);
end
end
endtask
task m_write;
input [7:0] waddr;
input [7:0] wdata;
begin
m_start(1'b0);
m_wr_data(waddr, wdata, 1'b1);
repeat(50) @(posedge clk);
if ( `DEBUG_INFO) begin
$display("status: %t master WRITE ADDR:%X,\tDATA:%X\n", $time, waddr, wdata);
end
end
endtask
task m_write_pkg;
// input [7:0] len;
input [8*PKG_LEN-1:0] addr;
input [8*PKG_LEN-1:0] data;
begin
m_start(1'b0);
m_wr_data(addr[(PKG_LEN-0)*8-1:(PKG_LEN-1)*8], data[(PKG_LEN-0)*8-1:(PKG_LEN-1)*8], 1'b0);
m_wr_data(addr[(PKG_LEN-1)*8-1:(PKG_LEN-2)*8], data[(PKG_LEN-1)*8-1:(PKG_LEN-2)*8], 1'b0);
m_wr_data(addr[(PKG_LEN-2)*8-1:(PKG_LEN-3)*8], data[(PKG_LEN-2)*8-1:(PKG_LEN-3)*8], 1'b0);
m_wr_data(addr[(PKG_LEN-3)*8-1:(PKG_LEN-4)*8], data[(PKG_LEN-3)*8-1:(PKG_LEN-4)*8], 1'b1);
repeat(50) @(posedge clk);
end
endtask
task m_read;
input [7:0] rd_addr;
output [7:0] rd_dout;
begin
@(posedge clk);
#`DELAY
m_start(1'b0);
while(~req) @(posedge clk);
#`DELAY
rd_en_o = 1'b1;
rd_addr_o = rd_addr;
@(posedge clk);
#`DELAY
rd_en_o = 1'b0;
rd_addr_o = 8'h0;
while(~req) @(posedge clk);
#`DELAY
m_start(1'b1);
while(~rd_valid_i) @(posedge clk);
#`DELAY
rd_dout = rd_data_i;
repeat(50) @(posedge clk);
if ( `DEBUG_INFO ) begin
$display("status: %t master READ ADDR:%X,\tDATA:%X\n", $time, rd_addr, rd_dout);
end
end
endtask
task m_cmp;
input [7:0] addr;
input [7:0] dexp; // the expected data
begin
m_read(addr, dout);
if ( dexp != dout ) begin
$display("\t\t\tData compare error. Received %h, expected %h at time %t", dout, dexp, $time);
end
end
endtask
endmodule // the end of iic_master_model
声明:上述时序需要搭配iic master
使用;根据文章,只是用来解释task任务的使用方法。
task简介
任务(task)就是一段封装在task-endtask
之间的程序。
通过调用来执行,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,则任务就不会被执行。
调用某个任务就以位置可能需要它处理某些数据并返回操作结果,因此Verilog中的task存在输入端和输出端。
定义
task
的定义方式如下:
task<任务名>; // <= task task_id;
<端口及数据类型声明语句> // <= [declaration]
<语句1> // <= procedural_statement
<语句2> // <= procedural_statement
..... // <=
<语句n> // <= procedural_statement
endtask // <= endtask // <= endtask
其中,关键词task
和endtask
将他们之间的内容标志成一个任务定义;
- task标志着一个任务定义结构的开始;
- task_id是任务名;
- 可选项declaration是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;
- procedural_statement是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;
- endtask为任务定义结构体结束标志。
task可以启动其他的task,其他的task又可以启动别的task,可以启动的task是没有限制的,只有当所有的task都完成之后,控制才能返回。task可以没有或者有多个输入输出类型的变量。
列子
一个task的列子为:
task find_max; //任务定义结构开头,命名为 find_max
input [15:0] x,y; //输入端口说明
output [15:0] tmp; //输出端口说明
if(x>y) //给出任务定义的描述语句
tmp = x;
else
tmp = y;
endtask
注意事项
编写task的注意事项如下:
- 第一行
task
语句中不能列出端口名称; - 任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及双向端口;
- 在任务中可以调用其他的任务或函数,也可以调用自身;
- 在任务定义中可以出现
disable
中止语句,用于中断这个在执行的任务,但其是不可以综合的。当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
task的调用
task调用语句的语法形式如下:
task_id(端口1, 端口2, 端口n);
其中,task_id是要调用的任务名,端口1, 端口2,端口n是参数列表。参数列表给出传入任务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果)。
在调用task时,需要注意以下几点:
- 任务调用语句只能出现在过程块内;
- 任务调用语句和一条普通的行为描述语句的处理方法一致;
- 当被调用输入、输出户双向端口诗,任务调用语句必须包含端口名列表,且信号端口顺序、类型必须于任务定义结构中的顺序、类型一致;
- 任务的输出端口必须和寄存器类型的数据变量对应。
- 可综合的任务只能实现组合逻辑,也就是说调用可综合任务的时间为0。而在面向仿真的任务中可以带有时序控制,如多个时钟周期或时延,一次面向藩镇的任务调用时间不为0。
上述IIC master model
可以通过简单的读写任务,对IIC
总线添加测试激励并读取总线数据:
/**
* 寄存器全部读一遍
*/
for (cnt = 8'h0; cnt <= 8'h57; cnt = cnt + 1'b1) begin
m0.m_read(cnt, rd_dout);
end
/**
* 寄存器全部写一遍
*/
for (cnt = 8'h0; cnt <= 8'h57; cnt = cnt + 1'b1) begin
m0.m_write(cnt, cnt);
end
/**
* 寄存器值进行比较
*/
for (cnt = 8'h0; cnt <= 8'h57; cnt = cnt + 1'b1) begin
m0.m_cmp(cnt, cnt);
end
/**
* 连续写入,需要修改参数:PKG_LEN;
* PKG_LEN: 连续写入寄存器的个数;
* addr: 连续写入寄存器地址拼接,MSB 8 bit 优先写入;
* data: 连续写入寄存器值拼接,MSB 8 bit 优先写入;
* 需要手动修改 m_write_pkg的主体内容;
*/
m0.m_write_pkg( addr, data );
// 短暂复位,寄存器值复位;
// sys_rst_n = 1'b0;
// #500
// sys_rst_n = 1'b1;
// $display("\nstatus: %t reset again", $time);
//
// for (cnt = 8'h0; cnt <= 8'h57; cnt = cnt + 1'b1) begin
// m0.m_read(cnt, rd_dout);
// end
对于CPU读写时序以及类似的UART/SP/IIC/PC/USB
等总线接口时序,早期的验证方法都是提供一些任务来进行总线交互,而现在则有一个专有名称:BFM(Bus Function Model)
。
任务与函数的区别
任务与函数都是两种常见的执行调用对象,二者区别在于:
差异项 | 任务特点 | 函数特点 |
---|---|---|
1 | 任务可以有input、output和inout,数量不限 | 函数只有input参数,且至少有一个input |
2 | 任务可以包含时序控制(如延时等) | 函数不能包含任何延迟,仿真时间0 |
3 | 任务可以用disable中断 | 函数不允许disable,wait语句 |
4 | 任务可以同过I/O端口实现值传递 | 函数名即输出变量名,通过函数返回值 |
5 | 任务可以调用其他任务和函数 | 函数只能调用其他函数,不能调用任务 |
6 | 任务可以dinginess自己的仿真时间单位 | 函数只能于主模块公用一个仿真时间单位 |
7 | 任务能支持多种目的,能计算多个结果值,结果值只能通过被调用的任务输出端口输出或总线端口送出 | 函数通过一个返回值来响应输入信号的值 |
8 | 函数中不能有wire型变量 |
参考文章
Verilog重点解析(3)(task&function):文章中有对静态task和动态task的描述。
2021-6-26.