Vlab Verilog OJ 做题记录(10-20)
这一部分主要是比特矢量。
向量
题目描述
Hint: 向量是为了编写、阅读代码方便,将一组相关的信号用一个向量名称统一命名的方式。例如:
1 |
|
声明了一个8bit位宽的向量信号w,实际上代表的是8个1bit的wire型信号。 注意向量信号的声明是将位宽信息放在信号名之前,这与C语言不太一样。我们可以将向量信号中的一位或多位单独拿来使用。例如:
1 |
|
任务目标: 创建一verilog模块,具有一个3bit位宽的输入向量信号,然后将其输出到3bit位宽的输出向量信号,同时再分别输出到3个1bit位宽的输出信号,如下图所示
输入格式
1个3bit位宽的向量信号vec
输出格式
1个与输入vec保持一致的3bit位宽向量信号outv
; 3个1bit位宽信号o0
, o1
, o2
,分别对应输入信号vec
的三位
代码和解析
当位宽大于 1 时,wire
或 reg
即可声明为向量的形式。这种“向量”也叫“比特矢量”,为的是强调它的每一位都是一个二进制位。一个比特矢量一般来说是这么定义的:
1 |
|
对于一个比特矢量来说,我们更应该把它看成一个“变量”或“数”,而尽可能不把它看成“数列”,这样可能会为思考带来些许方便。比如说,一个比特矢量是可以直接赋值的:
1 |
|
这样的话,变量var
就被赋予了8位二进制数8'b1111_1111
的值,也就是十进制下的511。在比特矢量中,更靠“左”的位对应的“下标”的数字会更大。初看很奇怪,但是只要把左边的理解成二进制的“高位”,那么位越高数字越大,就很显然了。
在访问单个元素时,可以使用类似于“数组”的形式来访问。
1 |
|
向量_续 1
题目描述
创建一 Verilog 模块,将 16bit 输入信号 in
分成两个 8bit 的信号 out_hi
、out_lo
,然后输出,如下图所示:
输入格式
输入信号 in
, 位宽 16bit,类型为 wire
。
输出格式
输出信号out_hi
,位宽 8bit,为输入信号的高 8 位。 输出信号out_lo
,位宽 8bit,为输入信号的低 8 位。
代码和解析
Verilog里面的比特矢量是可以以和Python里面的列表切片有点类似的方法使用的,就像这里面一样,[15:8]
就表示取第15位到第8位。这个学名叫“part-select操作”。
1 |
|
向量_续2
题目描述
一个32bit的向量信号包含有4个字节(bit[31:24]、bit[23:16]等),创建一个电路,用以调整4个字节的顺序,该电路经常用于在不同大小端系统之间进行数据交互: 1
AaaaaaaaBbbbbbbbCcccccccDddddddd => DdddddddCcccccccBbbbbbbbAaaaaaaa
输入格式
1个 32bit 位宽的向量信号 in
输出格式
1个 32bit 位宽的向量信号 out
代码和解析
正如题目所说的,part-select操作即可以用于赋值语句的左侧也可用于右侧。
1 |
|
位操作
题目描述
创建一个电路,包含两个 3bit 的输入信号 a 和 b,分别对 ab 进行按位或、逻辑或操作,以及将 ab 拼接成 6bit 信号后进行按位取反,如下图所示:
输入格式
a = 3'b101 b = 3'b000
输出格式
按位或:3'b101 逻辑或:1 拼接ab后再按位取反:6'b111010
代码和解析
这里主要区分一下按位操作和逻辑操作。在逻辑操作中,一个数如果不等于0(即所有位都是0),那么它代表“真”值,反之,如果所有位都是0,那么它代表“假”值。
Verilog中,比特矢量是可以拼接的。只需要用个大括号把两部分括起来就行了。
1 |
|
位操作2
题目描述
创建一个组合逻辑电路,包含4bit输入(in[3:0]),和3个输出,分别为:
out_and
:四输入与门的输出信号out_or
:四输入或门的输出信号out_xor
:四输出异或门的输出信号
电路结构如下图所示
输入格式
0 0 0 0
输出格式
0 0 0
代码和解析
在Verilog中,可以把一个本来是二目运算符的按位算符放在一个比特矢量的前面,这表示从高位到低位一个一个依次用这个二目运算符计算,最后得到一个一位的结果。这种操作叫“归约操作符”。归约操作符包括:归约与&
,归约与非~&
,归约或|
,归约或非~|
,归约异或^
,归约同或~^
。
1 |
|
向量拼接
题目描述
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 |
|
创建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 |
|
向量翻转
题目描述
创建verilog电路,将8bit的输入信号按bit翻转,并输出到输出端口,如下图所示:
输入格式
8 bit in
输出格式
8 bit out, 为in的向量翻转
代码和解析
想到翻转,我第一时间想到的是直接写成这样:
1 |
|
然而实践证明并不行,是因为 Verilog 不允许翻转向量的位顺序,所以还是只能一个一个赋值。
正确的代码是这样的:
1 |
|
复制算子
题目描述
复制算子是拼接算子的一种特殊情况,如a={b,b,b,b,b,b}
便可以写成a={6{b}}
的形式。复制算子的格式为:{num{vector}}
,其中num
必须为常量。如下所示:
1 |
|
创建一verilog电路,将一个8bit位宽的输入信号进行符号位扩展,并通过32bit的输出端口输出,如下图所示
输入格式
8位in信号
输出格式
32位out信号
代码和解析
关于复制算子,题目里已经说得很清楚了,照着写就行了。
1 |
|
复制算子_2
题目描述
创建一verilog电路,包含5个1bit输入,使所有输入两两进行同或(两bit相同时输出1,不同时输出0),并将结果通过25bit的向量信号输出,如下图所示:
输入格式
1位的a,b,c,d,e
输出格式
25位的out
代码和解析
使用复制算子实现该电路,可以大大减少代码量,提高编码效率。
1 |
|
模块例化
题目描述
通过前面一系列的练习,用户应当已经熟悉单个模块电路的设计了。对于功能上更复杂的电路模块,一般都是由若干子模块以及附加的功能电路构成的。
在模块实例化过程中,被例化模块的端口信号是最重要的,用户甚至可以不知道模块的内部结构。上图展示了一个非常简单的包含有子模块电路的电路结构,在此电路中,创建模块mod_a的一个实例化,并将该实例化模块的三个端口(in1
,in2
,out
)与顶层电路的三个端口(a
,b
,out
)直接连接,其中mod_a
模块的代码如下:
1 |
|
模块实例化一般有两种语法格式,分别称为基于端口名称的实例化和基于端口位置的实例化。 基于位置的实例化和C语言中的函数调用类似(只是语法上类似,实际上该例化会产生实际的硬件电路),以上述mod_a
模块的实例化为例,可以在上层模块中使用以下语句:
1 |
|
其中inst_name1
是mod_a
模块的实例化名称,可以由用户自定义,通过这种例化方式,便实现了端口对应:wa↔︎in1
, wb↔︎in2
, wc↔︎out
。 基于端口名称的实例化如下所示
1 |
|
本教程推荐用户使用基于端口名称的例化方式,因为这种方式编写的代码可读性更强。 试创建一verilog电路,并按照上图中所示实例化mod_a
模块(建议使用基于端口名称的方式实例化)。
Hint:
- 推荐使用基于端口名称的实例化方式
- 模块调用就像是一个树形的层次结构,不允许循环调用,如a调用b,b又调用a,也不允许模块调用自身,即模块c中又实例化模块c。
- 不允许在进程块(如always、initial等)或赋值语句(如assign语句)内进行模块实例化
- 模块的实例化名称可以自定义,如在同一模块中要对一个模块多次实例化,需要有不同的实例化名称。
输入格式
一位线网型变量a、b
输出格式
一位线网型变量out
代码和解析
正如上一篇文章中所说,模块实例化可以类比为C语言里面的函数调用。
1 |
|