首先第一个实现的功能是ALU模块,主要还是对书中代码的复现从中学会了很多先在就来表述一下。
首先看模块声明
module ALU(
input [11:0] alu_control,//控制采用11位的one-hot
input [31:0] alu_src1,
input [31:0] alu_src2,
output [31:0]alu_result
);
可以看出
,输入一个十一位的控制信号,使用的是独热码(one-hot),即一共有11条指令被实现。
两个操作数alu_src1,和alu_src2,最后output的结果是alu_result.
实现的指令:
add sub slt sltu and nor or xor sll srl sra lui
对于指令的读取,按照独热码一位表示一个数
assign op_add=alu_control[0];
assign op_sub=alu_control[1];
assign op_alt=alu_control[2];
assign op_sltu=alu_control[3];
assign op_and=alu_control[4];
assign op_nor=alu_control[5];
assign op_or=alu_control[6];
assign op_xor=alu_control[7];
assign op_sll=alu_control[8];
assign op_srl=alu_control[9];
assign op_sra=alu_control[10];
assign op_lui=alu_control[11];
然后对于输出结果声明为wire(其实这里比较疑惑,为什么不可以只用一个32位的wire反而要声明这么多的结果?)
wire [31:0]add_sub_result;
wire [31:0]slt_result;//有符号比较,小于置位
wire [31:0]sltu_result;//无符号比较,小于置位
wire [31:0]and_result;
wire [31:0]nor_result;
wire [31:0]or_result;
wire [31:0]xor_result;
wire [31:0]sll_result;
wire [31:0]srl_result;
wire [31:0]sra_result;
wire [31:0]lui_result;//高位加载
然后首先进行最简单的逻辑运算,这里可以直接使用verilog的逻辑运算符,所以没有什么技术难度,感觉也不会有什么提升效率的空间(此处存疑)
贴上代码:
assign and_result=alu_src1&alu_src2;
assign or_result=alu_src1|alu_src2;
assign nor_result=~or_result;
assign xor_result=alu_src1^alu_src2;
assign lui_result={alu_src2[15:0],16'b0};
然后进行加法器的运算:
首先是一些wire的声明:
wire [31:0]adder_a;
wire [31:0]adder_b;
wire adder_cin;
wire [31:0]adder_result;
wire adder_cout;
需要注意的是,计算机中,数值的存储方式为补码,所以模拟的内存也进行了补码存储,此处的计算就是对补码进行计算。需要注意的是,此处加法和减法可以做统一处理。
assign adder_a=alu_src1;
assign b=(op_sub|op_slt|op_sltu)?~alu_src2:alu_src2;//取反
assign adder_cin=(op_sub|op_slt|op_sltu)?1'b1:1'b0;//还原补码
assign {adder_cout,adder_result}=adder_a+adder_b+adder_cin;//存贮结果和进位
assign add_sub_result=adder_result;
接下来是slt,sltu这两个指令略有差别,即有符号数和无符号数的差别,他们首先比较两个寄存器数值的大小,如果小于就置末位为1,否则为0.(需要说明的是,第三行的代码,即result[0]的置位代码我看的不是很懂)
assign add_sub_result=adder_result;
assign slt_result[31:1]=31'b0;
assign slt_result[0]=(alu_src1[31]&~alu_src2[31])|(~(alu_src1[31]^alu_src2[31])&adder_result[31]);
//计算两个寄存器值的大小
assign sltu_result[0]=~adder_cout;
下面时两个逻辑左移和右移,还有一个算术右移。
此处可能需要说明一下算术右移和逻辑右移的区别:
逻辑右移就是不考虑符号位,右移一位,左边补零即可。
算术右移需要考虑符号位,右移一位,若符号位为1,就在左边补1,;否则,就补0。
assign sll_result=alu_src2<<alu_src1[4:0];
assign arl_result=alu_src2>>alu_src1[4:0];
assign sra_result=($signed(alu_src2))>>>alu_src1[4:0];
最后对alu_result赋值即可,这个赋值的格式需要注意,很有用!
assign alu_result=({32{op_add|op_sub}}&add_sub_result)
|({32{op_slt }}&slt_result)
|({32{op_sltu }}&sltu_result)
|({32{op_and }}&and_result)
|({32{op_nor }}&nor_result)
|({32{op_or }}&or_result)
|({32{op_xor }}&xor_result)
|({32{op_sll }}&sll_result)
|({32{op_srl }}&srl_result)
|({32{op_sra }}&sra_result)
|({32{op_lui }}&lui_result);