verilog从入门到进阶——HDLBits刷题日志
HDLBits是一个verilog在线评测题库,在远程quartus运行评测结果。
相当于leetcode、洛谷等在线评测系统,是一个不错的入门verilog环境
正在准备8.20在城大举办的EDAthon,顺便精进一下自己的verilog coding能力。
getting start
分别是输出0和1,使用assign实现即可
module top_module( output one );
assign one = 1'b1;
endmodule
module top_module ( output zero );
assign zero = 1'b0;
endmodule
值得注意的是,对于一个output变量,未赋值的时候默认为0,所以第二题什么都不写交上去就能通过,见hint
第一章Basic
wire 导线
与物理线路不同,Verilog 中的线路(以及其他信号)是定向传输的。这意味着信息只朝一个方向流动,即从一侧的源端流向另一侧的接收端。在 Verilog 的“连续赋值”中,右侧信号的值会被驱动到左侧的线路上。这种赋值被称为“连续赋值”,因为即使右侧的值发生变化,赋值仍然持续进行。连续赋值并非一次性操作。
模块上的端口也有方向性(通常分为输入端口和输出端口)。输入端口由模块外部的刺激驱动,而输出端口则驱动外部设备。从模块内部来看,输入端口相当于驱动器或源,而输出端口则相当于接收器。
下面的图表展示了电路中各个部分与 Verilog 代码中的各个位是如何对应的。模块和端口的声明构成了电路中的黑色部分。你的任务是通过添加 assign 语句来连接 in 和 out ,从而创建一条连线(用绿色表示)。盒外的部分不需要你去关注,但你需要知道,通过将我们的测试支架中的信号连接到 top_module 上的端口,你的电路就可以被测试了。

我是这么写的:
module top_module( input in, output out );
wire w;
assign w=in;
assign out=w;
endmodule
而output 默认是 wire 类型,更简单可写成:
module top_module( input in, output out );
assign out = in;
endmodule
wire4 四根导线
创建一个包含 3 个输入和 4 个输出的模块,其运作方式类似于电线之间的连接:
a -> w
b -> x
b -> y
c -> z

想法很简单,找个中间的wire变量来存b,ac像上一道题一样直接assign即可。
module top_module(input a,b,c,output w,x,y,z );
assign w=a,z=c;
wire wr=b;
assign x=wr,y=wr;
endmodule
官方:
module top_module (
input a,
input b,
input c,
output w,
output x,
output y,
output z );
assign w = a;
assign x = b;
assign y = b;
assign z = c;
// If we're certain about the width of each signal, using
// the concatenation operator is equivalent and shorter:
// assign {w,x,y,z} = {a,b,b,c};
endmodule
Inverter 逆变器

非门,取反即可。
module top_module( input in, output out );
assign out=!in;
endmodule
AND gate 与门

module top_module(
input a,
input b,
output out );
assign out=a&b;
endmodule
NOR gate 或非门

module top_module(
input a,
input b,
output out );
assign out=!(a|b);
endmodule
XNOR gate 或非门

module top_module(
input a,
input b,
output out );
assign out=!(a^b);
endmodule
Declaring wires 声明导线
随着电路变得越来越复杂,就需要使用导线来连接内部组件了。当你需要使用导线时,应该在该模块的代码体中对其进行声明,即在它被首次使用之前。将来,你会遇到更多需要声明的变量和信号,但现在,我们先从类型为 wire 的信号开始讲解吧。


在上述模块中,有三条线路( in 、 out 和 not_in )。其中两条已经作为模块的输入和输出端口被定义好了(这就是为什么在之前的练习中不需要声明这些线路的原因)。而线路 not_in 则需要定义在模块内部,因为它无法从模块外部看到。接着,通过两条 assign 语句创建了两个非门。需要注意的是,先创建哪个非门并不重要,最终得到的电路都是一样的。
实现以下电路。创建两条中间线路(可以任意命名),将与门和或门连接在一起。注意,通往非门的线路实际上就是 out 这条线路,因此不必在这里声明第三条线路。注意,每条线路都只由一个源点驱动(即门的输出),但也可以连接多个输入。

