Theme NexT works best with JavaScript enabled

Verilog HDL 学习笔记

本文记录了 0xfaner 学习硬件描述语言 Verilog HDL 的笔记。

写在前面

电子电工实验和数字电路与逻辑设计都需要用到 Verilog HDL (下文简称 Verilog )这门语言,而懒惰的 0xfaner 一直没有认真学习这门语言,导致作业压力很大(x

终于到了开学第十周,电子电工实验开课了。 0xfaner 迫于作业压力,终于开始了 Verilog 语言的学习。

正式学习之前要推荐一个网站: HDLBits 。该网站类似于菜鸟教程,可以在线学习 Verilog 语言。

Getting Started

Getting Start

建立一个无输入,一个输出恒定为 $ 1 $ 的电路。

1
2
3
module top_module(output one);
assign one = 1;
endmodule

Output Zero

建立一个无输入,一个输出恒定为 $ 0 $ 的电路。

1
2
3
module top_module(output zero);
assign zero = 0;
endmodule

其中 Verilog1995 和 Verilog2001 的写法并不相同:

Verilog-1995
1
2
3
module top_module(zero);
output zero;
endmodule
Verilog-2001
1
2
module top_module(output zero);
endmodule

0xfaner 的课程采用的是老标准。

Verilog Language

Basics

Simple wire

按照下图逻辑编写代码:

1
2
3
module top_module(input in, output out);
assign out = in;
endmodule

Four wires

按照下图逻辑编写代码:

1
2
3
4
5
6
module top_module(input a, b, c, output w, x, y, z);
assign w = a;
assign x = b;
assign y = b;
assign z = c;
endmodule

Interver

非门有两种,一种是逻辑非 ! ,一种是按位非 ~

按照下图逻辑编写代码:

1
2
3
module top_module(input in, output out);
assign out = !in;
endmodule

AND gate

与有两种,一种是逻辑与 && ,一种是按位与 &

按照下图逻辑(与门)编写代码:

1
2
3
module top_module(input a, b, output out);
assign out = a && b;
endmodule

NOR gate

或有两种,一种是逻辑或 || ,一种是按位或 |

按照下图逻辑(或非门)编写代码:

1
2
3
module top_module(input a, b, output out);
assign out = !(a || b);
endmodule

XNOR gate

异或只有一种按位异或 ^

按照下图逻辑(同或门)编写代码:

1
2
3
module top_module(input a, b, output out);
assign out = !(a ^ b);
endmodule

Declaring wires

尝试进行导线之间的组合,需要自己定义导线。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
`default_nettype none
module top_module(input a, b, c, d, output out, out_n);
wire and1, and2, or1;
assign and1 = a && b;
assign and2 = c && d;
assign or1 = and1 | and2;
assign out = or1;
assign out_n = !or1;
endmodule

7458 chip

尝试实现一个 7458 芯片。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module top_module(
input p1a, p1b, p1c, p1d, p1e, p1f,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y);
wire p11, p12, p13;
wire p21, p22, p23;
assign p11 = p1a & p1b & p1c;
assign p12 = p1d & p1e & p1f;
assign p13 = p11 | p12;
assign p21 = p2a & p2b;
assign p22 = p2c & p2d;
assign p23 = p21 | p22;
assign p1y = p13;
assign p2y = p23;
endmodule

Vectors

Vectors

向量用于对相关信号进行分组,以使其更易于操作。

按照下图逻辑编写代码:

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

Vectors in more detail

构建一个组合电路,将输入的一个半字(英文 half-word , $ 16\ bits $ , $ [15:0] $ )分为 $ [7:0] $ 和 $ [15:8] $ 两部分。

1
2
3
4
5
6
7
8
`default_nettype none
module top_module(
input wire [15:0] in,
output wire [7:0] out_hi,
output wire [7:0] out_lo );
assign out_lo[7:0] = in[7:0];
assign out_hi[7:0] = in[15:8];
endmodule

Vector part select

建立一个电路,该电路将反转四字节字的字节顺序。规则如下:

AaaaaaaaBbbbbbbbCcccccccDddddddd => DdddddddCcccccccBbbbbbbbAaaaaaaa

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

Bitwise operators

构建一个具有两个 $ 3 $ 位输入的电路,该输入可计算两个向量的按位或、逻辑或、反,共三种运算结果。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
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[2:0] = a[2:0] | b[2:0];
assign out_or_logical = | (a[2:0] | b[2:0]);
assign out_not[5:3] = ~b[2:0];
assign out_not[2:0] = ~a[2:0];
endmodule

第九行给我写傻了

Gates4

建立一个四输入的组合电路。

有 $ 3 $ 个输出:

  • out_and:四输入与门。
  • out_or:四输入或门。
  • out_xor:四输入异或门。
1
2
3
4
5
6
7
8
9
10
module top_module( 
input [3:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = & (in[3:0]);
assign out_or = | (in[3:0]);
assign out_xor = ^ (in[3:0]);
endmodule

不知道直接对一个序列运算算不算 verilog 的语法糖……但是写起来真的好爽啊(

Vector concatenation operator

向量串联运算符可以将多个小向量按顺序串联起来变成大向量。

给定几个输入向量,将它们连接在一起,然后将它们分成几个输出向量。

六个五位输入向量:a,b,c,d,e,f

四个八位输出向量:w,x,y,z

输出应该是输入的串联,后跟两个 $ 1 $ :

按照下图逻辑编写代码:

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

啊这个代码压行好爽啊

Vector reversal 1

给定一个八位输入向量,请将其按位顺序反转。

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

向量串联运算符允许我们将多个小向量串联起来变成大向量,而如果我们需要将多个相同小向量串联,则可以使用向量复制运算符。

建立一个将 $ 8 $ 位数字符号扩展为 $ 32 $ 位的电路。这需要串联 $ 24 $ 个符号位的副本(即,复制 $ bit [7] $ $ 24 $ 次),然后是 $ 8 $ 位数字本身。

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

More replication

给定五个 $ 1 $ 位信号,在 $ 25 $ 位输出向量中计算所有 $ 25 $ 个成对的一位比较。如果要比较的两位相等,则输出应为 $ 1 $ 。

按照下图逻辑编写代码:

1
2
3
4
5
module top_module (
input a, b, c, d, e,
output [24:0] out );
assign out = ~{{5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}}} ^ {5{a, b, c, d, e}};
endmodule

巧妙运用了向量串联运算符和向量复制运算符。

Modules: Hierarchy

Modules

模组就类似C语言中的函数,但和函数的区别在于 Verilog 是个硬件描述语言,用于描述硬件之间的逻辑关系,所以调用模组时我们需要实例化出一个硬件来承担这个这个模组的输出,并指定其输入。

按照下图逻辑编写代码:

1
2
3
module top_module ( input a, input b, output out );
mod_a s(a, b, out);
endmodule

Connecting ports by position

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
module top_module ( 
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a s(out1, out2, a, b, c, d);
endmodule

Connecting ports by name

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
module top_module ( 
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a s(.out1(out1), .out2(out2), .in1(a), .in2(b), .in3(c), .in4(d));
endmodule

没给出模组 mod_a 的端口顺序,所以必须直接按照端口名称进行连接。

Three modules

模组可以多次实例化。

按照下图逻辑编写代码:

1
2
3
4
5
6
module top_module ( input clk, input d, output q );
wire q1, q2;
my_dff D1(clk, d, q1);
my_dff D2(clk, q1, q2);
my_dff D3(clk, q2, q);
endmodule

Modules and vectors

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
module top_module ( 
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire [7:0] q1, q2, q3;
my_dff8 D1(clk, d, q1[7:0]);
my_dff8 D2(clk, q1[7:0], q2[7:0]);
my_dff8 D3(clk, q2[7:0], q3[7:0]);
assign q[7:0] = sel[1:0] == 2'b11 ? q3[7:0] : sel[1:0] == 2'b10 ? q2[7:0] : sel[1:0] == 2'b01 ? q1[7:0] : d[7:0];
endmodule

利用三目运算符实现了信号的选择输出。

Adder 1

利用两个 $ 16 $ 位加法器实现一个 $ 32 $ 位加法器。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire c1, c2;
add16 s1(a[15:0], b[15:0], 1'b0, sum[15:0], c1);
add16 s2(a[31:16], b[31:16], c1, sum[31:16], c2);
endmodule

Adder 2

要求实现一个 $ 1 $ 位加法器(已经给定基于 $ 1 $ 位加法器的 $ 16 $ 位加法器),并用两个 $ 16 $ 位加法器实现一个 $ 32 $ 位加法器。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire c1, c2;
add16 s1(a[15:0], b[15:0], 1'b0, sum[15:0], c1);
add16 s2(a[31:16], b[31:16], c1, sum[31:16], c2);
endmodule

module add1(input a, input b, input cin, output sum, output cout);
assign sum = a ^ b ^ cin;
assign cout = a & b | a & cin | b & cin;
endmodule

Carry-select adder

进位选择加法器比串行进位加法器更快,因为串行进位加法器需要等待最低位计算完才能继续计算。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire c0, c11, c12;
wire [15:0] s11, s12;
add16 s0(a[15:0], b[15:0], 1'b0, sum[15:0], c0);
add16 s1(a[31:16], b[31:16], 1'b1, s11[15:0], c11);
add16 s2(a[31:16], b[31:16], 1'b0, s12[15:0], c12);
assign sum[31:16] = (c0 == 1'b1 ? s11[15:0] : s12[15:0]);
endmodule

Adder-subtractor

从编码角度考虑, $ a+b+1’b0=a-b+1’b1 $ ,因此可以用加法器实现减法器。

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire c1, c2;
wire [31:0] c;
assign c = ~b;
add16 s1(a[15:0], sub == 1 ? c[15:0] : b[15:0], sub == 1 ? 1'b1 : 1'b0, sum[15:0], c1);
add16 s2(a[31:16], sub == 1 ? c[31:16] : b[31:16], c1, sum[31:16], c2);
endmodule

实现并不难,但是学到这里还没介绍 always 块,不能用 if 就很难受。

Procedures

Always blocks (combinational)

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_assign = a & b;
always @(*) out_alwaysblock = a & b;
endmodule

终于学到 always 块啦!

Always blocks (clocked)

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff);
assign out_assign = a ^ b;
always @(*) out_always_comb = a ^ b;
always @(posedge clk) out_always_ff = a ^ b;
endmodule

小结一下就是 always clocked 需要一个激励,比如 clk 。而 always combinational 则不需要。

If statement

要求构建一个 $ 2 $ 选 $ 1 $ 数据选择器。

按照下表逻辑编写代码:

sel_b1 sel_b2 out_assign out_always
0 0 a
0 1 a
0 0 a
1 1 b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always);
assign out_assign = sel_b1 && sel_b2 ? b : a;
always @(*)
begin
if (sel_b1 && sel_b2)
begin
out_always = b;
end
else
begin
out_always = a;
end
end
endmodule

If statement latches

按照下图逻辑编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving );
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
else
shut_off_computer = 0;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = 0;
end
endmodule

Case statement

编写代码,利用 Case 语句实现一个 $ 6 $ 选 $ 1 $ 数据选择器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out);
always@(*) begin
case(sel)
3'b000 : out[3:0] = data0[3:0];
3'b001 : out[3:0] = data1[3:0];
3'b010 : out[3:0] = data2[3:0];
3'b011 : out[3:0] = data3[3:0];
3'b100 : out[3:0] = data4[3:0];
3'b101 : out[3:0] = data5[3:0];
default : out[3:0] = 3'b000;
endcase
end
endmodule