一文学会制作riscvCPU
在本节中将会介绍如何进行alu的实现
ALU
module alu (
/* ALU 端口 */
input [`XLEN-1:0] alu_a_i,
input [`XLEN-1:0] alu_b_i,
input [`ALUOP_LEN-1:0] alu_op_i,
output [`XLEN-1:0] alu_out,
//比较指令输出
output compare_out
);
Input | 位宽 | Output | 位宽 |
---|---|---|---|
alu_a_i(ALU的第一个操作数) | 64 | alu_out | 64 |
alu_b_i(ALU的第二个操作数) | 64 | compare_out | |
alu_op_i(ALU的操作符) | 6 |
解析ALU的指令
//加减和逻辑
wire _aluop_add = (alu_op_i == `ALUOP_ADD);
wire _aluop_sub = (alu_op_i == `ALUOP_SUB);
wire _aluop_xor = (alu_op_i == `ALUOP_XOR);
wire _aluop_or = (alu_op_i == `ALUOP_OR);
wire _aluop_and = (alu_op_i == `ALUOP_AND);
//移位
wire _aluop_sll = (alu_op_i == `ALUOP_SLL);
wire _aluop_srl = (alu_op_i == `ALUOP_SRL);
wire _aluop_sra = (alu_op_i == `ALUOP_SRA);
wire _aluop_sllw = (alu_op_i == `ALUOP_SLLW);
wire _aluop_srlw = (alu_op_i == `ALUOP_SRLW);
wire _aluop_sraw = (alu_op_i == `ALUOP_SRAW);
//比较
wire _aluop_slt = (alu_op_i == `ALUOP_SLT);
wire _aluop_sltu = (alu_op_i == `ALUOP_SLTU);
wire _aluop_beq = (alu_op_i == `ALUOP_BEQ);
wire _aluop_bne = (alu_op_i == `ALUOP_BNE);
wire _aluop_blt = (alu_op_i == `ALUOP_BLT);
wire _aluop_bge = (alu_op_i == `ALUOP_BGE);
wire _aluop_bltu = (alu_op_i == `ALUOP_BLTU);
wire _aluop_bgeu = (alu_op_i == `ALUOP_BGEU);
//乘法
wire _aluop_mul = (alu_op_i == `ALUOP_MUL);
wire _aluop_mulh = (alu_op_i == `ALUOP_MULH);
wire _aluop_mulhsu = (alu_op_i == `ALUOP_MULHSU);
wire _aluop_mulhu = (alu_op_i == `ALUOP_MULHU);
wire _aluop_mulw = (alu_op_i == `ALUOP_MULW);
//除法
wire _aluop_div = (alu_op_i == `ALUOP_DIV);
wire _aluop_divu = (alu_op_i == `ALUOP_DIVU);
wire _aluop_rem = (alu_op_i == `ALUOP_REM);
wire _aluop_remu = (alu_op_i == `ALUOP_REMU);
wire _aluop_divw = (alu_op_i == `ALUOP_DIVW);
wire _aluop_divuw = (alu_op_i == `ALUOP_DIVUW);
wire _aluop_remw = (alu_op_i == `ALUOP_REMW);
wire _aluop_remuw = (alu_op_i == `ALUOP_REMUW);
加法 减法 以及比较器的实现
比较运算符实际上能够通过减法设置标志位来实现,本文的实现采用计算机组成原理中的实现方法有如下代码:
标志位需参考:
https://blog.youkuaiyun.com/mariodf/article/details/125334271*/
wire _isCMP = _aluop_slt | _aluop_bgeu | //比较类运算
_aluop_sltu |_aluop_beq |
_aluop_bne |_aluop_blt |
_aluop_bge|_aluop_bltu ;
/* 如果是减法、比较操作则进行减法 */
wire _isSUBop = _aluop_sub | _isCMP;
/* 进位 */
wire [`XLEN:0] _cin = {{64{1'b0}}, _isSUBop}; //减法的加1
/* 扩展为双符号位 */
wire [`XLEN:0] _alu_a = {{1{alu_a_i[`XLEN-1]}}, alu_a_i};
wire [`XLEN:0] _alu_b = {{1{alu_b_i[`XLEN-1]}}, alu_b_i} ^ {65{_isSUBop}}; //异或实现取反
wire [`XLEN:0] _add_out;
/* 加法器 */
assign _add_out = _alu_a + _alu_b + _cin; //取反加1
/* 标志位生成 具体看https://blog.youkuaiyun.com/mariodf/article/details/125334271*/
//通过真值表得到,最高位进位,用于计算 CF 标志位
wire _tb_A = _alu_a[`XLEN];
wire _tb_B = _alu_b[`XLEN];
wire _tb_C = _add_out[`XLEN];
wire _tb_NOTA = ~_tb_A;
wire _tb_NOTB = ~_tb_B;
wire _tb_NOTC = ~_tb_C;
// 最高位进位,(测试)
wire _isC64in = (_tb_A|_tb_B|_tb_C) &
(_tb_A|_tb_NOTB|_tb_NOTC)&
(_tb_NOTA|_tb_B|_tb_NOTC)&
(_tb_NOTA|_tb_NOTB|_tb_C);
wire _isZero = (_add_out == 65'd0);
wire _isOF = _add_out[`XLEN] ^ _add_out[`XLEN-1];
wire _isSF = _add_out[`XLEN-1]; //leesum(bug),最高位为扩展符号位,次高位为原始符号位
wire _isCF = _isSUBop ^ _isC64in;
/* 比较信息 具体看 obsidian 笔记 */
// wire _isSLT = _isOF ^ _add_out[`XLEN-1];
wire _isSLT = _isSF ^ _isOF; // a<b (signed)
wire _isSLTU = _isCF; //a<b (unsigned)
wire _isBLT = _isSLT; // a<b(signed)
wire _isBLTU = _isSLTU; // a<b(unsigned)
wire _isBGE = ~_isSLT; // a>=b(signed)
wire _isBGEU = ~_isSLTU; //a>=b(unsigned)
wire _isBEQ = _isZero; //a==b
wire _isBNE = ~_isZero; //a!=b
/* 并行多路选择器,选择比较输出 */
wire _compare_out = ((_aluop_slt|_aluop_blt)&_isSLT)|
((_aluop_sltu|_aluop_bltu)&_isSLTU)|
((_aluop_beq)&_isBEQ)|
((_aluop_bne)&_isBNE)|
((_aluop_bge)&_isBGE)|
((_aluop_bgeu)&_isBGEU);
上述代码采用双符号位实现加法 减法以及比较运算,最终加法运算的结果,与减法运算的结果相同,因为减一个数等于加一个数的取反加一(得到其负数的补码)。
移位器运算
wire _shift_sra = _aluop_sra | _aluop_sraw; //算数右移
wire _shift_srl = _aluop_srl | _aluop_srlw; //逻辑右移
wire _shift_sll = _aluop_sll | _aluop_sllw; //逻辑左移
首先分析是何种移位运算,是算术右移、逻辑右移、逻辑左移、是否是32位,我们将进行移位的操作数,移位次数作为输入,移位结果作为输出
wire _isshift32 = _aluop_sllw | _aluop_sraw | _aluop_srlw; //是否忽略高32位
wire [`XLEN-1:0] _shift_num = alu_a_i; //进行移位的操作数
wire [5:0] _shift_count = alu_b_i[5:0]; //移位次数
wire [`XLEN-1:0] _shift_out; //移位结果
alu_shift u_alu_shift (
.shift_sra (_shift_sra),
.shift_srl (_shift_srl),
.shift_sll (_shift_sll),
.isshift32 (_isshift32),
.shift_num (_shift_num),
.shift_count(_shift_count),
.shift_out (_shift_out)
);
移位器代码
module alu_shift (
input shift_sra,
input shift_srl,
input shift_sll,
input isshift32,
input [`XLEN-1:0] shift_num,
input [5:0] shift_count,
output [`XLEN-1:0] shift_out
);
判断被移位数是什么
wire _op_shift = shift_sra | shift_srl | shift_sll;
/* 选择是否忽略高32位 */
wire [`XLEN-1:0] _shift_num = (isshift32) ? {32'b0, shift_num[31:0]} : shift_num;
我们通过控制信号isshift32对被移位数进行判断
逻辑移位(公用一个移位器 利用位颠倒)
wire [`XLEN-1:0] _shift_num = (isshift32) ? {32'b0, shift_num[31:0]} : shift_num;
wire [`XLEN-1:0] _shift_num_inv;
/* 位颠倒 */
Vectorinvert #(
.DATA_LEN(`XLEN)
) u_Vectorinvert1 (
.in (_shift_num),
.out(_shift_num_inv)
);
//将右移转换为左移
wire [`XLEN-1:0] _shifter_in1 = {`XLEN{_op_shift}} & ((shift_sra | shift_srl) ? _shift_num_inv : _shift_num);//操作数
wire [5:0] _shifter_in2 = (isshift32) ? {1'b0, shift_count[4:0]} : shift_count; //TODO:BUG(很坑)移位次数
/* 实际移位操作,用一个移位器实现左移和右移 */
wire [`XLEN-1:0] _shifter_res = _shifter_in1 << _shifter_in2;
wire [`XLEN-1:0] _sll_res = _shifter_res; //逻辑左移结果
/*逻辑右移结果,srl_in->位颠倒->移位器(左移)->位颠倒->srl_out*/
wire [`XLEN-1:0] _srl_res;
Vectorinvert #(
.DATA_LEN(`XLEN)
) u_Vectorinvert2 (
.in (_sll_res),
.out(_srl_res)
);
- 进行位颠倒
- 如果是右移操作则将颠倒后的数作为移位书,如果是左移则不改变
- 进行左移位
- 将左移后的在颠倒实际上就为逻辑右移
上述就通过一个移位器实现了逻辑左移与右移
算术移位 算术右移
通过掩码的思路实现算术右移,本质上就是通过逻辑右移后的值进行处理
/* 选择掩码,64位移位和32位移位掩码不同 */
wire [5:0] _eff_mask_shift_count = (isshift32) ? (_shifter_in2 + 6'd32) : _shifter_in2;
/* 选择符号位,32位移位需要忽略输入num的高32位 */
wire _lastbit = (isshift32) ? _shift_num[31] : _shift_num[`XLEN-1];
/* 算数右移结果,采用掩码算法实现算数右移 */
wire [`XLEN-1:0] _eff_mask = (~(`XLEN'b0)) >> _eff_mask_shift_count;
wire [`XLEN-1:0] _sra_res = (_srl_res & _eff_mask) | ({`XLEN{_lastbit}} & (~_eff_mask));
多路选择 决定移位的最终输出
wire [`XLEN-1:0] _shift_out = ({`XLEN{shift_srl}}&_srl_res) |
({`XLEN{shift_sra}}&_sra_res) |
({`XLEN{shift_sll}}&_sll_res);
assign shift_out = _shift_out;
逻辑运算
wire [`XLEN-1:0] _and_res = alu_a_i & alu_b_i;
wire [`XLEN-1:0] _or_res = alu_a_i | alu_b_i;
wire [`XLEN-1:0] _xor_res = alu_a_i ^ alu_b_i;
乘法运算
wire _is_mul_sr1_signed = _aluop_mul | _aluop_mulh | _aluop_mulhsu | _aluop_mulw;
wire _is_mul_sr2_signed = _aluop_mul | _aluop_mulh | _aluop_mulw;
wire [`XLEN*2-1:0] _mul_result;
alu_mul_top u_alu_mul_top (
.is_sr1_signed(_is_mul_sr1_signed),
.is_sr2_signed(_is_mul_sr2_signed),
.sr1_data (alu_a_i),
.sr2_data (alu_b_i),
.mul_result (_mul_result)
);
- 是否需要src1、src2需要进行符号拓展
alu_mul_top
module alu_mul_top (
// input clk, //为流水线准备
// input rst,
input is_sr1_signed,
input is_sr2_signed,
input [`XLEN-1:0] sr1_data,
input [`XLEN-1:0] sr2_data,
output [`XLEN*2-1:0] mul_result
);
/* 双符号位扩展 */
wire _sr1_sign = (is_sr1_signed) ? sr1_data[`XLEN-1] : 1'b0;
wire _sr2_sign = (is_sr2_signed) ? sr2_data[`XLEN-1] : 1'b0;
/* 共65位 */
wire [`XLEN:0] _sr1_65 = {_sr1_sign, sr1_data};
wire [`XLEN:0] _sr2_65 = {_sr2_sign, sr2_data};
wire [`XLEN*2-1:0] _mul_result = _sr1_65 * _sr2_65;
assign mul_result = _mul_result;
endmodule
- 根据是否带有符号位,进行符号拓展
- 进行双符号位进行相乘得到最终结果
不同乘法指令的结果
/* 不同乘法指令的结果 */
wire [`XLEN-1:0] _inst_mul_result = _mul_result[`XLEN-1:0];
wire [`XLEN-1:0] _inst_mulh_mulhsu_mulhu_result = _mul_result[`XLEN*2-1:`XLEN];
// w 指令的符号扩展统一在 execute 中执行.
wire [`XLEN-1:0] _inst_mulw_result = {32'b0, _mul_result[31:0]};
除法运算
wire _is_div_signed = _aluop_div | _aluop_divw | _aluop_rem | _aluop_remw;
wire _is_div32 = _aluop_divw | _aluop_divuw | _aluop_remw | _aluop_remuw;
wire [`XLEN-1:0] _div_result, _rem_result;
div_top
module alu_div_top (
// input clk, //为流水线准备
// input rst,
input issigned,
input isdivw,
input [`XLEN-1:0] sr1_data,
input [`XLEN-1:0] sr2_data,
output [`XLEN-1:0] div_result,
output [`XLEN-1:0] rem_result
);
/* 64 位除法 */
wire signed [`XLEN-1:0] sr1_64_signed = sr1_data;
wire signed [`XLEN-1:0] sr2_64_signed = sr2_data;
// 有符号
wire signed [`XLEN-1:0] div64_signed = sr1_64_signed / sr2_64_signed;
wire signed [`XLEN-1:0] rem64_signed = sr1_64_signed % sr2_64_signed;
// 无符号
wire [`XLEN-1:0] div64_unsigned = sr1_data / sr2_data;
wire [`XLEN-1:0] rem64_unsigned = sr1_data % sr2_data;
// 结果
wire [`XLEN-1:0] div64_result = (issigned) ? div64_signed : div64_unsigned;
wire [`XLEN-1:0] rem64_result = (issigned) ? rem64_signed : rem64_unsigned;
/* 32 位除法 */
wire signed [32-1:0] sr1_32_signed = sr1_data[31:0];
wire signed [32-1:0] sr2_32_signed = sr2_data[31:0];
//有符号
wire signed [32-1:0] div32_signed = sr1_32_signed / sr2_32_signed;
wire signed [32-1:0] rem32_signed = sr1_32_signed % sr2_32_signed;
//无符号
wire [32-1:0] div32_unsigned = sr1_data[31:0] / sr2_data[31:0];
wire [32-1:0] rem32_unsigned = sr1_data[31:0] % sr2_data[31:0];
//结果
wire [32-1:0] div32_result = (issigned) ? div32_signed : div32_unsigned;
wire [32-1:0] rem32_result = (issigned) ? rem32_signed : rem32_unsigned;
// 最终结果
assign div_result = (isdivw) ? {32'b0, div32_result} : div64_result;
assign rem_result = (isdivw) ? {32'b0, rem32_result} : rem64_result;
endmodule
- 是否为32位除法,是否带有符号位除法
- 利用wiresigned方式实现有符号运算
- 得到符号运算的除法 与 取余
- 在32位除法中,采用相同的方法,并对最后的结果进行输出
alu_out 与 compare_out
assign alu_out = (_isCMP) ? {63'b0, _compare_out} : _alu_out;
assign compare_out = _compare_out;
判断是否是比较指令,随后进行相应的输出
Regfile
将数据存储到通用寄存器当中,输入输出列表如下:
module rv64reg (
input clk,
/* 读取数据 */
input wire [`REG_ADDRWIDTH-1:0] rs1_idx,
input wire [`REG_ADDRWIDTH-1:0] rs2_idx,
output wire [`XLEN-1:0] rs1_data,
output wire [`XLEN-1:0] rs2_data,
/* 写入数据 */
input wire [`REG_ADDRWIDTH-1:0] write_idx,
input wire [`XLEN-1:0] write_data,
input wire wen
);
/* 寄存器组 */
reg [`XLEN-1:0] rf[`REG_NUM-1:0];
/* 写入数据,若目的寄存器为 x0,写入数据永远为0 */
wire _isX0 = (write_idx == `REG_ADDRWIDTH'b0);
wire [`XLEN-1:0] _write_data = (_isX0) ? `XLEN'b0 : write_data; // x0 恒为0
/* 写入使能 */
wire _wen = wen;
always @(posedge clk) begin
if (_wen) rf[write_idx] <= _write_data;
end
/* 读取数据 */
assign rs1_data = rf[rs1_idx];
assign rs2_data = rf[rs2_idx];
- 寄存器初始化
- 设定当寄存器为0则写入数据为0
- 每个时钟的上升沿进行赋值