2.2 向量、位宽与数组


文档摘要

2.2 向量、位宽与数组 本节摘要:Verilog 的算术发生在指定位宽的容器里,溢出静默截断、不报异常;位选择与部分选择是从容器里取证据的手术刀,数组则把多位向量排成寄存器堆。位宽意识是区分新手与熟手的第一道分水岭。 类型定了 wire 还是 reg 之后,登记的下一栏是宽度。这一节把向量声明、位选择、数组、以及贯穿所有运算的位宽规则一次讲清——它们合起来决定了你电路里每根总线的物理宽度。 向量声明与位序习惯 标量一位,向量多位: 声明八位总线,方括号里是「最高位下标 : 最低位下标」。Verilog 不强制降序, 合法,但工程惯例统一用降序——因为多字节拼接时降序写法与「高位在左」的阅读顺序一致,混用升序降序是接线错位的经典来源。 按位与按段取值有三套写法: 取一位; 取固定段;

2.2 向量、位宽与数组

本节摘要:Verilog 的算术发生在指定位宽的容器里,溢出静默截断、不报异常;位选择与部分选择是从容器里取证据的手术刀,数组则把多位向量排成寄存器堆。位宽意识是区分新手与熟手的第一道分水岭。

类型定了 wire 还是 reg 之后,登记的下一栏是宽度。这一节把向量声明、位选择、数组、以及贯穿所有运算的位宽规则一次讲清——它们合起来决定了你电路里每根总线的物理宽度。

向量声明与位序习惯

标量一位,向量多位:wire [7:0] data; 声明八位总线,方括号里是「最高位下标 : 最低位下标」。Verilog 不强制降序,[0:7] 合法,但工程惯例统一用降序——因为多字节拼接时降序写法与「高位在左」的阅读顺序一致,混用升序降序是接线错位的经典来源。

按位与按段取值有三套写法:data[3] 取一位;data[7:4] 取固定段;data[base +: 4] 按变量基址取四位的索引部分选择。最后一种在循环取段的场景不可替代——固定段写法不允许边界是变量,而 +: 语法可以:

module slicer( input wire [31:0] din, input wire [2:0] idx, // 段序号,运行期可变 output wire [3:0] dout ); // 取第 idx 段(每段 4 位):等价于 din[idx*4 +: 4] assign dout = din[idx*4 +: 4]; endmodule

这段代码综合成一组 8 选 1 的 4 位多路选择器。写成 din[idx*4+3 : idx*4] 是非法的——冒号右侧必须是常量表达式,+: 语法就是为绕开这个限制而生。

位宽规则:表达式有多宽

Verilog 的位宽规则可以压缩成三条。其一,赋值语句的上下文位宽取「左侧目标与右侧所有操作数」的最大值——注意是所有操作数参与竞赛,不只是直接运算的两个。其二,未指定位宽的整数字面量按 32 位参与运算,这是大量位宽膨胀的祸根。其三,赋值时右侧位宽超出左侧就静默截高位,不足则左侧按规则补零或补符号位(有符号类型补符号位)。

module width_demo( input wire [7:0] a, b, input wire [15:0] wide, output wire [7:0] s1, output wire [8:0] s2, output wire [7:0] hi_byte ); assign s1 = a + b; // 上下文 8 位:加法器 8 位,进位被丢 assign s2 = a + b; // 上下文 9 位:左值参与竞赛,进位保住 assign hi_byte = wide[15:8]; // 显式取高字节 endmodule

s1s2 的差别值得盯着看很久:同样的加法表达式,左值宽度不同,综合出的加法器就差一位。想要「带进位的和」,要么像 s2 那样把左值放宽一位,要么显式拼接 {1'b0, a} + b

02-02-fig01

数组:把总线排成队伍

数组是「向量组成的向量」:reg [7:0] mem [0:255]; 声明一个 256 项、每项八位的存储阵列。数组的每一项都是完整的向量,访问用 mem[addr],配合位选择可以写到 mem[addr][3]。RTL 里数组最常见的用途是寄存器堆与小容量存储器;大容量存储该交给工艺库的 RAM 单元,综合器会把合适的数组推理成块存储,但推理条件苛刻,工程上更常用显式实例化存储单元的方式。

数组的初始化要一条条做,或者用系统任务装载数据:$readmemh 把十六进制文本文件灌进数组,是仿真里预置存储内容的标准手法。数组访问的下标可以是变量,综合成多路选择器;一次访问一个元素,想同时读两项就要自己写两套寻址——这也是「存储器一个周期一读」的硬件规矩在语言层面的投影。

案例展开:计数器溢出静默归零。 背景:某采集系统用八位计数器统计事件数,上位机偶发读到明显偏小的值。操作:复查 RTL 发现 assign event_cnt = event_cnt + 1; 上下文八位,计数到上限后直接回卷;而软件同事按「计数器会一直增大」的假设在读取端做了求平均,回卷污染了统计。结果:RTL 改为九位内部累加并加溢出标志位,软件在标志置位时切换处理分支。解读:截断本身不是 bug,回卷是八位计数器的合法行为;bug 在于两侧对「计数器多宽」没有共同认知。硬件把截断做得太安静,安静到没人会从日志里发现它。变式:无法改位宽时,显式写回卷逻辑并产生中断标志,把「静默截断」变成「显式事件」,是嵌入式协作里更通用的解法。

位宽自查清单

写完一个模块,按这四问过一遍:加法、乘法的结果位宽是否预留了进位与乘积空间(N 位加 M 位,和 N 加一位,积 N 加 M 位);比较运算两侧是否同宽,不同宽时谁被静默扩展;移位的结果宽度是否与预期一致(左移不扩宽,高位直接丢失);拼接符 {} 的总宽度是否数清楚了。这四问覆盖了日常工程里绝大多数位宽案子,第九章的错案清单还会与它们重逢。

位宽问题的三个追问

问:位宽不匹配为什么只有警告没有错误

因为语言把截断定义为合法行为:超宽赋值丢弃高位是标准语义,不是错误。设计者可能就是要低位——比如取哈希的低八位。工具无法区分「故意截断」与「意外截断」,只能警告。这个设计哲学贯穿整门语言:硬件里截断是廉价且常见的操作,把它定为非法反而碍事。代价就是责任完全落在写代码的人身上。

问:如何让位宽检查不靠人眼

三道防线。编译警告全开,位宽类警告一条不豁免;Lint 工具的位宽分析比编译器细,能抓到隐式扩展与有符号混算;最关键的是代码习惯——字面量全带位宽、算术结果显式定位宽,写法规范了,警告才 meaningful。三道防线一起上,位宽案子的发案率能压到接近零。

问:多维数组和存储器 IP 怎么选

看容量与端口需求。几百项以内的寄存器堆用数组,综合成触发器,读写时序自由;大容量用工艺库的存储单元,面积功耗好几个数量级的差距。端口形态也是判据:存储单元通常一两个口、每周期一次访问;数组推理成触发器后访问端口随意。设计前期就要想清楚——后期从数组换存储单元,访问时序的重写量不小。

本节要点回顾

  • 位序用降序[N-1:0] 是工程惯例,升序降序混用是接线错位的高发原因;
  • 变基址取段用加冒号语法din[base +: 4],边界可以是变量,固定段写法不行;
  • 位宽三条规则:上下文竞赛定宽度、字面量默认 32 位、超宽静默截断;
  • 数组按项访问:下标可综合为多路选择器,大容量存储交给库单元;
  • 截断无告警:溢出回卷是合法行为,跨软硬件的位宽共识必须显式建立。

宽度登记完毕,下一节给电路装上「可配置的预埋条款」——参数与常量体系。


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