这个CPU的设计是教科书式的4级流水线结构。IF-ID-EX-MEM/WB 因此较为简单,效率也还可以。但是由于RISC-V IMC指令集是16-32位变长指令集。IF阶段需要正确地区分两种指令。不像OpenMIPS一样一个劲儿pc+=4就可以了。
另外求精~这个帖子会慢慢连载,不过学业较重,可能更新不及时。这个帖子适合看过自己动手写CPU——OpenMIPS的人。
由此,我们开始说IF阶段的设计。
先上仿真波形图:
先上传RISC-V的指令集描述资料~
riscv-privileged-v1.9.1.pdf
(611 KB)
eetop.cn_riscv-spec-v2.1中文版.pdf
(3.65 MB)
首先,RISC-V指令的最后两位表示指令类型。为11是非压缩指令,11则是压缩指令。这样对指令的区分要比连续变长的CISC处理器简单很多。也不会有ARM的Thumb-ARM切换带来的开销。
同步RAM的数据落后于地址一个周期,自然而然我们需要有一个超前1word(4)的pc指针。将它称为pc_4。
同样,由于新版RISC-V规范规定32位指令不必须对齐到32位,因此,很有可能出现一条指令被截为两半的情况,一半存于上次取来的指令的高16位,一半存在新取来的指令中的低16位。因此我们还需要一个32位寄存器来缓冲上一次取来的指令,将它称为last_instr。
在32位指令对齐的情况下,pc直接对应于内存输出的数据。因此,判断是否是压缩指令,只需要判断内存输出最后两位即可。
如果在纯16位指令的情况下,pc也将直接对应于内存输出数据分别的低16位和高16位的最后两位。在判断到取到了16位指令后,将pc的自增速度从4调为2即可。
如果在32位指令和16位指令混合的情况下,我们需要引入一个等待逻辑,由于指令一半存于上次取来的数据的高16位,一半存在新取来的数据中的低16位。于是不能让原先取得的数据由于时钟的作用被新取来的数据覆盖掉。
于是,等待逻辑的执行条件是原先的pc自增量为2,并且当前的指令于32位对齐的情况。
故有代码:
`timescale 1ns/1ps
module kamikaze_fetch(clk_i,
rst_i,
im_addr_o,
im_data_i,
instr_o,
instr_valid_o,
is_compressed_instr_o,
pc_o);
input clk_i;
input rst_i;
input [31:0] im_data_i;
output reg [31:0] instr_o;
output [31:0] im_addr_o;
output reg instr_valid_o;
output is_compressed_instr_o;
output [31:0] pc_o;
//input stall_i; /* IF 停止信号 */
wire [30:0] word_address = im_addr_o[31:2];
reg stall_i = 0;
reg [31:0] pc;
reg [31:0] pc_4;
reg [2:0] pc_add;
reg [2:0] pc_add_prev;
reg [31:0] last_instr; /* 一级缓冲 */
reg is_compressed_instr;
reg fetch_start;
assign is_compressed_instr_o = is_compressed_instr;
localparam CPU_START = 32'h0; /* 启动地址 */
assign im_addr_o = pc_4[1]? (pc_4 + 2'b10): pc_4; /* 舍入 */
assign stall_requiring = (pc_add_prev == 2) && (pc[1:0] == 2'b00); /* 16位对齐等待,防止冲数据 */
assign pc_o = pc;
always @(posedge clk_i or negedge rst_i)
begin
if(!rst_i)
begin
pc_4 <= CPU_START;
pc <= CPU_START;/* PC 比 pc_4 滞后1 CLK */
fetch_start <= 0;
pc_add_prev <= 4;
last_instr <= 32'h0;
instr_valid_o <= 0;
end
else
begin
if(!stall_i)
begin
if(fetch_start == 1'b0)
begin
fetch_start <= 1'b1; /* 取 0 指令 */
pc_4 <= pc_4 + 16'h4;
instr_valid_o <= 1;
end
else
begin
pc_4 <= pc_4 + pc_add;
pc <= pc + pc_add;
if(!stall_requiring)
last_instr <= im_data_i;
pc_add_prev <= pc_add;
end
end
end
end
always @*
begin
if(pc[1:0] == 2'b00)
begin
if(stall_requiring)
begin
if(last_instr[1:0] != 2'b11) /* 对齐的压缩指令 */
begin
is_compressed_instr <= 1;
instr_o = last_instr[15:0];
end
else
begin
is_compressed_instr <= 0;
instr_o = last_instr[31:0];
end
end
else
begin
if(im_data_i[1:0] != 2'b11) /* 对齐的压缩指令 */
begin
is_compressed_instr <= 1;
instr_o = im_data_i[15:0];
end
else
begin
is_compressed_instr <= 0;
instr_o = im_data_i[31:0];
end
end
end
else
begin //pc[1:0] == 10
if(last_instr[17:16] != 2'b11) /* 不对齐的压缩指令 */
begin
is_compressed_instr <= 1;
instr_o = last_instr[31:16];
end
else /* 不对齐的非压缩指令 */
begin
is_compressed_instr <= 0;
instr_o = {im_data_i[15:0], last_instr[31:16]};
end
end
pc_add = is_compressed_instr? 2: 4;
end
/* 这地方得有个分支预测器,你说我是直接预测全部不跳好,还是预测全部跳好呢? */
endmodule
建议在GitHub上看更佳。 |