一文学会如何设计属于你自己的CPU riscv
在上述文章中,我们描述了取指令以及解析指令,在进行完解析指令后我们在本节中讲解内存以及执行的概括。
memory 内存指令的处理
module memory (
input clk,
input rst,
input [ `XLEN-1:0] pc,
input [`REG_ADDRWIDTH-1:0] rd_idx,
input [ `XLEN-1:0] rs1_data,
input [ `XLEN-1:0] rs2_data,
input [ `IMM_LEN-1:0] imm_data,
input [ `MEMOP_LEN-1:0] mem_op, // 访存操作码
input [`XLEN-1:0] exc_in,
output [`XLEN-1:0] mem_out,
output isloadEnable //读数据使能
);
对上述信号进行解析:
Input | 位宽 | Output | 位宽 |
---|---|---|---|
clk | 1 | mem_out(l从内存中读取出来的数据) | 64 |
rst | 1 | isloadEnable(是否进行读取使能) | 1 |
pc(当前的PC) | 64 | ||
rd_idx(rd 通用寄存器索引) | 5 | ||
rs1_data (rs1的数据) | 64 | ||
rs2_data (rs2的数据) | 64 | ||
imm_data (imm的数据) | 64 | ||
mem_op (内存操作的类型) | 4 | ||
exc_in | 64 (execute 进行alu处理完后的数据) |
具体代码详解
wire _memop_none = (mem_op == `MEMOP_NONE);
wire _memop_lb = (mem_op == `MEMOP_LB);
wire _memop_lbu = (mem_op == `MEMOP_LBU);
wire _memop_sb = (mem_op == `MEMOP_SB);
wire _memop_lh = (mem_op == `MEMOP_LH);
wire _memop_lhu = (mem_op == `MEMOP_LHU);
wire _memop_sh = (mem_op == `MEMOP_SH);
wire _memop_lw = (mem_op == `MEMOP_LW);
wire _memop_lwu = (mem_op == `MEMOP_LWU);
wire _memop_sw = (mem_op == `MEMOP_SW);
wire _memop_ld = (mem_op == `MEMOP_LD);
wire _memop_sd = (mem_op == `MEMOP_SD);
/* 写入还是读取 */
wire _isload = (_memop_lb |_memop_lbu |_memop_ld|_memop_lh|_memop_lhu|_memop_lw|_memop_lwu)&(~_memop_none);
wire _isstore = (_memop_sb | _memop_sd | _memop_sh | _memop_sw) & (~_memop_none);
/* 读取或写入的 byte */
wire _ls8byte = _memop_lb | _memop_lbu | _memop_sb;
wire _ls16byte = _memop_lh | _memop_lhu | _memop_sh;
wire _ls32byte = _memop_lw | _memop_sw | _memop_lwu;
wire _ls64byte = _memop_ld | _memop_sd;
/* 是否进行符号扩展 */
wire _unsigned = _memop_lhu | _memop_lbu | _memop_lwu;
wire _signed = _memop_lh | _memop_lb | _memop_lw | _memop_ld;
/* 输出使能端口 */
wire _isloadEnable = _unsigned | _signed;
assign isloadEnable = _isloadEnable;
/* 从内存中读取的数据 */
wire [`XLEN-1:0] _mem_read;
/* 符号扩展后的结果 TODO:改成并行编码*/
wire [ `XLEN-1:0] _mem__signed_out = (_ls8byte)?{{`XLEN-8{_mem_read[7]}},_mem_read[7:0]}:
(_ls16byte)?{{`XLEN-16{_mem_read[15]}},_mem_read[15:0]}:
(_ls32byte)?{{`XLEN-32{_mem_read[31]}},_mem_read[31:0]}:
_mem_read;
/* 不进行符号扩展的结果 TODO:改成并行编码 */
wire [ `XLEN-1:0] _mem__unsigned_out = (_ls8byte)?{{`XLEN-8{1'b0}},_mem_read[7:0]}:
(_ls16byte)?{{`XLEN-16{1'b0}},_mem_read[15:0]}:
(_ls32byte)?{{`XLEN-32{1'b0}},_mem_read[31:0]}:
_mem_read;
/* 读取数据:选择最终结果 */
wire [`XLEN-1:0] _mem_out = (_signed) ? _mem__signed_out:
(_unsigned)? _mem__unsigned_out:
`XLEN'b0;
assign mem_out = _mem_out;
/* 写入数据 */
wire [`XLEN-1:0] _mem_write = (_ls8byte) ? {56'b0, rs2_data[7:0]} :
(_ls16byte) ? {48'b0, rs2_data[15:0]}:
(_ls32byte) ? {32'b0, rs2_data[31:0]}:
rs2_data;
/* 写数据 mask 选择,_mask:初步选择 _wmask:最终选择 */
wire [7:0] _mask = ({8{_ls8byte}}&8'b0000_0001) |
({8{_ls16byte}}&8'b0000_0011) |
({8{_ls32byte}}&8'b0000_1111) |
({8{_ls64byte}}&8'b1111_1111);
wire [7:0] _wmask = (_isstore) ? _mask : 8'b0000_0000;
/* 地址 */
wire [`XLEN-1:0] _addr = (_memop_none) ? `PC_RESET_ADDR : exc_in;
wire [`XLEN-1:0] _raddr = _addr;
wire [`XLEN-1:0] _waddr = _addr;
/***************************内存读写**************************/
import "DPI-C" function void pmem_read(
input longint raddr,
output longint rdata
);
import "DPI-C" function void pmem_write(
input longint waddr,
input longint wdata,
input byte wmask
);
// always @(*) begin
// pmem_read(_raddr, _mem_read);
// pmem_write(_waddr, _mem_write, _wmask);
// end
always @(posedge clk) begin
pmem_read(_raddr, _mem_read);
pmem_write(_waddr, _mem_write, _wmask);
end
endmodule
- 对存储操作中存储的指令进行解析,得到具体是哪个指令被采取
- 区分指令类型,是load型 或 store 类型
- 区分指令的位宽,是读取或写入 8位 16位 32位 64位中的哪种
- 区分是否进行符号拓展,只有在读取数据时需要进行分类
- 添加输出信号,是否存在读取指令
- 对输出的信号进行符号拓展
通过掩码实现对不同位数的数据进行读取
wire [7:0] _mask = ({8{_ls8byte}}&8'b0000_0001) |
({8{_ls16byte}}&8'b0000_0011) |
({8{_ls32byte}}&8'b0000_1111) |
({8{_ls64byte}}&8'b1111_1111);
掩码的目的在于写数据的时候进行位宽的判断,如果不写入数据时,则将掩码设置为0,不对数据进行存储
wire [7:0] _wmask = (_isstore) ? _mask : 8'b0000_0000;
通过DPI-C机制对数据进行读写
读写的技巧:
wire [`XLEN-1:0] _addr = (_memop_none) ? `PC_RESET_ADDR : exc_in;
wire [`XLEN-1:0] _raddr = _addr;
wire [`XLEN-1:0] _waddr = _addr;
上述代码首先通过判断是否是存储指令,如果不是存储指令则将读取的初始值设置为PC的起始值,目的是防止读取过界,出现outofmem的情况
exc_in为excute计算出来的地址
import "DPI-C" function void pmem_read(
input longint raddr,
output longint rdata
);
import "DPI-C" function void pmem_write(
input longint waddr,
input longint wdata,
input byte wmask
);
always @(posedge clk) begin
pmem_read(_raddr, _mem_read);
pmem_write(_waddr, _mem_write, _wmask);
end
上述代码通过DPI-C机制对verilator中的存储进行读取,读取代码如下所示:
extern "C" void pmem_read(long long raddr, long long* rdata ) {
if(raddr == 0) {
return ;
}
if((uint64_t)raddr == 0xa0000048) {
*rdata = get_time();
} else *rdata = St->mem->paddr_read(raddr, 8);
}
extern "C" void pmem_write(long long waddr, long long wdata, char wmask) {
// 总是往地址为`waddr & ~0x7ull`的8字节按写掩码`wmask`写入`wdata`
// `wmask`中每比特表示`wdata`中1个字节的掩码,
if((uint64_t)waddr == 0xa00003f8) {
putchar((char)wdata);
} else {
uint32_t temp = (uint8_t)wmask;
switch (temp) {
case 1: St->mem->paddr_write(waddr, 1, wdata); break; // 0000_0001, 1byte.
case 3: St->mem->paddr_write(waddr, 2, wdata); break; // 0000_0011, 2byte.
case 15: St->mem->paddr_write(waddr, 4, wdata); break; // 0000_1111, 4byte.
case 255: St->mem->paddr_write(waddr, 8, wdata); break; // 1111_1111, 8byte.
default: break;
}
}
}
上述就是内存数据读取的关键,接下来给大家介绍执行模块,执行模块是整个CPU的计算中枢
execute
首先老规矩继续分析执行模块的输入与输出
module execute (
input [ `XLEN-1:0] pc,
input [`REG_ADDRWIDTH-1:0] rd_idx,
input [ `XLEN-1:0] rs1_data,
input [ `XLEN-1:0] rs2_data,
input [ `IMM_LEN-1:0] imm_data,
/* CSR 译码结果 */
input [ `XLEN-1:0] csr_data,
input [ `IMM_LEN-1:0] imm_CSR,
input isNeedimmCSR,
input [`ALUOP_LEN-1:0] alu_op, // alu 操作码
input [`MEMOP_LEN-1:0] mem_op, // 访存操作码
input [`EXCOP_LEN-1:0] exc_op, // exc 操作码
input [`CSROP_LEN-1:0] csr_op, // exc_csr 操作码
output [ `XLEN-1:0] exc_alu_out,
output [ `XLEN-1:0] exc_csr_out,
output exc_csr_valid
);
Input | 位宽 | Output | 位宽 |
---|---|---|---|
pc(当前的pc值) | 64 | exc_alu_out(alu 计算出来的输出值) | 64 |
rd_idx(rd 的索引值) | 5 | exc_csr_out(执行csr相关操作的输出值) | 64 |
rs1_data(rs1 通用寄存器存储的值) | 64 | exc_csr_valid(是否执行csr相关指令) | 1 |
rs2_data(rs2 通用寄存器存储的值) | 64 | ||
imm_data(立即数数据) | 64 | ||
csr_data(csr存储的数据) | 64 | ||
imm_CSR(CSR存储的立即数) | 64 | ||
isNeedimmCSR(CSR 是否需要立即数) | 1 | ||
alu_op(alu的操作码) | 6 | ||
mem_op(mem的操作码)4 | |||
exc_op(操作类似指令的操作码) | 5 | ||
csr_op(csr相关指令的操作码) | 3 |
解析出对应的执行指令
wire _excop_auipc = (exc_op == `EXCOP_AUIPC);
wire _excop_lui = (exc_op == `EXCOP_LUI);
wire _excop_jal = (exc_op == `EXCOP_JAL);
wire _excop_jalr = (exc_op == `EXCOP_JALR);
wire _excop_load = (exc_op == `EXCOP_LOAD);
wire _excop_store = (exc_op == `EXCOP_STORE);
wire _excop_branch = (exc_op == `EXCOP_BRANCH);
wire _excop_opimm = (exc_op == `EXCOP_OPIMM);
wire _excop_opimm32 = (exc_op == `EXCOP_OPIMM32);
wire _excop_op = (exc_op == `EXCOP_OP);
wire _excop_op32 = (exc_op == `EXCOP_OP32);
wire _excop_csr = (exc_op == `EXCOP_CSR);
wire _excop_ebreak = (exc_op == `EXCOP_EBREAK);
wire _excop_none = (exc_op == `EXCOP_NONE);
上述代码通过exc_op中的数据存储对应的操作类型,将操作类型取出进行分析,alu_in1 与 alu_in2两个操作数的值
,首先我们对两个不同的操作数进行分类有如下代码:
/* ALU 两端操作数选择 */
wire _rs1_rs2 = _excop_op32 | _excop_op | _excop_branch;
wire _rs1_imm = _excop_opimm | _excop_opimm32 | _excop_load | _excop_store;
wire _pc_4 = _excop_jal | _excop_jalr;
wire _pc_imm12 = _excop_auipc;
wire _none_imm12 = _excop_lui;
wire _none_csr = _excop_csr; //保存原来的 csr
通过下面的代码将两个操作数进行解析
wire [`IMM_LEN-1:0] _imm_aui_auipc = {imm_data[`IMM_LEN-1:12], 12'b0};
// ALU 第一个操作数
wire [ `XLEN-1:0] _alu_in1 = ({`XLEN{_rs1_rs2 | _rs1_imm}}&rs1_data) |
({`XLEN{_pc_4 | _pc_imm12}}&pc) |
({`XLEN{_none_imm12|_none_csr}}&`XLEN'b0);
// ALU 第二个操作数
wire [ `XLEN-1:0] _alu_in2 = ({`XLEN{_rs1_rs2}}&rs2_data) |
({`XLEN{_rs1_imm}}&imm_data) |
({`XLEN{_none_csr}}&csr_data) |
({`XLEN{_pc_4}}&`XLEN'd4) |
({`XLEN{_pc_imm12|_none_imm12}}&_imm_aui_auipc);
由于auipc的立即数具有特殊性,进行单独的提取,也可以在译码的时候进行添加一路选择器将R型指令进行处理,这里我们可以选择这种方式也可以选择通过立即数类型不同的方式进行分类
埋坑:此时 rs2_data imm_data csr_data在regfile文件中后续会进行讲解
随后进入alu的端口
在ALU中我们把ALU的操作类型alu_op作为输入以及操作数alu_1 与 alu_2作为输入,最终得到alu_out
wire [`XLEN-1:0] _alu_out;
wire _compare_out;
alu u_alu (
/* ALU 端口 */
.alu_a_i(_alu_in1),
.alu_b_i(_alu_in2),
.alu_out(_alu_out),
.alu_op_i(alu_op),
.compare_out(_compare_out)
);
wire _alu_sext = _excop_opimm32 | _excop_op32;
wire [`XLEN-1:0] _alu_sext_out = {{32{_alu_out[31]}}, _alu_out[31:0]};
assign exc_alu_out = (_alu_sext) ? _alu_sext_out : _alu_out;
并对需要符号拓展的进行特殊处理
随后进入CSR相关的执行操作
我们将 CSR_imm作为输入,rs1_data与csr_data作为输入,以及是否是立即数isNeedimmCSR作为输入,以及csr的操作类型csr_op,这里信号输入过多,埋坑需要改进把输出结果说是否有效作为输出
wire [`XLEN-1:0] _csr_exe_result;
wire _csr_exe_valid;
execute_csr u_execute_csr (
.imm_CSR_i (imm_CSR),
.isNeedimmCSR_i (isNeedimmCSR), // 是否是立即数指令
.rs1_data_i (rs1_data), // rs1 data
.csr_data_i (csr_data), // 读取的 CSR 数据
.csr_op_i (csr_op), // csr 操作码
.csr_exe_result (_csr_exe_result),
.csr_exe_valid (_csr_exe_valid)
);
assign exc_csr_out = _csr_exe_result;
assign exc_csr_valid = _csr_exe_valid;
特殊指令特殊处理 ebreak
通过verilator的特殊,当ebreak指令进行时,则结束仿真。
always @(*) begin
if (_excop_ebreak) begin
$finish;
end
end
excute_csr 对csr进行处理
module execute_csr (
input [ `IMM_LEN-1:0] imm_CSR_i,
input isNeedimmCSR_i, // 是否是立即数指令
input [ `XLEN-1:0] rs1_data_i, // rs1 data
input [ `XLEN-1:0] csr_data_i, // 读取的 CSR 数据
input [`CSROP_LEN-1:0] csr_op_i, // csr 操作码
output [ `XLEN-1:0] csr_exe_result,
output csr_exe_valid
);
对上述输入进行分析
Input | 位宽 | output | 位宽 |
---|---|---|---|
imm_CSR_i(CSR的立即数) | 64 | csr_exe_result | 64 |
isNeedimmCSR(是否需要CSR立即数) | 1 | csr_exe_valid | 1 |
rs1_data_i(rs1数据) | 64 | ||
csr_data_i (csr的数据) | 64 | ||
csr_op_i(csr的操作码) | 3 |
通过操作码读出指令类型
wire _csr_write = (csr_op_i == `CSROP_WRITE);
wire _csr_set = (csr_op_i == `CSROP_SET);
wire _csr_clear = (csr_op_i == `CSROP_CLEAR);
wire _csr_read = (csr_op_i == `CSROP_READ);
wire _csr_none = (csr_op_i == `CSROP_NONE);
得到csr两个操作数并根据不同指令进行运算
wire [`XLEN-1:0] _csr_op1 = csr_data_i;
wire [`XLEN-1:0] _csr_op2 = isNeedimmCSR_i ? imm_CSR_i : rs1_data_i;
wire [`XLEN-1:0] _csr_write_result = _csr_op2;
wire [`XLEN-1:0] _csr_set_result = _csr_op1 | _csr_op2;
wire [`XLEN-1:0] _csr_clear_result = _csr_op1 & (~_csr_op2);
wire [`XLEN-1:0] _csr_read_result = _csr_op1;
通过选择器输出
wire [`XLEN-1:0] _csr_exe_result= ({`XLEN{_csr_write}}&_csr_write_result)|
({`XLEN{_csr_set}}&_csr_set_result)|
({`XLEN{_csr_clear}}&_csr_clear_result)|
({`XLEN{_csr_read}}&_csr_read_result);
assign csr_exe_result = _csr_exe_result;
assign csr_exe_valid = ~(_csr_none | _csr_read); // 读取不写回
通过选择器将解析后的指令进行输出