Verilog 实现一个简单 CPU 计算器

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

写在前面

  • 实验在不用 [+,-, *,>,<] 符号的基础之上实现有符号数的加法减法乘法逻辑运算比较 功能。【计算机组成与设计】实验—-PS: 今天是安装 vivido 的第六天,几乎从零开始的,太痛苦了。
  • 因为代码太长了,下面只放了部分代码,完整代码我放在了 另外一篇博客 了哈。

1、实验任务

  • 本次实验需要实现一个 CPU 的运算器。简易运算器由三个 8 位寄存器 R0、R1、R2 和一个算术逻辑单元(ALU)构成,其中 ALU 应该至少支持加法、减法、乘法,按位与、按位或、按位异或、逻辑非运算。
  • 输入由开关控制;每一步运算后,相应标志位(标志位设置同实验四)的情况通过 LED灯表示;运算结果以十进制通过数码管显示。读取数据的结果以十进制通过数码管显示。时钟信号、复位信号等控制信号允许用开关控制。
  • 注:1. 本次实验涉及的数据皆为补码。2. 实验说明给出的默认指令集由定长指令构成,其中指令的操作码为变长操作码。

2、实验说明

  • 实验使用八位二进制串( b7b6b5b4b3b2b1b0)表示指令,与开发板上的八个开关对应。实验使用四位二进制补码作为输入,与开发板上的四个开关对应。三个寄存器 R0,R1,R2 分别对应二进制地址码 00、01、10。
  • 注:运算结果始终默认存放至寄存器 R2 因此不在指令中显式指出。有符号乘法的结果若超过 8 比特应当解释为溢出。

1、运算指令格式

  • 高四位(b7b6b5b4)为操作码,次低两位(b3b2)为地址码指明存放第一个操作数的寄存器,最低两位(b1b0)为地址码指明存放第二个操作数的寄存器。
  • 运算指令的操作码(b7b6b5b4): 运算操作
  • 0000 ——-> A + B
  • 0001 ——-> A + 1
  • 0010 ——-> A - B
  • 0011 ——-> A - 1
  • 1100 ——-> A * B
  • 0100 ——-> 按位与
  • 0101 ——-> 按位或
  • 0110 ——-> 按位异或
  • 0111 ——-> A >= B ?

2、存储指令格式

  • 最高两位(b7b6)为操作码置 10,次高两位(b5b4)为写入寄存器的地址码,低四位(b3b2b1b0)为待写入数据的二进制补码。

3、读取指令格式

  • 最高六位(b7b6b5b4b3b2)为操作码置 111100,最低两位(b1b0)为地址码置 10。要求该指令可读取寄存器 R2 的值并以十进制通过数码管显示。

3、主要实验代码以及注解

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
ini复制代码module adder(
input ia,
input ib,
input cin,
output cout,
output cur
);
assign cur = ia ^ ib ^ cin; //当前位
assign cout = ia & cin | ib & cin | ia & ib; // 进位
endmodule

module add_8(
input [7:0] a,
input [7:0] b,
input cin,
output [7:0] s,
output o, // overflow,
output cout
);
wire c0,c1,c2,c3,c4,c5,c6,c7;
adder d0(a[0],b[0],cin,c0,s[0]);
adder d1(a[1],b[1],c0,c1,s[1]);
adder d2(a[2],b[2],c1,c2,s[2]);
adder d3(a[3],b[3], c2,c3,s[3]);
adder d4(a[4],b[4],c3,c4,s[4]);
adder d5(a[5],b[5],c4,c5,s[5]);
adder d6(a[6],b[6],c5,c6,s[6]);
adder d7(a[7],b[7],c6,c7,s[7]);
assign cout = c7;
assign o = (a[7] & b[7] & ~s[7]) | (~a[7] & ~b[7] & s[7]); // 溢出
endmodule

