课下提交

Q1 P1_L0_splitter

使用 Verilog\mathcal{Verilog} 搭建一个32位 Splitter\bf{Splitter} , 给定一个32位的二进制数作为输入,将其划分为四个8位的二进制数作为输出。

模块名:splitter

信号名 方向 描述
A[31:0] I 输入的二进制数
O1[7:0] O A的[31:24]位
O2[7:0] O A的[23:16]位
O3[7:0] O A的[15:8]位
O4[7:0] O A的[7:0]位

没什么好说的,直接assign即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
`timescale 1ns / 1ps
module splitter(
input [31:0] A,
output [7:0] O1,
output [7:0] O2,
output [7:0] O3,
output [7:0] O4
);

assign O1 = A[31:24];
assign O2 = A[23:16];
assign O3 = A[15:8];
assign O4 = A[7:0];

endmodule

Q2 P1_L0_ALU

使用 Verilog\mathcal{Verilog} 搭建一个 32 位六运算 ALU 并提交。具体模块端口定义如下:
模块名:alu

信号名 方向 描述
A[31:0] I 参与ALU计算的第一个值
B[31:0] I 参与ALU计算的第二个值
ALUOp[2:0] I ALU功能的选择信号
000: ALU进行加法运算
001: ALU进行减法运算
010: ALU进行与运算
011: ALU进行或运算
100: ALU进行逻辑右移
101: ALU进行算数右移
C[31:0] O ALU的计算结果

模块功能定义如下:

序号 功能名称 功能描述
1 无符号加运算 C=A+B (A,B无符号)
2 无符号减运算 C=A-B (A,B无符号)
3 与运算 C=A&B
4 或运算 C=A|B
5 逻辑右移 C=A>>B (A,B无符号)
6 算术右移 C=A>>>B (A有符号,B无符号)

需要注意的是算术右移的处理,$signed()的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
`timescale 1ns / 1ps
module alu(
input [31:0] A,
input [31:0] B,
input [2:0] ALUOp,
output [31:0] C
);

