HDLBits第二章
- 向量0(Vector0)
矢量用于使用一个名称对相关信号进行分组,以便更方便地操作。例如,wire [7:0] w; 声明了一个名为w的 8 位向量,它在功能上等同于具有 8 条单独的线。
请注意,向量的声明将维度放在向量名称之前,这与 C 语法相比是不寻常的。然而,部分选择了尺寸后,如你所期望的矢量名称。
wire [99:0] my_vector; // Declare a 100-element vector
assign out = my_vector[10]; // Part-select one bit out of the vector
构建一个具有一个 3 位输入的电路,然后输出相同的向量,并将其拆分为三个单独的 1 位输出。将输出连接o0到输入向量的位置 0、o1位置 1 等。
在图中,旁边带有数字的刻度线表示向量(或“总线”)的宽度,而不是为向量中的每一位绘制单独的线。
代码实现:
//Vectors
module top_module (
input wire [2:0] vec,
output wire [2:0] outv,
output wire o2,
output wire o1,
output wire o0 ); // Module body starts after module declaration
assign outv = vec;
assign o2 = vec[2];
assign o1 = vec[1];
assign o0 = vec[0];
endmodule
验证结果:
2. 向量1(Vector1)
矢量用于使用一个名称对相关信号进行分组,以便更方便地操作。例如,wire [7:0] w; 声明一个名为w的 8 位向量,相当于有 8 条单独的线。
声明向量
向量必须声明:
type [upper:lower] vector_name;
type指定向量的数据类型。这通常是wire或reg。如果您要声明输入或输出端口,则类型还可以另外包括端口类型(例如,input或output)。例如:
wire [7:0] w; // 8-bit wire
reg [4:1] x; // 4-bit reg
output reg [0:0] y; // 1-bit reg that is also an output port (this is still a vector)
input wire [3:-2] z; // 6-bit wire input (negative ranges are allowed)
output [3:0] a; // 4-bit output wire. Type is 'wire' unless specified otherwise.
wire [0:7] b; // 8-bit wire where b[0] is the most-significant bit.
向量的字节序(或非正式地,“方向”)是最低有效位是否具有较低的索引(小端,例如 [3:0])或较高的索引(大端,例如 [ 0:3])。在 Verilog 中,一旦用特定的字节序声明了向量,就必须始终以相同的方式使用它。例如,vec[0:3]在vec声明时写入wire [3:0] vec;是非法的。与字节序保持一致是一种很好的做法,因为如果分配或一起使用不同字节序的向量,就会发生奇怪的错误。
隐式网络
隐式网络通常是难以检测的错误的来源。在 Verilog 中,网络类型信号可以通过assign语句或通过将未声明的东西附加到模块端口来隐式创建。隐式网络始终是一位连线,如果您打算使用矢量,则会导致错误。可以使用该`default_nettype none指令禁用隐式网络的创建。
wire [2:0] a, c; // Two vectors
assign a = 3'b101; // a = 101
assign b = a; // b = 1 implicitly-created wire
assign c = b; // c = 001 <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide if not declared.
// This could be a bug if the port was intended to be a vector.
添加`default_nettype none会使第二行代码出错,从而使错误更加明显。
未打包与打包数组
您可能已经注意到,在声明中,向量索引写在向量名称之前。这声明了数组的“打包”维度,其中位被“打包”到一个 blob 中(这与模拟器相关,但与硬件无关)。将解压后的尺寸宣布后的名称。它们通常用于声明内存数组。由于 ECE253 没有涵盖内存数组,我们在本课程中没有使用压缩数组。有关更多详细信息,请参阅http://www.asic-world.com/systemverilog/data_types10.html。
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.
访问向量元素:部分选择
访问整个向量是使用向量名称完成的。例如:
assign w = a;
获取整个 4 位向量a并将其分配给整个 8 位向量w(声明取自上面)。如果左右两边的长度不匹配,则根据需要进行零扩展或截断。
部分选择运算符可用于访问向量的一部分:
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] // Two lowest bits of z
b[3:0] // Illegal. Vector part-select must match the direction of the declaration.
b[0:3] // The *upper* 4 bits of b.
assign w[3:0] = b[0:3]; // Assign upper 4 bits of b to lower 4 bits of w. w[3]=b[0], w[2]=b[1], etc.
练习
构建一个组合电路,将输入半字(16 位, [15:0] )分成低 [7:0] 和高 [15:8] 字节。
代码实现:
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
验证结果:
3. 向量2(Vector2)
可以将 32 位向量视为包含 4 个字节(位 [31:24]、[23:16] 等)。构建一个电路来反转4 字节字的字节顺序。
AaaaaaaaBbbbbbbbCccccccccDddddddd => DdddddddCccccccccBbbbbbbbAaaaaaaa
当需要交换一段数据的字节序时,通常使用此操作,例如在小端 x86 系统和许多 Internet 协议中使用的大端格式之间。
代码实现:
module top_module(
input [31:0] in,
output [31:0] out );//
// assign out[31:24] = ...;
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
验证结果:
4. 矢量门(Vectorgates)
构建一个具有两个 3 位输入的电路,用于计算两个向量的按位或,两个向量的逻辑或,以及两个向量的逆 (NOT)。将 的倒数b放在out_not(即位 [5:3])的上半部分,将 的倒数a放在下半部分。
按位与逻辑运算符
早些时候,我们提到有各种布尔运算符的按位和逻辑版本(例如,norgate)。使用向量时,两种运算符类型之间的区别变得很重要。两个 N 位向量之间的按位运算复制向量每一位的操作并产生 N 位输出,而逻辑运算将整个向量视为布尔值(真 = 非零,假 = 零)和产生 1 位输出。
查看模拟波形,了解按位或和逻辑或的不同之处。
代码实现:
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[2]|b[2],a[1]|b[1],a[0]|b[0]};
assign out_or_logical = a || b;
assign out_not = {~b[2],~b[1],~b[0],~a[2],~a[1],~a[0]};
endmodule
验证结果:
5. 四输入门(Gates4)
构建一个具有四个输入的组合电路in[3:0]。
有3个输出:
out_and:4 输入与门的输出。
out_or:4 输入或门的输出。
out_xor:4 输入异或门的输出。
代码实现:
module top_module(
input [3:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = in[3] & in[2] & in[1] & in[0];
assign out_or = in[3] | in[2] | in[1] | in[0];
assign out_xor = in[3] ^ in[2] ^ in[1] ^ in[0];
endmodule
验证结果:
6. 向量3(Vector3)
部分选择用于选择向量的部分。连接运算符{a,b,c}用于通过将向量的较小部分连接在一起来创建更大的向量。
{3'b111, 3'b000} => 6'b111000
{1'b1, 1'b0, 3'b101} => 5'b10101
{4'ha, 4'd10} => 8'b10101010 // 4'ha and 4'd10 are both 4'b1010 in binary
连接需要知道每个组件的宽度(或者你怎么知道结果的长度?)。因此,{1, 2, 3}是非法的并导致错误消息:concatenations 中不允许使用未定义大小的常量。
连接运算符可用于赋值的左侧和右侧。
input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in; // Swap two bytes. Right side and left side are both 16-bit vectors.
assign out[15:0] = {in[7:0], in[15:8]}; // This is the same thing.
assign out = {in[7:0], in[15:8]}; // This is different. The 16-bit vector on the right is extended to
// match the 24-bit vector on the left, so out[23:16] are zero.
// In the first two examples, out[23:16] are not assigned.
练习
给定几个输入向量,将它们连接在一起,然后将它们分成几个输出向量。有六个 5 位输入向量:a、b、c、d、e 和 f,总共 30 位输入。有四个 8 位输出向量:w、x、y 和 z,用于 32 位输出。输出应该是输入向量的串联,后跟两个1位:
代码实现:
module top_module (
input [4:0] a, b, c, d, e, f,
output [7:0] w, x, y, z );
// assign { ... } = { ... };
assign w = {a[4:0],b[4:2]};
assign x = {b[1:0],c[4:0],d[4]};
assign y = {d[3:0],e[4:1]};
assign z = {e[0],f[4:0],1'b1,1'b1};
endmodule
验证结果:
7. 向量反转(Vectorr)
给定一个 8 位输入向量 [7:0],反转其位顺序。
代码实现:
module top_module(
input [7:0] in,
output [7:0] out
);
assign out = {in[0],in[1],in[2],in[3],in[4],in[5],in[6],in[7]};
endmodule
验证结果:
8. 向量4(Vector4)
所述并置运算符允许矢量串联起来以形成更大的载体。但是有时候你想把同一个东西连接在一起很多次,做诸如assign a = {b,b,b,b,b,b};之类的事情仍然很乏味。
复制运算符允许重复向量并将它们连接在一起:
{num{向量}}
这种复制载体由NUM倍。num必须是常数。两组大括号都是必需的。
例如:
{5{1'b1}} // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}} // The same as {a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 9'b101_110_110. It's a concatenation of 101 with
// the second vector, which is two copies of 3'b110.
练习
看到复制运算符的一个常见地方是将较小的数字符号扩展为较大的数字,同时保留其有符号值。这是通过将较小数字的符号位(最高有效位)复制到左侧来完成的。例如,将4’b 0 101 (5)符号扩展为 8 位的结果为8’b 0000 0101 (5),而将4’b 1 101 (-3)符号扩展为 8 位的结果为8’b 1111 1101 (-3)。
构建一个将 8 位数字符号扩展为 32 位的电路。这需要将符号位的 24 个副本(即,复制位 [7] 24 次)和 8 位数字本身串联起来。
代码实现:
module top_module (
input [7:0] in,
output [31:0] out );//
// assign out = { replicate-sign-bit , the-input };
assign out = {{24{in[7]}},in};
endmodule
验证结果:
9. 向量5(Vector5)
给定 5 个 1 位信号(a、b、c、d 和 e),计算 25 位输出向量中的所有 25 个成对的 1 位比较。如果被比较的两位相等,则输出应为 1。
out[24] = ~a ^ a; // a == a, so out[24] is always 1.
out[23] = ~a ^ b;
out[22] = ~a ^ c;
...
out[ 1] = ~e ^ d;
out[ 0] = ~e ^ e;
如图所示,使用复制和串联运算符可以更轻松地完成此操作。
顶部向量是每个输入的 5 次重复的串联
底部向量是 5 个输入的串联的 5 个重复
代码实现:
module top_module (
input a, b, c, d, e,
output [24:0] out );//
// The output is XNOR of two vectors created by
// concatenating and replicating the five inputs.
// assign out = ~{ ... } ^ { ... };
wire [24:0]abcde1;
wire [24:0]abcde2;
assign abcde1 = {{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}};
assign abcde2 = {5{a,b,c,d,e}};
assign out[24] = ~abcde1[24] ^ abcde2[24];
assign out[23] = ~abcde1[23] ^ abcde2[23];
assign out[22] = ~abcde1[22] ^ abcde2[22];
assign out[21] = ~abcde1[21] ^ abcde2[21];
assign out[20] = ~abcde1[20] ^ abcde2[20];
assign out[19] = ~abcde1[19] ^ abcde2[19];
assign out[18] = ~abcde1[18] ^ abcde2[18];
assign out[17] = ~abcde1[17] ^ abcde2[17];
assign out[16] = ~abcde1[16] ^ abcde2[16];
assign out[15] = ~abcde1[15] ^ abcde2[15];
assign out[14] = ~abcde1[14] ^ abcde2[14];
assign out[13] = ~abcde1[13] ^ abcde2[13];
assign out[12] = ~abcde1[12] ^ abcde2[12];
assign out[11] = ~abcde1[11] ^ abcde2[11];
assign out[10] = ~abcde1[10] ^ abcde2[10];
assign out[9] = ~abcde1[9] ^ abcde2[9];
assign out[8] = ~abcde1[8] ^ abcde2[8];
assign out[7] = ~abcde1[7] ^ abcde2[7];
assign out[6] = ~abcde1[6] ^ abcde2[6];
assign out[5] = ~abcde1[5] ^ abcde2[5];
assign out[4] = ~abcde1[4] ^ abcde2[4];
assign out[3] = ~abcde1[3] ^ abcde2[3];
assign out[2] = ~abcde1[2] ^ abcde2[2];
assign out[1] = ~abcde1[1] ^ abcde2[1];
assign out[0] = ~abcde1[0] ^ abcde2[0];
endmodule
验证结果: