向量与向量运算:Vector0 ~ Vector5(Verilog Language → Vectors)
1.载体

vec:这是这个模块的输入。
斜杠和数字"3":这是图中最关键的细节之一。在数字电路图中,一根线上画一条斜杠加上数字"3",表示这不是一根线,而是3根线并排组成的一束线(总线/向量)。
你可以把它想象成一根三芯电缆,里面同时传输3个二进制位(0或1)。
左边的蓝色字 input 2:0 vec 是它的官方身份证:"我叫 vec,我是一个3位的输入总线,我的编号是第2位到第0位"。右边有两组输出:
outv:同样带斜杠和"3",说明它也是一个3位的输出总线(三芯电缆)。
o2, o1, o0:这三根是独立的单芯线(1位宽),分别输出3个单独的信号。主干直连:输入总线 vec 直接连向了输出总线 outv。这意味着:不管你输入什么,我都原封不动地复制给 outv。就像一根三芯电缆接到了另一个三芯插座上。
总线拆分(提取单根):从 vec 这束总线里,分出了三根细线,分别接到了 o2、o1 和 o0。
仔细看图上小字标注:
连到 o2 的线,标着 2:表示从总线里抽出第2位(最高位)。
连到 o1 的线,标着 1:表示从总线里抽出第1位(中间位)。
连到 o0 的线,标着 0:表示从总线里抽出第0位(最低位)。
`default_nettype none
module top_module(
input [2:0] vec,
output [2:0] outv,
output o2, o1, o0
);
assign outv = vec; // 整体赋值(也可以单独赋值,但是那样的话要写很多语句)
assign o2 = vec[2]; // 提取第2位
assign o1 = vec[1]; // 提取第1位
assign o0 = vec[0]; // 提取第0位
endmodule

(Emm~为什么这是解决的第一个题呢,因为我是到这题才发现网站可以提交代码)
2.向量
类型指定向量的数据类型。这通常是线路或调节器。如果你声明输入端口或输出端口,类型还可以包含端口类型(例如输入端口或输出端口)。举几个例子:
wire [7:0] w; // 8-bit wire(8比特位宽的线)
reg [4:1] x; // 4-bit reg(4比特位宽的寄存器)
output reg [0:0] y; // 1位寄存器,同时也是一个输出端口(这仍然是一个向量)
input wire [3:-2] z; // 6位线输入(允许负数范围)
output [3:0] a; // 4位输出线。类型是 'wire',除非另有说明。
wire [0:7] b; // 8位线,其中 b[0] 是最高有效位。
向量的端序性(或非正式地说,"方向")是指最低有效位的索引较低(小端序,例如 3:0)还是更高的指标(大端序,例如 0:3)。在Verilog中,一旦向量被指定为特定端序,必须始终以相同方式使用。例如,在vec被wire 3:0 vec;宣告时写作vec0:3是非法的。保持一致音序是个好习惯,因为如果分配或使用不同的词序向量,会出现奇怪的错误。
隐式网络常常是难以检测的漏洞来源。 在Verilog中,网络类型信号可以通过语assign句或将未声明的内容附加到模块端口隐式创建。隐式网总是一位线,如果你打算用向量线,会有bug。禁用隐式网的创建可以通过指令`default_nettype none完成。
wire [2:0] a, c; // 两个向量
assign a = 3'b101; // a = 101
assign b = a; // b = 1 隐式创建的线
assign c = b; // c = 001 <-- bug
my_module i1 (d,e); //如果没有声明,d 和 e 默认是 1 位宽。
如果不声明,d 和 e 隐式为一比特宽。
如果端口本来是向量,这可能是个bug。
添加`default_nettype none代码会让第三行代码变成错误,这样bug就更明显了。
你可能注意到,在声明中,向量索引写在向量名称之前。这会声明数组的"打包"维数,即比特被"打包"成一个斑点(这在模拟器中有用,但在硬件中不重要)。未压缩的维数在名称后声明。它们通常用于声明内存数组。由于ECE253没有涉及内存数组,因此我们这门课没有使用打包数组。详情请参见 http://www.asic-world.com/systemverilog/data_types10.html。
reg [7:0] mem [255:0]; // 256 个解包元素,每个都是一个 8 位的寄存器打包向量。
reg mem2 [28:0]; // 29 个展开的元素,每个都是 1 位寄存器。
取整个4位向量A,并将其赋值到整个8位向量W(声明取自上文)。如果左右两边的长度不匹配,则根据需要进行零延长或截断。
The part-select operator can be used to access a portion of a vector:
部分选择算子可用于访问向量的一部分:
w3:0 //只有 w 的低 4 位
x1 // x 的最低位
x1:1 //...也是 x 的最低位
z-1:-2 // z 的最低两个位
b3:0 //不合法。向量部分选择必须与声明的方向匹配.
b0:3 // b 的高4位
assign w[3:0] = b[0:3]; // 将 b 的高 4 位分配给 w 的低 4 位。w[3]=b[0],w[2]=b[1],依此类推。
作业:构建一个组合电路,将输入半字(16位,15:0)拆分为低7:0和高15:8字节
`default_nettype none // 题目模板自带的,建议保留,防止隐式网线产生的Bug
module top_module(
input wire [15:0] in,
output wire [7:0] out_hi,
output wire [7:0] out_lo
);
// 切出高8位(第15位到第8位)
assign out_hi = in[15:8];
// 切出低8位(第7位到第0位)
assign out_lo = in[7:0];
endmodule