wire [31:0] shift_right;
//放在外面先计算好, 免得符号出错
assign shift_right = $signed(A) >>> B;
assign C = (ALUOp == 3'b000) ? (A + B) :
(ALUOp == 3'b001) ? (A - B) :
(ALUOp == 3'b010) ? (A & B) :
(ALUOp == 3'b011) ? (A | B) :
(ALUOp == 3'b100) ? (A >> B) : shift_right;

endmodule

Q3 P1_L0_EXT

使用 Verilog\mathcal{Verilog} 实现 EXT 。EXT为扩展单元,其主要功能是完成将输入到其中的16位数据进行符号扩展、零扩展以及将输入的16位数加载到高位等操作。具体模块端口定义如下:
模块名:ext

信号名 方向 描述
imm[15:0] I 输入EXT内部需要被扩展的16位数据
Eop[1:0] I 输入数据进行扩展的方式的选择信号
00: 将imm进行符号扩展到32位
01: 将imm进行高位零扩展到32位
10: 将imm加载到高位,低位补0
11: 将imm进行符号扩展后左移两格
ext[31:0] O 进行扩展之后的输出数据

模块功能定义如下:

序号 功能名称 功能描述
1 符号扩展 将Input进行符号扩展到32位
2 零扩展 将Input进行高位补零扩展到32位
3 加载到高位 将Input加载到高位,低位补0
4 符号扩展后左移 将Input符号扩展后,左移两位
  • 使用位拼接运算符处理即可
  • 需要注意的是,常数必须指明位宽,否则默认为32位;同时,拼接可以嵌套,但需要用{}括起来
1
2
3
4
5
6
7
8
9
10
11
12
`timescale 1ns / 1ps
module ext(
input [15:0] imm,
input [1:0] EOp,
output [31:0] ext
);

assign ext = (EOp == 2'b00) ? {{16{imm[15]}}, imm} :
(EOp == 2'b01) ? {16'b0, imm} :
(EOp == 2'b10) ? {imm, {16{1'b0}}} : ({{16{imm[15]}}, imm} << 2);
end
endmodule

Q4 P1_L0_gray

使用 Verilog\mathcal{Verilog} 设计一个格雷码计数器。模块端口定义如下:
模块名:gray

信号名 方向 描述
Clk I 时钟信号
Reset I 同步复位信号
En I 使能信号
Output[2:0] O 计数器当前值
Overflow O 溢出标志位

要求实现功能如下:

  1. 在任意一个时钟上升沿到来的时候,如果复位信号有效,则将计数器清零
  2. 每个时钟上升沿到来的时候,如果使能信号有效,计数器的值+1
  3. 在满足1时,即使2的条件满足,也不必执行2
  4. 计数器初值为0
  5. 当计数器的值在+1后出现溢出的情况时,将会回到零,同时从发生溢出的这个时钟上升沿开始,溢出标志位将会持续输出1,直到计数器被清零为止(其余情况下溢出标志位必须为0)

关于格雷码

  • 对于格雷码,有G(n)=nn2G(n) = n \oplus \left \lfloor \frac{n}{2} \right \rfloor
  • 其余部分就是普通计数器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
`timescale 1ns / 1ps
module gray(
input Clk,
input Reset,
input En,
output [2:0] Output,
output Overflow
);

reg [2:0] st;
reg flag;
assign Output = st ^ (st >> 1);
assign Overflow = flag;

always @ (posedge Clk) begin
if (Reset) begin
st <= 0;
flag <= 0;
end
else begin
if (En) begin
if (st == 3'd7) begin
st <= 3'd0;
flag <= 1'b1;
end
else st <= st + 1;
end
else st <= st;
end
end

endmodule

Q5 P1_L0_表达式状态机

现在,我们需要你用 VerilogHDL\mathcal{Verilog HDL} 语言设计一个有限状态机来识别这样一类表达式F:

  • 表达式F中只含有数字0-9,加号+,乘号*。
  • 表达式F可以按如下的规则产生:
    • 单个数字[0-9]是F
    • 如果X是F,Y是F,X+Y也是F
    • 如果X是F,Y是F,X*Y也是F

模块规格:
模块名:expr

端口名 位宽 功能
clk 1 接收时钟信号
clr 1 接收异步清零信号
in[7:0] 8 接收逐个输入的字符,用 ASCII 编码
out 1 输出合法性信号
  • 每个时钟上升沿,状态机从 in 中读入一个ASCII编码的字符。假设读入的第i个字符为cic_i,则第n个时钟上升沿时,可以拼出一个字符串: s=c1c2cns=c_1c_2 \cdots c_n
  • 此时若s符合F的定义,那么 out 应输出1,否则输出0
  • 如果s当前是空串,out也应输出0。清零后,上面定义的字符串s也应从空串开始计算
  • 考虑这几种状态:
    1. S0: 当前字符串为空
    2. S1: 当前字符串为F(即合法)
    3. S2:字符串为F+ or F*
    4. S3:字符串非法,如99+9++等(此时只能等待清零信号)
  • 注意要异步清零
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
`timescale 1ns / 1ps
module expr(
input clk,
input clr,
input [7:0] in,
output out
);

reg [1:0] st;
wire [1:0] check = (in >= "0" && in <= "9") ? 2'b01 :
(in == "+" || in == "*") ? 2'b10 : 2'b00;
assign out = (st == 2'd1) ? 1'b1 : 1'b0;

always @ (posedge clk or posedge clr) begin
//异步清零
if (clr == 1'b1) st <= 0;
else begin
case (st)
2'd0: begin
if (check == 2'b01) st <= 2'd1;
else st <= 2'd3;
end
2'd1: begin
if (check == 2'b10) st <= 2'd2;
else st <= 2'd3;
end
2'd2: begin
if (check == 2'b01) st <= 2'd1;
else st <= 2'd3;
end
2'd3: st <= st;
default: st <= 0;
endcase
end
end
endmodule

附加题Q1 P1_L1_BlockChecker

现在需要你用 Verilog\mathcal{Verilog}语言编写一个模拟语句块检查的工具。
为了简化要求,输入由ASCII字母和空格组成。一个或多个连续出现的字母构成一个单词,单词不区分大小写,单词之间由一个或多个空格分隔开。检查工具检查自复位之后的输入中,begin和end是否能够匹配。

匹配规则类似括号匹配:一个begin只能匹配一个end,但是一个匹配的begin必须出现在对应的end之前;允许出现嵌套;最后若出现不能按上述规则匹配的begin或end,则匹配失败。
输入的读取在时钟上升沿进行。

匹配示例:Hello world,begin comPuTer orGANization End。

不匹配示例:eND,beGin study。

模块端口定义如下:
模块名:BlockChecker

信号名 方向 描述
clk I 时钟信号
reset I 异步复位信号(高电平有效,复位时将输入记录清空)
in[7:0] I 当前输入字符的ASCII码
result O 当前输入是否能够完成begin和end的匹配

示例波形

  • 显然,这题需要统计beginend单词的数量差,
    count{>0,mismatch=0,match<0,illegalcount\begin{cases} >0 ,&\tt{mismatch} \\ =0 ,&\tt{match} \\ <0 ,&\tt{illegal}\end{cases}
  • 同时,需要注意区分beginbeginx(endendx)
  • 处理组合逻辑和时序逻辑的时候,注意不要让同一个reg变量在多个always块中被赋值,否则会CompileError\mathcal{Compile Error}
  • state:
    1. S0: 等待单词读入(此时为空或空格)
    2. S1: 处理非beginend的单词
    3. S2-S5: 判断begin
    4. S6: 已读入一个begin, 读入空格->S7 else ->S1
    5. S7: 一个begin单词
    6. S8-S9: 判断end
    7. S10: 已读入一个end, 读入空格->S11 else ->S1
    8. S11: 一个end单词
    9. default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
`timescale 1ns / 1ps
module BlockChecker(
input clk,
input reset,
input [7:0] in,
// output reg [3:0] st,
output result
);

wire letter = (in == " ") ? 1'b0 : 1'b1;
reg [31:0] cnt;
reg [3:0] st;

assign result = (cnt == 32'b0 && st != 4'd6 && st != 4'd10) ? 1'b1 :
(cnt == 32'b1 && st == 4'd10) ? 1'b1 : 1'b0;

always @ (st, cnt, reset) begin
if (reset == 1'b1) cnt = 32'b0;
else begin
if (cnt != 32'hffff) begin
if (st == 4'd7) begin
cnt = cnt + 32'b1;//begin
end
else if (st == 4'd11) begin
if (cnt == 32'b0) cnt = 32'hffff;
//赋了一个很大的值
else cnt = cnt - 32'b1;//end
end
else cnt = cnt;
end
else cnt = cnt;
end
end

always @ (posedge clk or posedge reset) begin
if (reset == 1'b1) begin
st <= 4'd0;
end
else begin
case (st)
4'd0: begin
if (letter == 1'b1) begin
if (in == "b" || in == "B") st <= 4'd2;
else if (in == "e" || in == "E") st<= 4'd8;
else st <= 4'd1;
end
else st <= 4'd0;
end
4'd1: begin//其他单词
if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
4'd2: begin
if (in == "e" || in == "E") st <= 4'd3;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
4'd3: begin
if (in == "g" || in == "G") st <= 4'd4;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
4'd4: begin
if (in == "i" || in == "I") st <= 4'd5;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
4'd5: begin
if (in == "n" || in == "N") st <= 4'd6;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
4'd6: begin//出现begin字串
if (letter == 1'b1) st <= 4'd1;
else st <= 4'd7;
end
4'd7: begin//出现begin单词
if (in == "b" || in == "B") st <= 4'd2;
else if (in == "e" || in == "E") st <= 4'd8;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
'd8: begin
if (in == "n" || in == "N") st <= 4'd9;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
'd9: begin
if (in == "d" || in == "D") st <= 4'd10;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
'd10: begin//出现end字串
if (letter == 1'b1) st <= 4'd1;
else st <= 4'd11;
end
'd11: begin//出现end单词
if (in == "b" || in == "B") st <= 4'd2;
else if (in == "e" || in == "E") st <= 4'd8;
else if (letter == 1'b1) st <= 4'd1;
else st <= 4'd0;
end
default: st <= 4'd0;
endcase
end
end

endmodule

课上考试

Q1 P1_L1_dotProduct_2023

请你设计一个向量点乘模块,实现向量的点乘操作。

为了简化题目,我们用两个位宽为 32 的 wire 型变量来表示两个需要点乘的 32 维向量,也就是说,向量任何一个维度的值只能为 0 或 1。你需要将两个向量同一位置相乘并将所有位置的乘积相加输出。

模块端口定义如下:
模块名:dotProduct

信号名 方向 描述
vector_a[31:0] input 需要点乘的向量a
vector_b[31:0] input 需要点乘的向量b
result[5:0] output 点乘结果
  • 显然,1位二进制数的乘法和&操作等价,故答案为vector_avector_b各位相与之和
  • 故此题用for循环解决即可,或者ctrl + c v32次(
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
`timescale 1ns / 1ps
module dotProduct(
input [31:0] vector_a,
input [31:0] vector_b,
output reg [5:0] result
);

integer i;
always @(vector_a, vector_b) begin
result = 0;
//记得初始化,惨痛教训
for (i = 0; i < 6'd32; i = i + 1) begin
if ((vector_a[i] & vector_b[i]) == 1'b1) begin
result = result + 6'b1;
end
else begin
result = result;
end
end
end

endmodule

Q2 P1_L4_coloring_2023

小 B 同学准备对一列格子进行涂色,他想要使用红色,绿色,蓝色三种颜色,但是要求如下:同一颜色不得连续出现三次,红色不得与绿色相连。

我们将在每个周期通过 color 端口输入一个颜色代号:0(红色),1(绿色),2(蓝色),你需要设计 Moore 状态机来检测该涂色序列的合法性,并在检测到不合法的序列之后将输出端口 check 置为 1。

另外,在检测到不合法序列之后,小 B 同学会将最近的一次涂色擦除,重新涂色。

模块端口定义如下:
模块名:coloring

信号名 方向 描述
clk I 时钟信号
rst_n I 异步复位信号(低电平有效)
color[1:0] I 涂色信号
check O 当前序列合法性

实例波形

法一:
有限状态机直接设好状态做就行,这里就不给出状态表了因为我没用这种做法

法二:

  • 对于这道题,可以发现只需要判断,输入的序列连续两个或三个是什么颜色即可
  • 于是可以用四位state表示最近两次成功涂色,in为即将涂上的颜色,这样就可以很简单地进行状态的判断和转移
  • 需要注意的是,判断当前序列合法后,才能给state更新

低电平有效的异步复位的正确写法:

1
2
3
4
5
6
7
8
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
//reset
end
else begin
//……
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
`timescale 1ns / 1ps
module coloring(
input clk,
input rst_n,
input [1:0] color,
output check
);

reg flag;
reg [3:0] st;
assign check = (flag == 1'b1) ? 1'b1 : 1'b0;

always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
flag <= 1'b0;
st <= 4'hf;
//避免st初始状态为0(错误表示成红色)的情况
end
else begin
if (st[3:2] == st[1:0] && st[1:0] == color) begin
st <= st;
flag <= 1'b1;
end
else if ((st[1:0] == 2'b00 && color == 2'b01) || (st[1:0] == 2'b01 && color == 2'b00)) begin
st <= st;
flag <= 1'b1;
end
else begin
st <= {st[1:0], color};
flag <= 1'b0;
end
end
end
endmodule

Q3 P1_L5_numberNest_2023

题面忘了,先咕了