3.4 function 与 task:陈述的复用


文档摘要

3.4 function 与 task:陈述的复用 本节摘要:function 是无时序的纯函数,调用在综合期展开成组合逻辑;task 允许携带时序控制,只活在仿真世界。两者是 Verilog 里仅有的两级复用设施,分清活动范围,才能既省代码又不出界。 分支循环讲的是陈述的组织,这一节讲陈述的复用。同一个计算在多处出现时,复制粘贴是维护灾难的开始,function 与 task 就是这门语言给出的答案——但它们的权限差异极大,用错位置直接被综合器拒收。 function:纯函数的领地 function 的合同条款:零仿真时间(调用立即返回)、不能包含延迟与事件控制、不能调用 task、至少一个输入、返回值只有一个。

3.4 function 与 task:陈述的复用

本节摘要:function 是无时序的纯函数,调用在综合期展开成组合逻辑;task 允许携带时序控制,只活在仿真世界。两者是 Verilog 里仅有的两级复用设施,分清活动范围,才能既省代码又不出界。

分支循环讲的是陈述的组织,这一节讲陈述的复用。同一个计算在多处出现时,复制粘贴是维护灾难的开始,function 与 task 就是这门语言给出的答案——但它们的权限差异极大,用错位置直接被综合器拒收。

function:纯函数的领地

function 的合同条款:零仿真时间(调用立即返回)、不能包含延迟与事件控制、不能调用 task、至少一个输入、返回值只有一个。这些限制换来一个重要性质——function 是纯组合函数,综合时在每个调用点就地展开,等价于把函数体复制过去。

module saturating_add #( parameter WIDTH = 8 )( input wire signed [WIDTH-1:0] a, b, output wire signed [WIDTH-1:0] y ); // 饱和加法:结果超界时钳到极值 function signed [WIDTH-1:0] sat_add( input signed [WIDTH-1:0] x, input signed [WIDTH-1:0] z ); reg signed [WIDTH:0] ext; // 扩一位存进位与符号 begin ext = x + z; if (ext > $signed({{(WIDTH){1'b0}}, 1'b1}) * $signed((1 << (WIDTH-1)) - 1)) sat_add = {1'b0, {(WIDTH-1){1'b1}}}; // 正向饱和 else if (ext < -$signed((1 << (WIDTH-1)))) sat_add = {1'b1, {(WIDTH-1){1'b0}}}; // 负向饱和 else sat_add = ext[WIDTH-1:0]; end endfunction assign y = sat_add(a, b); endmodule

这段代码演示了 function 的两个实用场景:封装带边界判断的算术,以及在函数内用临时 reg 做中间量(函数内声明的变量是局部的,不产生实际寄存器,展开后就是组合网)。要注意函数的展开是「按调用点复制」——同一个 function 在五个地方调用,综合出五份电路,面积按调用次数计价。想共享一份硬件,答案是实例化一个模块,不是调用函数。

function 还有两处仿真侧的延伸用法:声明为 automatic 后每次调用独立分配局部变量,支持递归;在测试台里当计算工具(期望值生成、校验和计算)极为顺手。

task:可以消磨时间的过程

task 与 function 的决定性差异:task 可以包含延迟、事件等待、其他 task 调用,可以没有返回值但有多个输出参数。代价是综合器对它说不——task 属于仿真专用设施,活动范围是测试台。

// 测试台里的 task:封装一个「发一个字节并等应答」的动作 task send_byte(input [7:0] data); begin @(negedge clk); // 等到时钟下降沿 serial_dout = data; serial_valid = 1'b1; do @(posedge clk); while (!serial_ready); // 等待握手 @(negedge clk); serial_valid = 1'b0; end endtask // 激励序列因此读起来像脚本 initial begin serial_valid = 1'b0; repeat (4) @(posedge clk); send_byte(8'hA5); send_byte(8'h5A); send_byte(8'hFF); $finish; end

测试台代码的可读性几乎全靠 task 撑起来:把「一个事务」封装成 task,激励序列就变成一句句人话。第七章搭测试台时会大量使用这套手法。

一张边界对照表

维度 function task
仿真时间 零,立即返回 可含延迟与事件等待
可综合 可以,调用点展开 不可以
调用限制 不能调 task 可调 function 与 task
返回结构 单返回值 多输出参数或无返回
主战场 可综合组合逻辑、仿真计算 测试台激励与检查序列

记忆锚点:function 是「数学函数」,问一句答一句;task 是「办事流程」,可以排队、可以等待、可以分几步交差。

案例展开:CRC 校验的两栖部署。 背景:某串行协议需要在发送端生成 CRC 校验、测试台里校验接收端结果。操作:把 CRC 逐位计算写成一个 function,RTL 内部在移位路径上调用它生成校验位;测试台里用同一个 function(放进共享的函数声明文件,两端编译单元都包含)对收到的数据重新计算并比对。结果:同一份算法在设计与验证两侧各用一次,测试台不需要独立的参考模型,协议变更时只改一处。解读:function 的纯函数性质使它天然适合「设计内计算 + 验证侧参考」的双栖角色——只要函数无时序、无副作用,两侧行为就严格一致,这正是自检查测试台(第七章)的基础设施。变式:算法带时序(比如需要多拍完成)时,function 无力承担,正确做法是把算法写成可综合模块,测试台另写行为级参考模型,两者在测试台内对账。

自动与静态:变量生命期的坑

Verilog-2001 的 function 与 task 默认是静态的:所有调用共享同一份局部变量。仿真里两个进程并发调用同一个静态 function,局部变量互相踩踏,结果错得没有规律。需要可重入时显式声明 automatic——每次调用分配独立副本。工程惯例是测试台里的 function task 一律写 automatic,可综合代码里用不用都安全(综合后变量本来就展开成组合网),写上也不吃亏。

还有一个同名陷阱:函数名与信号名撞车时,调用处的歧义解析结果因工具而异。规避纪律很简单——函数名加统一前缀(如 fn_ 前缀),从命名上隔离命名空间。

本节要点回顾

  • function 是纯函数:零时间、无时序、单返回值,综合时按调用点展开,面积按调用次数计价;
  • task 属于仿真:可含延迟与等待,综合器拒收,活动范围是测试台;
  • 想共享硬件用模块:一份电路多处使用靠实例化,不靠函数调用;
  • 静态默认是坑:并发调用共享局部变量,测试台 function task 一律 automatic;
  • 双栖价值:纯函数可同时服务设计内计算与验证侧参考,是自检查的基础设施。

第 3 章的陈述规则到此齐备。下一章换一个维度——时间:事件何时触发、延迟如何建模、时钟与复位怎么写,那是让正确陈述在对的时刻被听见的语法。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U