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 的值并以十进制通过数码管显示。

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 块里面的代码是并发的,当敏感列表发生变化,就会执行一次,所以在使用时要格外小心。

vivido ! bye!

Guess you like

Origin juejin.im/post/7034844631074340900
Recommended