(Emm~为什么又跳到第九个了呢,因为我回去把第一章的题补完之后才来写的这题)
3.更多的向量
32位向量可以看作包含4个字节(位31:24、23:16等)。构建一个电路,可以反转4字节字的字节顺序。
如何进行翻转呢,就是把in的低位赋值给out的高位。32位向量有4个字节的8位向量,所以需要赋值四次。所以你想好怎么构建你的代码了吗?
`default_nettype none // 防止隐式网线产生的Bug
module top_module(
input wire [31:0] in,
output wire [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
编译错误
`default_nettype none // 防止隐式网线产生的Bug
module top_module(
input wire [31:0] in,
output wire [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

4.位运算符
构建一个电路,有两个3位输入,计算两个向量的位元或值、两个向量的逻辑或值,以及两个向量的逆(NOT)。将 的b逆元素置于上out_not半部分(即位数 5:3),将 的a逆置于下半部分。

在做这个题之前,我们先来梳理一下按位和逻辑的表达式有何不同。
按位运算(位运算)与逻辑运算(布尔运算)在符号、语义和用途上有明显区别,以下是常用算子的对照汇总。
符号对照表
| 运算类型 | 符号(数学/电路) | 编程常用符号 | 含义 |
|---|---|---|---|
| 与 | ∧ / · / & | &(位运算) &&(逻辑运算) | 所有条件为真(或所有位都为1)时结果为真(或1) |
| 或 | ∨ / + / | | |(位运算) ||(逻辑运算) | 至少一个条件为真(或至少一个位为1)时结果为真(或1) |
| 非 | ¬ / ′ / 上划线 | ~(位运算) !(逻辑运算) | 取反 |
| 异或 | ⊕ | ^(位运算) | 两个输入不同为1,相同为0 |
| 同或 | ⊙ | XNOR(电路) | 两个输入相同为1,不同为0 |
真值表对比
| A | B | A & B(位) | A | B(位) | A && B(逻辑) | A || B(逻辑) |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | false | false |
| 0 | 1 | 0 | 1 | false | true |
| 1 | 0 | 0 | 1 | false | true |
| 1 | 1 | 1 | 1 | true | true |
位运算是对每一位独立运算,逻辑运算是对整体布尔值运算
短路求值特性:
逻辑运算符 &&、|| 有短路求值:
&&:左为 false 时不计算右
||:左为 true 时不计算右
位运算符 &、| 无短路,两个操作数都会计算。
易混淆:
& 和 && 在数值上可能相同(非零为真),但语义不同:& 是位运算,&& 是逻辑运算。
在涉及指针、数组等可能产生副作用的表达式中,必须用逻辑运算符以避免不必要的副作用。
总结:
位运算:&、|、^、~,作用于二进制位,无短路。
逻辑运算:&&、\|\|、!,作用于布尔值,有短路。
在编程中,选择哪种取决于你是要操作位还是判断布尔条件

module top_module (
input wire [2:0] a,
input wire [2:0] b,
output wire [2:0] out_or_bitwise,
output wire out_or_logical,
output wire [5:0] out_not
);
assign out_or_bitwise = a | b;//位运算
assign out_or_logical = a || b;//逻辑运算
assign out_not = {~b, ~a};//(非门有多个输入的写法)
endmodule

5.四输入门
作业:👇

module top_module(
input [3:0] in1,
input [3:0] in2,
input [3:0] in3,
input [3:0] in4,
output out_and,
output out_or,
output out_xor
);
assign out_and=(in1 & in2 & in3 & in4);
assign out_or=(in1 | in2 | in3 | in4);
assign out_xor=~(in1 ^ in2 ^ in3 ^ in4);
endmodule
编译错误
错误原因:我的输入是 4 位,但三个输出都只声明了 1 位(output out_and 等价于 output 0:0 out_and)。
而右侧表达式是 4 位的,比如 in1 & in2 & in3 & in4 结果为 4 位。赋值给 1 位的输出时,Verilog 会静默截断,只保留最低位 bit0,高 3 位直接丢弃。
所以根据思路来写下你的代码:
module top_module(
input [3:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = ∈ // 4 输入与:仅当 in == 4'b1111 时为 1
assign out_or = |in; // 4 输入或:仅当 in == 4'b0000 时为 0
assign out_xor = ^in; // 4 输入异或:1 的个数为奇数时为 1
endmodule

也可以将其展开来写
module top_module(
input [3:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = in[0] & in[1] & in[2] & in[3];
assign out_or = in[0] | in[1] | in[2] | in[3];
assign out_xor = in[0] ^ in[1] ^ in[2] ^ in[3];
endmodule
这个也能成功编译
6.向量连接符
零件选择用于选择矢量的部分。连接算子 {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 和 4'd10 在二进制中都是 4'b1010
连接需要知道每个分量的宽度(否则你怎么知道结果的长度?)。因此,{1, 2, 3} 是非法的,并导致错误信息:不允许在串接中使用未大小常量。
连接算子可用于赋值的左右两侧。
input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in; // 交换两个字节。右侧和左侧都是16位矢量
assign out[15:0] = {in[7:0], in[15:8]}; // 这也是同样的道理。
assign out = {in[7:0], in[15:8]}; // 这次不一样。右侧的16位矢量扩展为
//在前两个例子中,out[23:16] 没有被分配
给定多个输入向量,将它们串接在一起,然后拆分成多个输出向量。共有六个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
);
wire [31:0] out;
assign out = {a, b, c, d, e, f, 2'b11};
//因为5*6=30《 4*8=32,所以还要加一个2'b11(因为最右边格子是11)
assign z = out[31:24]; // = {a, b[4:2]}
assign y = out[23:16]; // = {b[1:0], c, d[4]}
assign x = out[15:8]; // = {d[3:0], e[4:1]}
assign w = out[7:0]; // = {e[0], f, 2'b00}
endmodule
//编译成功但是电路不匹配:知道为什么吗?因为上面这段代码没按找表格里的顺序来。把z,y,x,w的顺序换为w,x,y,z就能编译成功了。
module top_module (
input [4:0] a, b, c, d, e, f,
output [7:0] w, x, y, z
);
wire [31:0] out;
assign out = {a, b, c, d, e, f, 2'b11};
assign w = out[31:24]; // = {a, b[4:2]}
assign x = out[23:16]; // = {b[1:0], c, d[4]}
assign y = out[15:8]; // = {d[3:0], e[4:1]}
assign z = out[7:0]; // = {e[0], f, 2'b11}
endmodule

module top_module (
input [4:0] a, b, c, d, e, f,
output [7:0] w, x, y, z
);
assign {w,x,y,z} = {a, b, c, d, e, f, 2'b11};
endmodule


7.矢量反转
给定一个8位输入向量7:0,反向其位序
矢量翻转的逻辑:矢量反转就是把每个时钟周期里向量的所有位按相反的顺序重新排列:原来的最低位移到最高位,原来的最高位移到最低位。反转不改变向量中 1 的个数,只改变这些 1 的位置。
module top_module(
input [7:0] in,
output [7:0] out
);
wire n0, n1, n2, n3, n4, n5, n6, n7; // 8 个,补上 n7 和分号
assign n0 = in[0]; // 注意来源是 in,不是 out
assign n1 = in[1];
assign n2 = in[2];
assign n3 = in[3];
assign n4 = in[4];
assign n5 = in[5];
assign n6 = in[6];
assign n7 = in[7];
assign out = {n0, n1, n2, n3, n4, n5, n6, n7};
endmodule
assign out7:0 = in0:7; 不行。Verilog 允许 0:7 这种反向位序声明,但位号的含义不变------in0:7 和 in7:0 是同一个向量,切片写法不能翻转物理顺序。这是题目 Hint 里明确点出的。
每个中间线必须是 1 位(wire n0),因为它承载单个比特,不是 2 位。


8.复制算符
连接算子允许将向量串联成更大的向量。但有时你希望同一东西多次串接在一起,而且像赋值a = {b,b,b,b,b,b};这样的操作仍然很繁琐。复制算子允许重复向量并将其串接:
这可以复制向量的数量乘以。NUM必须是常数。两套护具都是必备的。
{5{1'b1}} // 5'b111111 (或 5'd31 或 5'h1f)
{2{a,b,c}} // 与 {a,b,c,a,b,c} 相同
{3'd5, {2{3'd6}}} // 9'b101_110_110.它是101和第二个载体是两个3'B110的副本。
常见的复制算子是在将较小数字扩展为较大数字时,同时保持其带符号值。这是通过复制左侧较小数字的符号位(最高有效位)来实现的。例如,符号扩展为4'b0101 (5)为8位得到8'b00000101 (5),而符号扩展为4'b1101 (-3)为8位则为8'b11111101 (-3)。
作业:构建一个电路,将8位数字符号扩展到32位。这需要将24个符号位的复制件串接起来(即复制位724次),然后是8位编号本身。
module top_module (
input [7:0] in,
output [31:0] out );
wire mid;
assign mid={24{in[7]}};
assign out[31:0]={mid,in};
Endmodule
编译错误
知道为什么错误吗?因为你的mid位宽没有设对。
所以知道怎么改动代码了吗?
module top_module (
input [7:0] in,
output [31:0] out );
wire [23:0] mid;
assign mid={24{in[7]}};
assign out[31:0]={mid,in};
endmodule

9.更多的复制
给定五个1位信号(a、b、c、d和e),计算25位输出矢量中所有25个两两一比特比较。如果被比较的两个比特相等,输出应为1。
out[24] = ~a ^ a; // a == a,所以 out[24] 总是 1.
out[23] = ~a ^ b;
out[22] = ~a ^ c;
...
out[ 1] = ~e ^ d;
out[ 0] = ~e ^ e;

如图所示,使用复制和串接算子可以更轻松地实现这一点。
The top vector is a concatenation of 5 repeats of each input
顶向量是每个输入的5个重复串接
The bottom vector is 5 repeats of a concatenation of the 5 inputs
底部向量是5个输入串接的5次重复
module top_module (
input a, b, c, d, e,
output [24:0] out
);
wire [4:0] n1, n2, n3, n4, n5; // ← 补这行,每个 5 位 忘记定义n1~n5了
assign n1 = ~({5{a}} ^ {a,b,c,d,e});
assign n2 = ~({5{b}} ^ {a,b,c,d,e});
assign n3 = ~({5{c}} ^ {a,b,c,d,e});
assign n4 = ~({5{d}} ^ {a,b,c,d,e});
assign n5 = ~({5{e}} ^ {a,b,c,d,e});
assign out = {n1, n2, n3, n4, n5};
endmodule
