前言
一个三级流水线的RISC-V项目,但是更新的会比较慢。因为手上有别的项目,所以只能闲的时候学习。
RISC-V介绍
RISC,即精简指令集处理器,是相对于X86这种CISC(复杂指令集处理器)来说的。RISC-V中的V是罗马数字,也即阿拉伯数字中的5,就是指第5代RISC。
RISC-V是一种指令集架构,和ARM、MIPS这些是属于同一类东西。RISC-V诞生于2010年,最大的特点是开源,任何人都可以设计RISC-V架构的处理器并且不会有任何版权问题。
流水线
本处理器使用了三级流水线,即取指、译码和执行,设计的目标就是要对标ARM的Cortex-M3系列处理器。如果是五级流水线的话就是:取指(IF),译码(ID),执行(EX),访存(访问存储器,MEM),写回(数据写回到目标寄存器,write back)。三级流水线中的执行包括了访存和写回。
实现目标
本项目实现的是一个三级流水线设计,顺序、单发射、单核的32位RISC-V处理器。采用verilog语言编写。设计目标是对标ARM Cortex-M3系列处理器。
开发环境
RTL代码编写:Notepad–
仿真及查看波形:
Linux VCS+Verdi(前面出过一篇博客 ,有兴趣的可以去看看)
本章内容
编写取指代码并进行简单的验证
即if模块 if_id模块(打一拍)
编写思路
IF模块
module ifu #(
parameter bit BranchPredictor = 1'b1
)(
input wire clk,
input wire rst_n,
input wire flush_i, // 冲刷标志
input wire[31:0] flush_addr_i, // 冲刷地址
input wire[`STALL_WIDTH-1:0] stall_i, // 流水线暂停标志
input wire id_ready_i, // ID模块可以接收指令
// to ifu_idu
output wire[31:0] inst_o,
output wire[31:0] pc_o,
output wire inst_valid_o,
// 指令总线信号(核内部信号)
output wire instr_req_o, //请求输出信号
input wire instr_gnt_i, //判断指令是否正常,能否从指令中接收到正确的命令
input wire instr_rvalid_i,
output wire[31:0] instr_addr_o, //指令地址输出信号
input wire[31:0] instr_rdata_i,
input wire instr_err_i
);
取指的作用就是从PC的到指令地址,然后从ROM中根据指定的地址取出对应的指令
需要注意的是流水线冲刷机制
取指操作
这个过程我们用组合逻辑和一个状态机实现。
状态机主要判断是复位、正在取指还是取值有效结束。
状态机编写
//req_valid代表请求有效 指令正常且取值流水线未暂停
assign req_valid = instr_gnt_i & (~stall_i[`STALL_IF]);
always @ (*) begin
state_d = state_q;
case (state_q)
// 复位
S_RESET: begin
// 复位撤销后转到取指状态
if (rst_n) begin
state_d = S_FETCH;
end
end
// 取指
S_FETCH: begin
// 取指有效
if (req_valid) begin
state_d = S_VALID;
end
end
// 指令有效
S_VALID: begin
// 取指无效
if (~req_valid) begin
state_d = S_FETCH;
end
end
default: ;
endcase
end
指令有效和给打拍输入的信号(组合逻辑实现)
// 指令有效
assign inst_valid = (state_q == S_VALID) & instr_rvalid_i & id_ready_i;
assign inst_valid_o = inst_valid;
// 指令无效时有nop指令代替
assign inst_o = inst_valid ? instr_rdata_i: `INST_NOP;
assign pc_o = fetch_addr_q; //PC给的指令地址
更新地址
主要要考虑b型指令,看是否进行分支预测
// 更新取指地址
assign fetch_addr_n = flush_i ? flush_addr_i:
prdt_taken ? prdt_addr:
inst_valid ? fetch_addr_q + 4'h4:
fetch_addr_q;
// 取指请求
assign instr_req_o = (~stall_i[`STALL_IF]) & (state_q != S_RESET);
// 取指地址(4字节对齐)
assign instr_addr_o = {fetch_addr_n[31:2], 2'b00};
//分支预测 看是否进行地址的跳转 按照手册的opcode和fun3和fun7确定
if (BranchPredictor) begin: g_branch_predictor
bpu u_bpu(
.clk(clk),
.rst_n(rst_n),
.inst_i(instr_rdata_i),
.inst_valid_i(inst_valid),
.pc_i(fetch_addr_q),
.prdt_taken_o(prdt_taken),
.prdt_addr_o(prdt_addr)
);
end else begin: g_no_branch_predictor
assign prdt_taken = 1'b0;
assign prdt_addr = 32'h0;
end
总结
完成了取指代码的编写
单独对这一块进行测试没有意义
等到后面全部编写完再进行测试吧