首先第一个实现的功能是ALU模块,主要还是对书中代码的复现从中学会了很多先在就来表述一下。
    首先看模块声明

    1. module ALU(
    2. input [11:0] alu_control,//控制采用11位的one-hot
    3. input [31:0] alu_src1,
    4. input [31:0] alu_src2,
    5. output [31:0]alu_result
    6. );

    可以看出
    ,输入一个十一位的控制信号,使用的是独热码(one-hot),即一共有11条指令被实现。
    两个操作数alu_src1,和alu_src2,最后output的结果是alu_result.
    实现的指令:
    add sub slt sltu and nor or xor sll srl sra lui
    对于指令的读取,按照独热码一位表示一个数

    1. assign op_add=alu_control[0];
    2. assign op_sub=alu_control[1];
    3. assign op_alt=alu_control[2];
    4. assign op_sltu=alu_control[3];
    5. assign op_and=alu_control[4];
    6. assign op_nor=alu_control[5];
    7. assign op_or=alu_control[6];
    8. assign op_xor=alu_control[7];
    9. assign op_sll=alu_control[8];
    10. assign op_srl=alu_control[9];
    11. assign op_sra=alu_control[10];
    12. assign op_lui=alu_control[11];

    然后对于输出结果声明为wire(其实这里比较疑惑,为什么不可以只用一个32位的wire反而要声明这么多的结果?)

    1. wire [31:0]add_sub_result;
    2. wire [31:0]slt_result;//有符号比较,小于置位
    3. wire [31:0]sltu_result;//无符号比较,小于置位
    4. wire [31:0]and_result;
    5. wire [31:0]nor_result;
    6. wire [31:0]or_result;
    7. wire [31:0]xor_result;
    8. wire [31:0]sll_result;
    9. wire [31:0]srl_result;
    10. wire [31:0]sra_result;
    11. wire [31:0]lui_result;//高位加载

    然后首先进行最简单的逻辑运算,这里可以直接使用verilog的逻辑运算符,所以没有什么技术难度,感觉也不会有什么提升效率的空间(此处存疑)
    贴上代码:

    1. assign and_result=alu_src1&alu_src2;
    2. assign or_result=alu_src1|alu_src2;
    3. assign nor_result=~or_result;
    4. assign xor_result=alu_src1^alu_src2;
    5. assign lui_result={alu_src2[15:0],16'b0};

    然后进行加法器的运算:
    首先是一些wire的声明:

    1. wire [31:0]adder_a;
    2. wire [31:0]adder_b;
    3. wire adder_cin;
    4. wire [31:0]adder_result;
    5. wire adder_cout;

    需要注意的是,计算机中,数值的存储方式为补码,所以模拟的内存也进行了补码存储,此处的计算就是对补码进行计算。需要注意的是,此处加法和减法可以做统一处理。

    1. assign adder_a=alu_src1;
    2. assign b=(op_sub|op_slt|op_sltu)?~alu_src2:alu_src2;//取反
    3. assign adder_cin=(op_sub|op_slt|op_sltu)?1'b1:1'b0;//还原补码
    4. assign {adder_cout,adder_result}=adder_a+adder_b+adder_cin;//存贮结果和进位
    5. assign add_sub_result=adder_result;

    接下来是slt,sltu这两个指令略有差别,即有符号数和无符号数的差别,他们首先比较两个寄存器数值的大小,如果小于就置末位为1,否则为0.(需要说明的是,第三行的代码,即result[0]的置位代码我看的不是很懂)

    1. assign add_sub_result=adder_result;
    2. assign slt_result[31:1]=31'b0;
    3. assign slt_result[0]=(alu_src1[31]&~alu_src2[31])|(~(alu_src1[31]^alu_src2[31])&adder_result[31]);
    4. //计算两个寄存器值的大小
    5. assign sltu_result[0]=~adder_cout;

    下面时两个逻辑左移和右移,还有一个算术右移。
    此处可能需要说明一下算术右移和逻辑右移的区别:
    逻辑右移就是不考虑符号位,右移一位,左边补零即可。
    算术右移需要考虑符号位,右移一位,若符号位为1,就在左边补1,;否则,就补0。

    1. assign sll_result=alu_src2<<alu_src1[4:0];
    2. assign arl_result=alu_src2>>alu_src1[4:0];
    3. assign sra_result=($signed(alu_src2))>>>alu_src1[4:0];

    最后对alu_result赋值即可,这个赋值的格式需要注意,很有用!

    1. assign alu_result=({32{op_add|op_sub}}&add_sub_result)
    2. |({32{op_slt }}&slt_result)
    3. |({32{op_sltu }}&sltu_result)
    4. |({32{op_and }}&and_result)
    5. |({32{op_nor }}&nor_result)
    6. |({32{op_or }}&or_result)
    7. |({32{op_xor }}&xor_result)
    8. |({32{op_sll }}&sll_result)
    9. |({32{op_srl }}&srl_result)
    10. |({32{op_sra }}&sra_result)
    11. |({32{op_lui }}&lui_result);