Vlab Verilog OJ 做题记录(10-20)

这一部分主要是比特矢量。

向量

题目描述

Hint: 向量是为了编写、阅读代码方便,将一组相关的信号用一个向量名称统一命名的方式。例如:

1
wire [7:0] w;

声明了一个8bit位宽的向量信号w,实际上代表的是8个1bit的wire型信号。 注意向量信号的声明是将位宽信息放在信号名之前,这与C语言不太一样。我们可以将向量信号中的一位或多位单独拿来使用。例如:

1
2
3
4
5
6
7
8
wire out;
wire [3:0] out_4;
wire [99:0] my_vector;
// 声明一个100bit的向量my_vector
assign out = my_vector[11] & my_vector[10];
// 选择其中两位信号进行运算
assign out_4 = my_vector[23:20];
// 选择其中4bit信号

任务目标: 创建一verilog模块,具有一个3bit位宽的输入向量信号,然后将其输出到3bit位宽的输出向量信号,同时再分别输出到3个1bit位宽的输出信号,如下图所示

输入格式

1个3bit位宽的向量信号vec

输出格式

1个与输入vec保持一致的3bit位宽向量信号outv; 3个1bit位宽信号o0, o1, o2,分别对应输入信号vec的三位

代码和解析

当位宽大于 1 时,wirereg 即可声明为向量的形式。这种“向量”也叫“比特矢量”,为的是强调它的每一位都是一个二进制位。一个比特矢量一般来说是这么定义的:

1
wire [WIDTH-1:0] name;

对于一个比特矢量来说,我们更应该把它看成一个“变量”或“数”,而尽可能不把它看成“数列”,这样可能会为思考带来些许方便。比如说,一个比特矢量是可以直接赋值的:

1
2
wire [7:0] var;
var=8'hFF;

这样的话,变量var就被赋予了8位二进制数8'b1111_1111的值,也就是十进制下的511。在比特矢量中,更靠“左”的位对应的“下标”的数字会更大。初看很奇怪,但是只要把左边的理解成二进制的“高位”,那么位越高数字越大,就很显然了。

在访问单个元素时,可以使用类似于“数组”的形式来访问。

1
2
3
4
5
6
7
8
9
10
module top_module(vec,outv,o2,o1,o0);
input wire [2:0] vec;
output wire [2:0]outv;
output wire o0,o1,o2;

assign outv = vec[2:0];
assign o0=vec[0];
assign o1=vec[1];
assign o2=vec[2];
endmodule

向量_续 1

题目描述

创建一 Verilog 模块,将 16bit 输入信号 in分成两个 8bit 的信号 out_hiout_lo,然后输出,如下图所示:

输入格式

输入信号 in, 位宽 16bit,类型为 wire

输出格式

输出信号out_hi,位宽 8bit,为输入信号的高 8 位。 输出信号out_lo,位宽 8bit,为输入信号的低 8 位。

代码和解析

Verilog里面的比特矢量是可以以和Python里面的列表切片有点类似的方法使用的,就像这里面一样,[15:8]就表示取第15位到第8位。这个学名叫“part-select操作”。

1
2
3
4
5
6
7
8
9
10
module top_module(
input wire [15:0]in,
output wire [7:0]out_hi,
output wire [7:0]out_lo
);

assign out_lo = in[7:0];
assign out_hi = in[15:8];

endmodule

向量_续2

题目描述

一个32bit的向量信号包含有4个字节(bit[31:24]、bit[23:16]等),创建一个电路,用以调整4个字节的顺序,该电路经常用于在不同大小端系统之间进行数据交互:

1
AaaaaaaaBbbbbbbbCcccccccDddddddd => DdddddddCcccccccBbbbbbbbAaaaaaaa
提示:part-select操作即可以用于赋值语句的左侧也可用于右侧。

输入格式

1个 32bit 位宽的向量信号 in

输出格式

1个 32bit 位宽的向量信号 out

代码和解析