module add_sub_8(
input [7:0] a,
input [7:0] b,
input cin,
input op,
output [7:0] res,
output overflow,
output cout
);
add_8 d4(a,{op,op,op,op,op,op,op,op} ^ b,op ^ cin,res,overflow,cout);
endmodule

module slt(
input [7:0] a,
input [7:0] b,
output result
);
wire o, c;
wire [7:0] r;
add_sub_8 sub(a, b, 0, 1, r, o, c);
assign result = o ^ r[7];

endmodule



module shift(
input [7:0] a,
input b,
input [1:0]m,
output [7:0] out
); // 将相与的值向左移动 m 位
assign out = b == 1'b0 ? 8'b0000_0000 : ((8'b1111_1111 & a) << m);

endmodule

module mult(
input [3:0] a,
input [3:0] b,
output [7:0] out
);
wire [7:0] abs_a; // a 的绝对值
wire [7:0] abs_b; // b 的绝对值
wire flag;
assign flag = a[3] ^ b[3]; // 通过对符号位的异或取得乘积的符号
wire [3:0] t1;
wire [3:0] t2;
wire o1,c1,o2,c2,o3,c3,o4,c4,o5,c5,o6,c6;
add_sub_8 m0(a,1,0,1,t1,o1,c1); // a - 1
add_sub_8 m1(b,1,0,1,t2,o2,c2); // b - 1
assign abs_a[7:4] = 4'b0000; // 绝对值都是正数,所以拓展成 8 位后,前面四位都要补上 0
assign abs_b[7:4] = 4'b0000;
assign abs_a[3:0] = a[3] == 1'b1 ? ~t1 : a; // 取绝对值完成
assign abs_b[3:0] = b[3] == 1'b1 ? ~t2 : b;
wire [7:0] T1;
wire [7:0] T2;
wire [7:0] T3;
wire [7:0] T4;
shift h1(abs_a,abs_b[0],2'b00,T1); // 四位数乘法转换成 四次加法运算
shift h2(abs_a,abs_b[1],2'b01,T2);
shift h3(abs_a,abs_b[2],2'b10,T3);
shift h4(abs_a,abs_b[3],2'b11,T4);

wire [7:0] T5;
wire [7:0] T6;
wire [7:0] res;
wire [7:0] T7;
add_sub_8 m2(T1,T2,0,0,T5,o3,c3); // T5 = T1 + T2
add_sub_8 m3(T5,T3,0,0,T6,o4,c4); // T6 = T5 + T3
add_sub_8 m4(T6,T4,0,0,res,o5,c5); // res = T6 + T4

add_sub_8 m5(~res,1,0,0,T7,o6,c6); // T7 = ~res + 1 即是取 res 的补码
assign out = flag == 1'b1 ? T7 : res; // 由乘积符号标志,确定输出的是原值还是它的补码
endmodule







module CPU(
input read_to_R2, // 是否把值存入 R2
input alu, // 控制是否开启 alu 部分计算
input clk, // 时钟
input [7:0] b, // 数据源
output reg [7:0] led_id,
output reg [6:0] out_led, // 数码管显示
output ZF, // 运算结果全零,则为 1
CF, // 进借位标志位
OF, // 溢出标志位
SF, // 符号标志位,与 F 的最高位相同
PF // 奇偶标志位,F 有奇数个 1,则 PF=1,否则为 0
);
reg [7:0] R0; // 对应地址码 00
reg [7:0] R1; // 对应地址码 01
reg [7:0] R2; // 对应地址码 10

reg [7:0] data_to_R2; // 在 alu 计算 和 存入 R2 的值的零时变量

wire [7:0] res1,res2,res3,res4,res5;
wire res9;
wire OF1,OF2,OF3,OF4;
wire CF1,CF2,CF3,CF4;
reg of,cf;

reg [7:0] A1;
reg [7:0] B1;
wire [7:0] A; // 第一个源数据
wire [7:0] B; // 第二个源数据
assign A = A1;
assign B = B1;

add_sub_8 fun1(A,B,0,0,res1,OF1,CF1); // res1 = A + B
add_sub_8 fun2(A,1,0,0,res2,OF2,CF2); // res2 = A - 1
add_sub_8 fun3(A,B,0,1,res3,OF3,CF3); // res3 = A - B
add_sub_8 fun4(A,1,0,1,res4,OF4,CF4); // res4 = A - 1;
mult fun5(A[3:0],B[3:0],res5); // res5 = A * B
slt fun9(A,B,res9); // 比较 A 和 B 的大小,A >= B, res9 = 0; A < B, res9 = 1;


always@(b) begin

if (read_to_R2 == 1'b1) R2 = data_to_R2; // 是否将临时变量的值存如 R2

if (b[7:6] == 2'b10 && alu == 1'b0) begin // 写入数据进入寄存器
if (b[5:4] == 2'b00) begin // 根据地址码,存入值
if (b[3] == 1'b1) R0[7:4] = 4'b1111; // 根据写入数(补码)最高位,判断并 4 位长拓展为 8 位,下同
else R0[7:4] = 4'b0000;
R0[3:0] = b[3:0];
end
else if (b[5:4] == 2'b01) begin
if (b[3] == 1'b1) R1[7:4] = 4'b1111;
else R1[7:4] = 4'b0000;
R1[3:0] = b[3:0];
end
else if (b[5:4] == 2'b10) begin
if (b[3] == 1'b1) begin
R2[7:4] = 4'b1111;
data_to_R2[7:4] = 4'b1111;
end
else begin
R2[7:4] = 4'b0000;
data_to_R2[7:4] = 4'b0000;
end
R2[3:0] = b[3:0];
data_to_R2 = b[3:0];
end
end
else if (alu == 1'b1) begin
case(b[3:2])
2'b00: A1 = R0;
2'b01: A1 = R1;
2'b10: A1 = R2;
endcase
case(b[1:0])
2'b00: B1 = R0;
2'b01: B1 = R1;
2'b10: B1 = R2;
endcase
case(b[7:4]) // 以下模拟 alu 部分进行取出对应状态计算的结果和状态
4'b0000: begin
data_to_R2 = res1;
of = OF1;
cf = CF1;
end
4'b0001: begin
data_to_R2 = res2;
of = OF2;
cf = CF2;
end
4'b0010: begin
data_to_R2 = res3;
of = OF3;
cf = ~CF3;
end
4'b0011: begin
data_to_R2 = res4;
of = OF4;
cf = ~CF4;
end
4'b1100: data_to_R2 = res5;
4'b0100: data_to_R2 = A & B;
4'b0101: data_to_R2 = A | B;
4'b0110: data_to_R2 = A ^ B;
4'b0111: data_to_R2 = res9;
endcase
end
end


// 数码管灯显示数据
wire [7:0] n;
assign n = b[7:0] == 8'b1111_0010 ? R2 : // 读取 R2 的值
b[7:0] == 8'b1111_0000 ? R0 : // 读取 R0 的值
b[7:0] == 8'b1111_0001 ? R1 : // 读取 R1 的值
b[7:0] == 8'b1111_0011 ? data_to_R2 : // 读取储存 R2 值的中间变量
b[7:0] == 8'b1111_0100 ? A : // 读取第二个源数据
b[7:0] == 8'b1111_0101 ? B : // 读取第二个源数据
8'b0000_0000;

4、遇到的坑

  • assign 类型后面的连续赋值 等号左边 必须是 wire 类型的,等号右边可以是 reg 类型或者 wire 类型都可以。
  • always 块里面 等号左边 必须是 reg 类型的,等号右边 可以是 reg 类型或者 wire 类型。
  • 每个 alway 块里面的代码是并发的,当敏感列表发生变化,就会执行一次,所以在使用时要格外小心。

vivido ! bye!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%