HDLBits答案(2)_Verilog向量基础
HDLBits_Verilog向量基础
向量
如果把单个信号比作一根电线,那么向量就可以理解为一束电线。就像生活中常见的多芯电缆,向量把多个相关的信号打包在一起处理,让代码更简洁、更易读。
在Verilog中,向量维度的声明放在向量名之前,这一点与C语言不同;但使用向量时,维度信息仍然放在向量名之后。
1 | wire [99:0] my_vector; // 声明一个100根电线的向量 |
打个比方,声明向量就像给一排房子编号,你可以选择访问整排房子,也可以只访问其中一间。
向量的更多知识
声明向量
向量声明的基本语法是:
1 | type [upper:lower] vector_name; |
其中,type指定了向量的数据类型,通常为reg或wire类型。如果声明的是输入、输出向量,类型前还需要加上input和output:
1 | wire [7:0] w; // 8位wire向量 |
这里有一个很重要的概念:大小端。向量的大小端决定了最低有效位(LSB)具有较低的索引(如[3:0])还是较高的索引(如[0:3])。一旦用特定的索引规则定义了向量,就必须用相同的方式去使用它!
小贴士: 向量声明时数字的顺序很重要,它决定了向量是以大端存储还是小端存储。
- 若声明
wire [3:0] w,则w[0]为最低位,w[3]为最高位- 若声明为
wire [0:3] w,则结果完全相反
因此在向量的定义和使用时一定要保持大小端一致!
未定义的中间变量容易出错
使用wire型变量前请先声明,不要图一时之便不定义就直接拿来做中间变量,这样导致的错误会很难发现。
1 | wire [2:0] a, c; // 两个向量 |
向量名前为维度,向量名后为数目,不写统统视作1。可以这样类比:
- 维度就像每个房子的大小
- 数目就像房子的总数目
1 | reg [7:0] mem [255:0]; // 256个 unpacked 元素,每个都是8位 packed reg向量 |
获取向量元素:部分选择
访问整个向量直接使用向量名即可,例如:assign w = a;。如果左右两边的长度不匹配,则根据情况对向量进行补零或截断。
向量的部分选择操作可以访问向量的一部分:
1 | w[3:0] // 只取w的低4位 |
注意: 取用向量中特定维度的数时,一定要注意与向量的定义一致,注意大小端匹配的问题!
题目:字节序转换
题目描述:
构建一个电路,将一个4字节数的字节顺序调转,常用于数据的大小端转换。
AaaaaaaaBbbbbbbbCcccccccDddddddd => DdddddddCcccccccBbbbbbbbAaaaaaaa
Solution:
1 | module top_module( |
小贴士: 在向量赋值的左右两端都可以对部分数据进行操作,这让我们可以灵活地处理数据的不同部分。
对向量进行门操作
位操作运算符 vs 逻辑操作运算符
这是另一个容易混淆的地方!让我们搞清楚:
对两个N比特位宽的向量而言:
- 按位操作: 输出为N比特位宽向量,每一位分别运算
- 逻辑操作: 将整个向量视为布尔值(true=非零,false=零),产生1比特输出
打个比方:
- 按位操作就像逐个检查一排开关,每个开关都得出一个结果
- 逻辑操作就像判断”这一整排开关中是否有打开的”,只给出一个是或否的答案
题目1:向量的按位操作和逻辑操作
题目描述1: 对向量特定部分的数据进行按位操作和逻辑操作。
Solution 1:
1 | module top_module( |
题目2:缩位运算符
题目描述2:
对输入向量进行按位的与、或和异或操作。
Solution 2:
1 | module top_module( |
小贴士: 合理利用缩位运算符可以精简代码!按位进行逻辑运算,结果为1比特数。
- 与缩位运算符:
&- 或缩位运算符:
|- 异或缩位运算符:
^- 还可以组成复合运算符:
~&,~|,~^等
向量拼接操作
向量拼接操作就像把几根绳子接在一起。不过,拼接操作需要知道待拼接的各向量的位宽,否则你怎么知道结果的位宽呢?
所以 {1,2,3} 是无效的,因为未指定各向量位宽。但 {3'd1, 3'd2, 3'd3} 就是有效的!
向量的拼接操作在赋值的左右两端均可使用:
1 | input [15:0] in; |
注意:
- 位宽合并需要知道各成分的确切位宽,否则结果位宽不定
- 位宽合并时在assign两端均可实现,最好左右两端位宽相同,否则未被指定的数据会被置零
题目:向量拼接与分割
题目描述:
给定几个输入向量,将它们拼接在一起,然后分割成几个输出向量。有6个5位输入向量a, b, c, d, e, f,总共30位输入。对于32位的输出,有4个8位的输出向量w、x、y和z。输出应该是输入向量与两个”1”位的串联:

Solution:
1 | module top_module ( |
向量翻转
题目描述:
给定一个8位输入向量[7:0],颠倒它的位顺序。
Solution 1:
1 | module top_module( |
Solution 2:
1 | module top_module( |
小贴士: 建议使用Solution 2实现,可扩展性强!如果向量变成100位,只需要改一下循环边界就行。
向量复制操作
向量复制操作允许重复一个向量并将它们连接在一起,语法是:
1 | {num{vector}} |
例如:
1 | {5{1'b1}} // 5'b11111 (或者 5'd31 或者 5'h1f) |
小贴士: 复制向量有捷径,外层花括号带重复次数。主要用于符号位的拓展!
例如:
- 4’b0101 带符号扩展为8bit: 8’b00000101
- 4’b1101 带符号扩展为8bit: 8’b11111101
题目1:符号位扩展
题目描述: 建立一个电路,将8位数字扩展到32位。这需要将符号位的24个副本(即位[7]复制24次)与8位数字本身连接起来。
Solution:
1 | module top_module ( |
题目2:多向量复制
题目描述2: 给定5个1位信号(a、b、c、d和e),计算25位输出向量中所有25个成对的1位比较。如果两个被比较的位相等,输出应该是1。
1 | out[24] = ~a ^ a; // a == a, 所以out[24]总是1 |

思路: 该操作可以定位特定signal所在位置,两向量重复数据有规律可循,可用复制的方式来产生,简化代码。
Solution:
1 | module top_module ( |
入门者避坑指南
向量操作是Verilog设计中非常基础但也容易出错的部分,让我们看看初学者常踩的坑:
错误1: 向量大小端不匹配
错误表现:1
2
3
4
5
6
7module bad_example (
input [3:0] a, // a[3]是最高位,a[0]是最低位
output [0:3] b // b[0]是最高位,b[3]是最低位
);
assign b = a; // 虽然语法没问题,但位的对应关系可能出错!
assign b[0] = a[0]; // 错误!大小端顺序不同,这样赋值会混淆最高/最低位
endmodule
错误原因:
[3:0]表示索引从3降到0,3是最高位[0:3]表示索引从0升到3,0是最高位- 虽然整体赋值时Verilog会自动处理,但单独访问某一位时很容易出错
正确做法:1
2
3
4
5
6
7module good_example (
input [3:0] a,
output [3:0] b // 保持一致的大小端
);
assign b = a; // 简单直接
assign b[0] = a[0]; // 清楚明确:最低位赋值给最低位
endmodule
调试技巧:
- 整个项目中统一使用一种大小端约定(建议用
[N:0],低位在右) - 如果必须混用,一定要在注释里写清楚每个向量的大小端
错误2: 隐式创建的1位wire
错误表现:1
2
3
4
5
6
7
8module bad_example (
input [3:0] a,
output [3:0] y
);
// 错误!b没有声明,会被隐式创建为1位wire
assign b = a + 1'b1;
assign y = b; // y[3:1] 会是0,只有y[0]是有效的!
endmodule
错误原因:
Verilog允许不声明就使用wire,但会默认创建1位的wire。当你把多位向量赋值给它时,只会保留最低位。
正确做法:1
2
3
4
5
6
7
8module good_example (
input [3:0] a,
output [3:0] y
);
wire [3:0] b; // 显式声明
assign b = a + 1'b1;
assign y = b;
endmodule
调试技巧:
- 养成好习惯:所有wire都先声明再使用
- 开启编译器的警告选项,它会提醒你有隐式声明的wire
错误3: 拼接操作中未指定位宽的常量
错误表现:1
2
3
4
5
6
7module bad_example (
input [1:0] a, b,
output [5:0] y
);
// 错误!1和2没有指定位宽
assign y = {1, a, 2, b};
endmodule
错误原因:
拼接操作要求每个元素都有明确的位宽,否则编译器无法确定最终结果的位宽。
正确做法:1
2
3
4
5
6
7
8
9module good_example (
input [1:0] a, b,
output [5:0] y
);
// 每个元素都指定位宽
assign y = {2'd1, a, 2'd2, b}; // 2+2+2+2=8? 哦,等一下,让我们重新算...
// 如果要6位,可以这样:
assign y = {1'b1, a, 1'b0, b}; // 1+2+1+2=6位
endmodule
调试技巧:
- 拼接操作时,在纸上或注释里算好每部分的位宽
- 确保加起来的总位宽和目标一致
错误4: 部分选择时方向不匹配
错误表现:1
2
3
4
5
6
7module bad_example (
input [3:0] a, // 声明是 [3:0],从高到低
output [1:0] y
);
// 错误!部分选择的方向和声明不一致
assign y = a[0:1]; // 应该是 a[1:0]
endmodule
错误原因:
向量声明时是[3:0],表示索引从3降到0,那么部分选择时也必须保持同样的顺序。
正确做法:1
2
3
4
5
6
7module good_example (
input [3:0] a,
output [1:0] y
);
// 方向一致: 从高索引到低索引
assign y = a[1:0];
endmodule
调试技巧:
- 记住:声明时怎么写,使用时就怎么写
- 如果声明是
[upper:lower],使用时也必须是[higher:lower]
错误5: 复制操作的花括号嵌套错误
错误表现:1
2
3
4
5
6
7
8
9module bad_example (
input a,
output [3:0] y
);
// 错误!花括号位置不对
assign y = 4{a}; // 应该是 {4{a}}
// 或者这样也不对
assign y = {4, a}; // 这是拼接,不是复制
endmodule
错误原因:
复制操作需要两层花括号:
- 内层:
{a}表示被复制的内容 - 外层:
{4{...}}表示复制4次
正确做法:1
2
3
4
5
6
7module good_example (
input a,
output [3:0] y
);
// 正确的复制操作
assign y = {4{a}}; // 把a复制4次
endmodule
调试技巧:
- 复制操作口诀:外层套次数,内层套数据
- 区分拼接(
{a,b,c})和复制({3{a}})
本章小结
这一章我们学习了向量操作的相关知识,这些都是设计中非常实用的技巧:
- 向量声明: 理解大小端的区别,并在设计中保持一致
- 部分选择: 灵活访问向量的任意部分,但要注意方向匹配
- 拼接操作: 把多个向量接在一起,每个元素都需要明确位宽
- 复制操作:
{num{vector}},快速生成重复模式,常用于符号扩展 - 缩位运算符:
&,|,^,对向量所有位进行操作,结果为1位
向量就像给信号”打包”,让我们能够用更简洁的代码描述复杂的硬件连接。熟练掌握这些操作,会让你的代码既清晰又高效!




