LJgibbs · 2020年09月18日

HDLBits:在线学习 Verilog (三 · Problem 10-14)

转载自:知乎

本系列文章将和读者一起巡礼数字逻辑在线学习网站 HDLBits 的教程与习题,并附上解答和一些作者个人的理解,相信无论是想 7 分钟精通 Verilog,还是对 Verilog 和数电知识查漏补缺的同学,都能从中有所收获。

首先附上传送门:Vector0 - HDLBits

Problem 10 : Vectors

什么是 Verilog 中的向量(vector)?向量是一组 wire 信号的集合,通过赋予这一组信号的集合一个名称,以便于访问其中的 wire 信号。

向量类似于总线,一般将向量视为位宽超过 1 位的 wire 信号,不是特别在意向量这个概念本身。

举个栗子

wire [7:0] w ; 声明了一个 8 bit 位宽的信号,向量名为 w,等价于 8 个 1bit 位宽的 wire 信号。

verilog
assign w_0 = w[0]; //取出了向量中最低位的 wire 信号

如果你对 C 语言的数组非常熟悉的话,请注意声明向量时,位宽位于向量名之前。但在片选向量中某个 bit 时,使用的语法同 C 语言数组中取出某个数的语法相同。

verilog
wire [99:0] my_vector;      // Declare a 100-element vector 
assign out = my_vector[10]; // Part-select one bit out of the vector 

在同时声明多个向量时,位宽对于声明的多个向量都是起作用的,比如:

verilog
wire [7:0] x,y;  //y 也被声明为位宽为 8 的向量

牛刀小试

构造一个电路,拥有 1 个 3 bit 位宽的输入端口,4 个输出端口。其中一个输出端口直接输出输入的向量,剩下 3 个输出端口分别各自输出 3 bit 中的 1 bit。

上图中,箭头上的小斜杠旁边的数字代表该向量(总线)的位宽,用于将向量同 wire 信号区别开来。

解答与分析

verilog
module top_module(
    input [2:0] vec, 
    output [2:0] outv,
    output o2,
    output o1,
    output o0
);
    
    assign outv = vec;

    // This is ok too: assign {o2, o1, o0} = vec;
    assign o0 = vec[0];
    assign o1 = vec[1];
    assign o2 = vec[2];
    
endmodule

本题中我们练习了向量的定义以及如何片选向量中的某个 bit。

如果想要片选多个 bit,那么可以通过如下操作实现,该语法在 C 语言中不存在,但类似 Python 中的切片语法。

verilog
assign w = vec[1:0]; 

Problem 11 : Vector in more detail

向量组合了多个相关 wire 信号,通过向量名可以更方便地访问向量中信号。本题中我们将讨论有关向量的更多细节。

声明向量

向量在声明时,必须遵循:

text
type [upper:lower] vector_name;

其中 type 指定了向量的类型,一般为 wire 或者 reg 型。关于 reg 型,会在后续课程过程块的介绍中引入。如果向量为模块的输入输出端口,那么可以在 type 中添加 input/output 定义。

举一堆栗子

verilog
wire [7:0] w;         // 8-bit wire
reg  [4:1] x;         // 4-bit reg
output reg [0:0] y;   // 1-bit reg  output port (但仍然是一个向量)
input wire [3:-2] z;  // 6-bit wire input (在位宽中使用负数作为 index 是可以的,代表倒数第二位)
output [3:0] a;       // 4-bit output wire. wire 为默认定义,在没有显式声明的情况下
wire [0:7] b;         // 8-bit wire b[0]是这个向量的 最高位 MSB(most-significant bit)

这里你需要了解一个向量的比特顺序(_endianness_)信息,比特顺序取决于向量的 LSB 是向量的高位还是地位。比如声明为 [3:0] w 的向量,LSB 是 w[0],如果声明为 [0:3] w,那么 w[3] 是 LSB 。LSB 指的是二进制数中权值最低的一位。

在 Verilog 语法中,你可以将向量声明为 [3:0], 这种语法最为常见,但也可以将向量声明为 [0:3] 。这都是可以的,但必须在声明和使用时保持一致。如果声明为 wire [3:0] w ,但使用 w[0:3]赋值,这是不允许的。保持前后如一的比特顺序是很重要的一点,一些你挠破头都定位不了的 BUG 可能就是字节顺序不一致导致的。

变量隐式声明的危害

你知道吗,变量的隐式声明是 Verilog 中 BUG 的一大来源。

信号变量有两种声明方式,一是使用 wire 或者 assign 语句进行显示声明和定义,二是综合器的隐式声明和定义。

当你将一个未定义声明的信号连接到模块的输入输出端口时,综合器会“热心”地帮助你声明这个信号。但我可以向你保证,综合器没有厉害到能通过上下文,察言观色,“热心而正确”地帮你声明信号,它只会将其声明为 1 bit wire 型信号,当你本来需要使用一个超过 1 bit 的向量,但又忘记声明时,综合器往往就好心办坏事了。

(当然综合器会在这个生成 Warning,所以查看下 Warning 是查找 BUG 的好办法)

verilog
wire [2:0] a, c;    // Two vectors 
assign a = 3'b101;  // a = 101 
assign b = a;       // b =   1  隐式声明并定于了 b
wire assign c = b;  // c = 001  <-- bug 来了 b 被 coder 默认为和 a 相同的 3'b101,但其实 b 只有 1bit宽
my_module i1 (d,e); // d e 都被隐式声明为 1bit wire
                    //如果模块期望的是 vector 那么 BUG 就产生了

隐式声明的错误很容易在连接 IP 核的时候产生,从 IP 核模板文件复制来 IP 核模块后。往往会忘记声明连接 IP 模块之间的中间变量,而这些变量的隐式声明就可能被综合器“好心办了坏事”。

通过添加 `default_nettype none 宏定义会关闭隐式声明功能,那么这样一来,使用未声明的变量就会变成一个 Error 而不再只是 Warning。

unpacked vs. packed 数组

在声明向量时,一般向量的位宽写在向量名之前。位宽定义了向量的 packed 维度,该向量中每位信号都被视作一个块进行操作(在仿真中,硬件中有所不同)。unpacked 维度定义在向量名之后,通常用来定义向量数组。

verilog
reg [7:0] mem [255:0];   // 256 unpacked elements, each of which is a 8-bit packed vector of reg.
reg mem2 [28:0];         // 29 unpacked elements, each of which is a 1-bit reg.

(这段翻得磕绊,简单得说定义在向量名之前的是向量的位宽,定义在向量名之后的维度可以理解为向量数组的长度,同 C 语言中的数组长度概念相同,一般用来对存储器建模。)

获取向量元素:片选

通过向量名可以获得整个向量,在下方的 assign 语句中,向量名 a 代表了向量中的所有比特为信号。

verilog
assign w = a;

在 assign 赋值操作中,如果等号左右两侧信号的位宽不同,那么就会进行截断或者补零操作。

左侧信号位宽大于右侧信号位宽,右值的低位赋予左值对应的低位,左值高位的部分赋零

左侧信号位宽小于右侧信号位宽,右值的低位赋予左值对应的低位,右值高位的部分直接被截断。即保留右值的低位。

使用 [] 可以对信号进行片选,选择信号中特定几位比特,以下是一些片选的例子。

verilog
w[3:0]      // Only the lower 4 bits of w
x[1]        // The lowest bit of x
x[1:1]      // ...also the lowest bit of x
z[-1:-2]    // Z 最低两位
b[3:0]      // 如果 b 在声明时 声明为 wire [0:3] b;则不能使用 b [3:0]进行选择
b[0:3]      // b的高四位.
assign w[3:0] = b[0:3];    // 将 b 的高位赋予 w 的低位 w[3]=b[0], w[2]=b[1], etc.

牛刀小试

分别输出 16 位输入信号的高 8 位 和低 8 位。

解答与分析

`default_nettype none     // Disable implicit nets. Reduces some types of bugs.
module top_module( 
    input wire [15:0] in,
    output wire [7:0] out_hi,
    output wire [7:0] out_lo );
    assign out_hi = in[15:8];
    assign out_lo = in[7:0];
endmodule

这里使用 [ : ] 语法进行了最朴素和常用的信号片选。

Problem 12 : Vector part select

牛刀小试

一个 32 位的向量可以看做由 4 个字节组成(bits[31:24],[23:16],等等)。构建一个电路,将输入向量的字节顺序颠倒,也就是字节序大小端转换。

aaaaaaaabbbbbbbbccccccccdddddddd => ddddddddccccccccbbbbbbbbaaaaaaaa

这项操作常见于不同的大小端体系之间的数据交换,比如 x86 体系使用小端模式存储数据,而因特网协议中均使用大端模式,数据在本地和网络进行数据交换之前,均要进行大小端转换。

解答与分析

verilog
module top_module (
    input [31:0] in,
    output [31:0] out
);

    assign out[31:24] = in[ 7: 0];    
    assign out[23:16] = in[15: 8];    
    assign out[15: 8] = in[23:16];    
    assign out[ 7: 0] = in[31:24];    
    
endmodule

这里是官方的解答,其实也可以用一句 assign 语句代替,使用 {} 位拼接符,我们将在后续的课程中学到。

verilog
assign out = {in[7-:8],in[15-:8],in[23-:8],in[31-:8]};

Problem 13 : Bitwise operators

本题将关注逐位逻辑运算符(&)和逻辑运算符(&&)之间的差别

逐位逻辑运算符:对于 N 比特输入向量之间的逻辑比较,会在 N 比特上逐位进行,并产生一个 N 比特长的运算结果。

逻辑运算符:任何类型的输入都会被视作布尔值,_零_->,_非零_->,将布尔值进行逻辑比较后,输出一个 1 比特的结果。

牛刀小试

模块有两个 3bit 宽的输入变量 a,b ,要求输出 a,b 逐位或的,a,b 逻辑或以及 a,b 按位取反的结果,其中 b 在高位。

解答与分析

verilog
module top_module( 
    input [2:0] a,
    input [2:0] b,
    output [2:0] out_or_bitwise,
    output out_or_logical,
    output [5:0] out_not
);
    assign out_or_bitwise = a | b;
    assign out_or_logical = a || b;

    assign out_not[2:0] = ~a;    // Part-select on left side is o.
    assign out_not[5:3] = ~b;    //Assigning to [5:3] does not conflict with [2:0]
endmodule

本题分别练习了逐位逻辑比较以及逻辑操作,只从题目的角度来说,只要注意观察输出信号的位宽即可。

Problem 14 : Four-input gates

牛刀小试

分别构建一个 4 输入与门,或门以及异或门。

解答与分析

verilog
module top_module( 
    input [3:0] in,
    output out_and,
    output out_or,
    output out_xor
);
    assign out_and = & in;
    assign out_or  = | in;
    assign out_xor = ^ in;
endmodule

4 输入逻辑门,转换为 Verilog 的思想是将 4 个输入变量进行逻辑操作,得到 1 比特结果,在本题中,最简单的写法是

verilog
assign out_and = in[3] & in[2] & in[1] & in[0];

解答中的写法使用了缩减运算符的语法,和将位展开的写法相同,但更加简便,我们将在后续的课程中加以展开。

推荐阅读

关注此系列,请关注专栏FPGA的逻辑
推荐阅读
关注数
10512
内容数
513
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息