正如题目所说的,part-select操作即可以用于赋值语句的左侧也可用于右侧。

1
2
3
4
5
6
7
8
9
10
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

位操作

题目描述

创建一个电路,包含两个 3bit 的输入信号 a 和 b,分别对 ab 进行按位或、逻辑或操作,以及将 ab 拼接成 6bit 信号后进行按位取反,如下图所示:

输入格式

a = 3'b101 b = 3'b000

输出格式

按位或:3'b101 逻辑或:1 拼接ab后再按位取反:6'b111010

代码和解析

这里主要区分一下按位操作和逻辑操作。在逻辑操作中,一个数如果不等于0(即所有位都是0),那么它代表“真”值,反之,如果所有位都是0,那么它代表“假”值。

Verilog中,比特矢量是可以拼接的。只需要用个大括号把两部分括起来就行了。

1
2
3
4
5
6
7
8
9
10
11
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 = {~b,~a};
endmodule

位操作2

题目描述

创建一个组合逻辑电路,包含4bit输入(in[3:0]),和3个输出,分别为:

  • out_and:四输入与门的输出信号
  • out_or:四输入或门的输出信号
  • out_xor:四输出异或门的输出信号

电路结构如下图所示

输入格式

0 0 0 0

输出格式

0 0 0

代码和解析

在Verilog中,可以把一个本来是二目运算符的按位算符放在一个比特矢量的前面,这表示从高位到低位一个一个依次用这个二目运算符计算,最后得到一个一位的结果。这种操作叫“归约操作符”。归约操作符包括:归约与&,归约与非~&,归约或|,归约或非~|,归约异或^,归约同或~^

1
2
3
4
5
6
7
8
9
10
module top_module( 
input wire [3:0]in,
output wire out_and,
output wire out_or,
output wire out_xor
);
assign out_and = ∈
assign out_or = |in;
assign out_xor = ^in;
endmodule

向量拼接

题目描述

part_selection用于选择向量信号中的一部分,而向量拼接算子{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}就是非法的,因为无法确定各信号的位宽,语法检查时会报错。 向量拼接算子既可以用于赋值语句的左侧,也可用于右侧,如下所示:

1
2
3
4
5
input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in;
assign out[15:0] = {in[7:0], in[15:8]};
assign out = {in[7:0], in[15:8]};

创建Verilog电路,将6个5bit位宽的输入信号,以及2bit的常量信号2’b11拼接成32bit的向量信号,并将其拆成4个8bit的信号,分别赋值给4个输出信号,如下图所示:

输入格式

6个5位宽的输入信号a,b,c,d,e,f

输出格式

4个8位宽的信号w,x,y,z

代码和解析

正如题目中所说:向量拼接算子既可以用于赋值语句的左侧,也可用于右侧。

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

向量翻转

题目描述

创建verilog电路,将8bit的输入信号按bit翻转,并输出到输出端口,如下图所示:

输入格式

8 bit in

输出格式

8 bit out, 为in的向量翻转

代码和解析

想到翻转,我第一时间想到的是直接写成这样:

1
2
3
4
5
6
module top_module( 
input wire[7:0] in,
output wire[7:0] out
);
assign out=in[0:7];
endmodule

然而实践证明并不行,是因为 Verilog 不允许翻转向量的位顺序,所以还是只能一个一个赋值。

正确的代码是这样的:

1
2
3
4
5
6
module top_module( 
input wire[7:0] in,
output wire[7:0] out
);
assign out={in[0],in[1],in[2],in[3],in[4],in[5],in[6],in[7]};
endmodule

复制算子

题目描述

复制算子是拼接算子的一种特殊情况,如a={b,b,b,b,b,b}便可以写成a={6{b}}的形式。复制算子的格式为:{num{vector}},其中num必须为常量。如下所示:

1
2
3
{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

创建一verilog电路,将一个8bit位宽的输入信号进行符号位扩展,并通过32bit的输出端口输出,如下图所示

输入格式

8位in信号

输出格式

32位out信号

代码和解析

关于复制算子,题目里已经说得很清楚了,照着写就行了。

1
2
3
4
5
6
module top_module (
input wire[7:0] in,
output wire[31:0] out
);
assign out = {{24{in[7]}},in};
endmodule

复制算子_2

题目描述

创建一verilog电路,包含5个1bit输入,使所有输入两两进行同或(两bit相同时输出1,不同时输出0),并将结果通过25bit的向量信号输出,如下图所示:

输入格式

1位的a,b,c,d,e

输出格式

25位的out

代码和解析

使用复制算子实现该电路,可以大大减少代码量,提高编码效率。

1
2
3
4
5
6
7
8
9
10
module top_module (
input wire a, b, c, d, e,
output wire [24:0] out
);
wire [24:0] mid_1;
wire [24:0] mid_2;
assign mid_1={{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}};
assign mid_2={{5{a,b,c,d,e}}};
assign out=~(mid_1 ^ mid_2);
endmodule

模块例化

题目描述

通过前面一系列的练习,用户应当已经熟悉单个模块电路的设计了。对于功能上更复杂的电路模块,一般都是由若干子模块以及附加的功能电路构成的。

在模块实例化过程中,被例化模块的端口信号是最重要的,用户甚至可以不知道模块的内部结构。上图展示了一个非常简单的包含有子模块电路的电路结构,在此电路中,创建模块mod_a的一个实例化,并将该实例化模块的三个端口(in1,in2,out)与顶层电路的三个端口(a,b,out)直接连接,其中mod_a模块的代码如下:

1
2
3
4
module mod_a ( input in1, input in2, output out );
// Module body
assign out = in1 & in2; //这只是一个简单的示例
endmodule

模块实例化一般有两种语法格式,分别称为基于端口名称的实例化和基于端口位置的实例化。 基于位置的实例化和C语言中的函数调用类似(只是语法上类似,实际上该例化会产生实际的硬件电路),以上述mod_a模块的实例化为例,可以在上层模块中使用以下语句:

1
2
3
module top_module(input wa,input wb,output wc);
mod_a inst_name1(wa,wb,wc);
endmodule

其中inst_name1mod_a模块的实例化名称,可以由用户自定义,通过这种例化方式,便实现了端口对应:wa↔︎in1, wb↔︎in2, wc↔︎out。 基于端口名称的实例化如下所示

1
2
3
4
5
6
module top_module(input wa,input wb,output wc);
mod_a inst_name2(
.out (wc),
.in1 (wa),
.in2 (wb));
endmodule

本教程推荐用户使用基于端口名称的例化方式,因为这种方式编写的代码可读性更强。 试创建一verilog电路,并按照上图中所示实例化mod_a模块(建议使用基于端口名称的方式实例化)。

Hint:

  • 推荐使用基于端口名称的实例化方式
  • 模块调用就像是一个树形的层次结构,不允许循环调用,如a调用b,b又调用a,也不允许模块调用自身,即模块c中又实例化模块c。
  • 不允许在进程块(如always、initial等)或赋值语句(如assign语句)内进行模块实例化
  • 模块的实例化名称可以自定义,如在同一模块中要对一个模块多次实例化,需要有不同的实例化名称。

输入格式

一位线网型变量a、b

输出格式

一位线网型变量out

代码和解析

正如上一篇文章中所说,模块实例化可以类比为C语言里面的函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module top_module(
input a,
input b,
output out
);

// 请用户在下方编辑代码

mod_a mod_a_inst(.in1(a),.in2(b),.out(out));

//用户编辑到此为止
endmodule


module mod_a (
input in1,
input in2,
output out
);
assign out = in1 & in2;
endmodule


Vlab Verilog OJ 做题记录(10-20)
https://suzumiyaakizuki.github.io/2022/10/10/verilog10-20/
作者
SuzumiyaAkizuki
发布于
2022年10月10日
许可协议