`default_nettype none
module top_module(input a,input b,input c,input d,output out,output out_n);
wire x,y,z;
assign x=a&b;
assign y=c&d;
assign z=x|y;
assign out=z;
assign out_n=!z;
endmodule
7458
7458 是一种包含四个与门和两个或门的芯片。这个问题比 7420 要复杂一些。
创建一个具有与 7458 芯片相同功能的模块。该模块有 10 个输入和 2 个输出。你可以选择使用 assign 语句来驱动每个输出引脚,或者选择声明四个引脚作为中间信号使用,其中每个内部引脚都由其中一个与门的输出驱动。为了增加练习难度,可以尝试用这两种方式来实现。

module top_module (input p1a, p1b, p1c, p1d, p1e, p1f,output p1y,input p2a, p2b, p2c, p2d,output p2y );
wire a,b,c,d,e,f;
assign a=p2a&p2b,b=p2c&p2d,c=p1a&p1c&p1b,d=p1f&p1e&p1d,e=a|b,f=c|d;
assign p1y=f,p2y=e;
endmodule
第二章 Vector
vector 向量
向量被用来将相关的信号归为一组,这种命名方式使得操作更加方便。例如, wire [7:0] w; 定义了一个名为 w 的 8 位向量,其功能相当于拥有 8 条独立的线路。
在声明向量时,其维度会放在向量名称之前,这与 C 语言的语法有所不同。不过,使用“select”这个关键字时,维度确实如预期那样位于向量名称之后。
构建一个电路,该电路有一个 3 位输入,然后输出相同的向量,同时还将该向量分解为三个独立的 1 位输出。将输出 o0 连接到输入向量的第 0 位,将 o1 连接到第 1 位,以此类推。!
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,o0=vec[0],o1=vec[1],o2=vec[2];
endmodule
Declaring Vectors 声明向量
向量必须被声明。
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 为vec[3:0],那么使用 vec[0:3] 来表示相同的内容是不合法的。保持端序的一致性是一个良好的实践,因为如果不一致,可能会导致一些奇怪的错误,特别是当不同端序的向量被一起使用的时候。
Implicit nets 隐式网络
隐式网络往往是难以被发现的漏洞来源。在 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` 后,第三行代码将会出现错误,这样就能让这个错误更加明显了。
Unpacked vs. Packed Arrays 解包数组与打包数组
你可能已经注意到,在声明中,向量的索引通常写在向量名称之前。这种写法表示向量的各个维度是“打包”的,即各个位被组合在一起形成一个整体(这一点在模拟器中很重要,但在硬件中则没有这种需求)。而未被打包的维度则写在向量名称之后。这些维度通常用于声明内存数组。
Accessing Vector Elements: Part-Select 访问矢量元素:部分选择
将整个 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 位字节和上方的 15 位字节。
`default_nettype none // Disable implicit nets. Reduces some types of bugs.
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],out_hi=in[15:8];
endmodule
vector2 向量2
一个 32 位向量可以被视为包含 4 个字节的数据(即[31:24]、[23:16]等位组)。请构造一个电路,能够反转这 4 个字节的排列顺序。
module top_module(
input [31:0] in,
output [31:0] out );//
assign out[31:24] = in[7:0],out[23:16]=in[15:8],out[15:8]=in[23:16],out[7:0]=in[31:24];
endmodule
vectorgates 向量门
之前我们提到过,各种布尔运算符都有位运算和逻辑运算两种形式(例如 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[0]=a[0]|b[0],out_or_bitwise[1]=a[1]|b[1],out_or_bitwise[2]=a[2]|b[2];
assign out_or_logical=out_or_bitwise[0]|out_or_bitwise[1]|out_or_bitwise[2];
assign out_not[0]=!a[0],out_not[1]=!a[1],out_not[2]=!a[2],out_not[3]=!b[0],out_not[4]=!b[1],out_not[5]=!b[2];
endmodule
Four_input gates 四输入门
请构造一个具有四个输入的组合电路, in[3:0]
总共有 3 个输出端口:
- out:一个四输入与门的输出结果。
- out_or:一个四输入或门的输出。
- out_xor:一个 4 输入异或门的输出结果。
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],out_or=in[0]|in[1]|in[2]|in[3],out_xor=in[0]^in[1]^in[2]^in[3];
endmodule
Vector concatenation operator 向量串联运算符
选择操作用于挑选向量中的特定部分。而连接运算符 {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} 这种写法是不合法的,会导致错误信息: unsized constants are not allowed in 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 {w,x,y,z} = {a,b,c,d,e,f,2'b11};
endmodule
Vector reversal1 矢量反转1
给定一个 8 位输入向量[7:0],请将其位序反转。
另请参考:如何反转一个较长的向量。
module top_module(
input [7:0] in,
output [7:0] out
);
assign {out[0],out[1],out[2],out[3],out[4],out[5],out[6],out[7]} = in;
endmodule
Replication operator 复制操作符
连接运算符可以用来将多个向量连接在一起,形成更大的向量。不过,有时候你希望某个向量被重复多次连接在一起,而像 assign a = {b,b,b,b,b,b}; 这样的操作仍然显得很繁琐。而复制运算符则可以用来重复某个向量,然后再将其连接在一起。
{num{vector}}
该命令表示“重复向量 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 位。这需要先复制 sign 位 24 次(即,将 bit[7]重复 24 次),然后再加上原始的 8 位数值。
module top_module (
input [7:0] in,
output [31:0] out );//
assign out={{24{in[7]}},in[7:0]};
// assign out = { replicate-sign-bit , the-input };
endmodule
More replication 更多的复制
给定五个 1 位信号(a、b、c、d 和 e),需要计算这 25 个信号在 25 位输出向量中的所有 25 种两两比较结果。如果比较的两个位相等,则输出为 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;

module top_module (
input a, b, c, d, e,
output [24:0] out );//
wire [24:0] x={{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}};
wire [24:0] y={{5{a,b,c,d,e}}};
assign out=~x^y;
endmodule
Module 模块
Module 模块
到现在为止,你们已经熟悉了 module 这种电路了。这种电路通过输入和输出端口与外部世界进行交互。更复杂的电路则是通过将多个小型模块组合在一起构建而成的,这些小型模块又可以通过其他组件(如赋值语句和循环块)相互连接。这样就能形成一种层次结构,因为某些模块可以包含其他模块的实例。
下图展示了一个非常简单的电路,其中包含一个子模块。在这个练习中,你需要创建一个模块 **mod_a** 的实例,然后将该模块的三个引脚( in1 、 in2 和 out )连接到你顶层模块的三个端口( a 、 b 和 out )。模块 mod_a 是预先提供的,你需要实例化它。

模块的层次结构是通过在另一个模块中实例化一个模块来创建的,只要所有使用的模块都属于同一个项目即可(这样编译器就能知道去哪里找到该模块)。一个模块的代码不会写在另一个模块的体内(不同模块的代码不会嵌套在一起)。

Connecting Signals to Module Ports 将信号连接到模块端口上
将电线连接到端口上通常有两种方法:一种是按照位置进行连接,另一种则是按照名称进行连接。
By position 按位置划分
按照位置来连接电线与端口的语法应该很熟悉吧,因为它采用了类似 C 语言的语法。在实例化模块时,端口根据模块的声明从左到右进行连接。例如
mod_a instance1 ( wa, wb, wc );
这创建了一个类型为 mod_a 的模块,并为其分配了一个实例名称“instance1”。接着,将信号 wa (位于新模块之外)连接到新模块的第一个端口 in1 ,将 wb 连接到第二个端口 in2 ,而将 wc 连接到第三个端口 out 。这种语法的一个缺点是,如果模块的端口列表发生变化,那么所有实例也需要被重新查找并修改以匹配新的模块结构。
把下面的模块连接起來,形式是:module mod_a ( output, output, input, input, input, input );

module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a inst1(out1,out2,a,b,c,d);
endmodule
By name 按名称排列
通过按名称将信号连接到模块的端口上,即使端口列表发生变化,也能确保线路始终正确连接。不过,这种语法更为冗长。
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
上述代码实例定义了一个名为“instance2”的模块,该模块包含以下组件:信号 wa 连接到名为 in1 的端口, wb 连接到名为 in2 的端口,而 wc 则连接到名为 out 的端口。注意,在这里端口的顺序并不重要,因为连接将会按照正确的名称进行,而无需考虑其在子模块端口列表中的位置。此外,请注意这种语法中端口名称前的圆点符号。

给定一个名为 mod_a 的模块,它有 2 个输出端口和 4 个输入端口,端口顺序不固定。你必须根据名称将这 6 个端口与顶级模块的端口进行连接:
output out1 |
out1 |
|---|---|
output out2 |
out2 |
input in1 |
a |
input in2 |
b |
input in3 |
c |
input in4 |
d |
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a inst1(.in1(a),.in2(b),.in3(c),.in4(d),.out1(out1),.out2(out2));
endmodule
你可以根据端口名称或端口位置来将信号连接到该模块上。为了进一步练习,可以尝试这两种方法。

module top_module ( input a, input b, output out );
mod_a inst1(.in1(a),.in2(b),.out(out));
endmodule
Module shift 模式切换
你得到了一个名为 my_dff 的模块,该模块有两个输入和一个输出(实现一个 D 型触发器)。需要实例化三个这样的模块,然后将它们连接在一起,形成一个长度为 3 的移位寄存器。其中,clk 端口需要连接到所有实例上。
您获得的这个模块是: module my_dff ( input clk, input d, output q );
请注意,为了建立内部连接,您需要声明一些线路。在命名这些线路和模块实例时,请确保名称是唯一的。

module top_module ( input clk, input d, output q );
wire a,b;
my_dff inst1(clk,d,a);
my_dff inst2(clk,a,b);
my_dff inst3(clk,b,q);
endmodule
Module shift8 8模式切换
模块端口不再只是单针脚,而是模块以矢量作为端口,你会连接到这些端口上,而不是普通线。和 Verilog 的其他地方一样,端口的矢量长度不必与连接它的线匹配,但这会导致矢量的零填充或截断。此练习不使用矢量长度不匹配的联络。

你会得到一个模块 my_dff8,有两个输入和一个输出(实现了一组 8 个 D 触发器)。实例化三个,然后将它们串联起来,形成一个长度为 3 的 8 位宽移位寄存器。此外,创建一个 4 对 1 复用器(未提供),根据 sel[1:0] 选择输出内容:输入 d 处的值,第一之后,第二触发后,或第三次 D 触发器之后。(本质上,sel 选择了延迟输入的周期数,从零到三个时钟周期。)
提供的模块如下:module my_dff8 ( input clk, input [7:0] d, output [7:0] q );
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire [7:0]a,b,c;
my_dff8 dff1 (clk,d,a);
my_dff8 dff2 (clk,a,b);
my_dff8 dff3 (clk,b,c);
always @(*) begin
case (sel)
2'd1: q = a;
2'd2: q = b;
2'd3: q = c;
default: q = d;
endcase
end
endmodule

浙公网安备 33010602011771号