21 一文学会制作riscvCPU(第四弹)

一文学会制作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的第一个操作数)64alu_out64
alu_b_i(ALU的第二个操作数)64compare_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.csdn.net/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.csdn.net/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)
  );
  1. 进行位颠倒
  2. 如果是右移操作则将颠倒后的数作为移位书,如果是左移则不改变
  3. 进行左移位
  4. 将左移后的在颠倒实际上就为逻辑右移
    上述就通过一个移位器实现了逻辑左移与右移

算术移位 算术右移

通过掩码的思路实现算术右移,本质上就是通过逻辑右移后的值进行处理

  /* 选择掩码,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)
  );


  1. 是否需要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

  1. 根据是否带有符号位,进行符号拓展
  2. 进行双符号位进行相乘得到最终结果

不同乘法指令的结果

  /* 不同乘法指令的结果 */
  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
  1. 是否为32位除法,是否带有符号位除法
  2. 利用wiresigned方式实现有符号运算
  3. 得到符号运算的除法 与 取余
  4. 在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];
  1. 寄存器初始化
  2. 设定当寄存器为0则写入数据为0
  3. 每个时钟的上升沿进行赋值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值