这是我参与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 的值并以十进制通过数码管显示。
2、主要实验代码以及注解
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;
复制代码
3、遇到的坑
- assign 类型后面的连续赋值 等号左边 必须是 wire 类型的,等号右边可以是 reg 类型或者 wire 类型都可以。
- always 块里面 等号左边 必须是 reg 类型的,等号右边 可以是 reg 类型或者 wire 类型。
- 每个 alway 块里面的代码是并发的,当敏感列表发生变化,就会执行一次,所以在使用时要格外小心。