文章目录[隐藏]
系列文章目录
(一)从零开始设计RISC-V处理器——指令系统
(二)从零开始设计RISC-V处理器——单周期处理器的设计
(三)从零开始设计RISC-V处理器——单周期处理器的仿真
(四)从零开始设计RISC-V处理器——ALU的优化
(五)从零开始设计RISC-V处理器——五级流水线之数据通路的设计
(六)从零开始设计RISC-V处理器——五级流水线之控制器的设计
(七)从零开始设计RISC-V处理器——五级流水线之数据冒险
(八)从零开始设计RISC-V处理器——五级流水线之控制冒险
(九)从零开始设计RISC-V处理器——五级流水线之分支计算前移
(十)从零开始设计RISC-V处理器——五级流水线之静态预测
前言
上一篇文章 中已经详细介绍了计划实现的37条指令,今天开始进行单周期的处理器设计
一、CPU如何执行指令?
CPU包括控制器和数据通路,数据通路从字面意思理解,就是指处理器中数据流通的路径。不同类型的指令的数据通路不一样,具体由控制器产生的控制信号决定。
下面以一个简单的数据通路为例,介绍以下处理器执行指令的过程。
1.CPU执行指令的过程如下:
(1)pc做为地址,输入到指令存储器,读取一条指令出来。这条指令是一个32bit的二进制数,里面包含了将要执行的指令的所有信息,包括指令类型(opcode),指令执行的功能(func3,func7),立即数部分(imme),源寄存器(rs1,rs2),目的寄存器(rd)。
(2)将读出来的指令进行译码,输入到不同的部件。比如,rs1,rs2,rd输入到寄存器堆,将立即数部分进行立即数扩展,将opcode输入到控制部件,产生控制信号。将func3,func7输入到子控制模块,产生ALU的控制信号。
(3)译码出来的寄存器号,输入到寄存器堆,读出寄存器号对应的数据Read_data1和Read_data2。
对于R-type指令,进行的操作就是两个寄存器的数据进行运算。
对于I-type指令,进行的操作是一个寄存器的数据与立即数进行运算。
因此这里要产生立即数,并且需要一个二选一选择器进行数据的选择。
(4)ALU即是运算单元,对输入的两个数据进行运算操作并且将运算结果输出,具体进行什么运算,由ALU_control产生的控制信号决定。
(5)将计算结果写回寄存器,写入到目标寄存器号对应的寄存器中。
(6)对于访存指令,如lb,lh,lw,sb,sh,sw,指令,还需要与数据存储器进行数据交换。
对于load指令,执行的操作是将数据从数据存储器的某个地址读出数据并讲这个数据写回到寄存器。
对于store指令,执行的操作是将寄存器的数据写入到数据存储器的某个地址内。
这个数据存储器的地址怎么产生呢?
地址以基地址加偏移的形式给出,基地址从寄存器中取得,偏移量从立即数中得到,因此ALU进行加法运算的结果便是数据存储器的地址。
因此ALU的运算结果有两个去向,一是写回寄存器,二是作为数据存储器的地址。
(7)对于load指令,是将数据存储器的数据写回到寄存器。
因此寄存器的数据来源有两个,一是ALU的运算结果,二是从数据存储器读出的数据。
(8)对于不进行跳转的指令,每个周期pc+4,指令按顺序执行,因此需要一个加法器。为什么是加4呢?
一条指令有32bit,CPU的内存空间按字节编址,所以一条指令4个字节实际上占了4个地址空间。
(9)对于条件跳转指令,如BEQ,BNE等,满足跳转的条件时,跳转的新地址由pc加左移一位的立即数产生,又需要一个加法器。因此,pc的来源有两个,一是顺序执行,pc+4,二是条件跳转,pc+(imme2)。
(10)对于无条件跳转指令,如jal,pc+4写回寄存器,将pc+(imme2)作为新地址。对于jalr,将pc+4写回寄存器,将源寄存器的数据与立即数相加并将最低位置0,作为下一个pc。
因此,写入寄存器的数据来源变成3个:ALU的运算结果,数据存储器读出的数据,pc+4。
pc的来源也变成3个:pc+4,pc+imme,Read_data1+imme。
(11)再考虑U-type,即lui,auipc。对于lui,将立即数进行处理,写入寄存器。对于auipc,将pc+imme写入寄存器。
因此,写入寄存器的数据来源变成5个,分别是:ALU的运算结果,数据存储器读出的数据,pc+4,lui的立即数 ,auipc的pc+imme。
上图所示的数据通路并不完整,如果想实现我们提到的37条指令,必须逐条分析指令,在上图的基础上添加相应的部件。
完整的数据通路如下所示(图中不包含控制器):
2.从以上的分析中,可以将CPU的关键部件总结如下:
(1)指令存储器
(2)译码单元
(3)寄存器堆
(4)ALU
(5)数据存储器
(6)控制器
其中指令存储器和数据存储器为CPU外部的存储器,CPU的核心为数据通路+控制器。
因此,我们设计完的顶层模块应该是这样的:
下面来进行具体设计。
二、存储器
1.指令存储器
指令存储器属于只读存储器(rom),可读不可写。
实现方式有两种:
(1)用硬件描述性语言设计
(2)用FPGA工具调用专用的rom IP核。
此处为了提高代码的通用性,采用第一种方式设计。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
addr | 8bit | input | 存储器读地址 |
instr | 32bit | output | 读出的指令 |
对rom进行初始化,只需要将二进制指令存到文件“rom_binary_file.txt”中。如下所示:
代码如下:
module instr_memory(
addr,
instr
);
input [7:0]addr;
output [31:0]instr;
reg[31:0] rom[255:0];
//rom进行初始化
initial begin
$readmemb("./rom_binary_file.txt", rom);
//$readmemh("rom_hex_file.txt", rom);
end
assign instr = rom[addr];
endmodule
2.数据存储器
数据存储器属于随机读写存储器(ram),可读可写,同样是两种实现方式。
这里用硬件描述语言设计。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
clk | 1bit | input | 时钟信号 |
rst_n | 1bit | input | 读出的指令 |
W_en | 1bit | input | 写使能 |
addr | 32bit | input | 读(写)地址 |
RW_tyoe | 3bit | input | 读(写)类型,字节,半字,字,双字 |
din | 32bit | input | 要写入存储器的数据 |
dout | 32bit | output | 从数据存储器中读出的数据 |
由于指令集中包含多位宽的读写指令,所以这里数据存储器的设计也应该支持多位宽读写。
根据输入的RW_type信号来确定读写类型。这里的RW_type信号,其实就是指令中的func3信号。
RW_type | 对应指令 | 读写类型 |
---|---|---|
000 | lb,sb | 读取或写入一个字节,读取时将8bit符号扩展为32bit |
001 | lh,sh | 读取或写入两个字节,读取时将16bit符号扩展为32bit |
010 | lw ,sw | 读取或写入32bit |
100 | lbu | 读取一个字节,将8bit无符号扩展为32bit |
101 | lhu | 读取两个字节,将16bit无符号扩展为32bit |
代码如下:
`include "define.v"
module data_memory(
clk,
rst_n,
W_en,
R_en,
addr,
RW_type,
din,
dout
);
input clk;
input rst_n;
input W_en;
input R_en;
input [31:0]addr;
input [2:0]RW_type;
input [31:0]din;
output [31:0]dout;
reg [31:0]ram[255:0];
wire [31:0]Rd_data;reference
//
reg [31:0]Wr_data_B;//字节拼接
wire [31:0]Wr_data_H;//半字拼接
wire [31:0]Wr_data;
assign Rd_data=ram[addr[31:2]];/读基准
always@(*)
begin
case(addr[1:0])
2'b00:Wr_data_B={Rd_data[31:8],din[7:0]};
2'b01:Wr_data_B={Rd_data[31:16],din[7:0],Rd_data[7:0]};
2'b10:Wr_data_B={Rd_data[31:24],din[7:0],Rd_data[15:0]};
2'b11:Wr_data_B={din[7:0],Rd_data[23:0]};
endcase
end
assign Wr_data_H=(addr[1]) ? {din[15:0],Rd_data[15:0]} : {Rd_data[31:16],din[15:0]} ;
///根据写类型,选择写入的数捿
assign Wr_data=(RW_type[1:0]==2'b00) ? Wr_data_B :( (RW_type[1:0]==2'b01) ? Wr_data_H : din );
///上升沿写入数捿
always@(posedge clk)
begin
if(W_en)
ram[addr[9:2]]<=Wr_data;
end
//读部刿
reg [7:0]Rd_data_B;
wire [15:0]Rd_data_H;
wire [31:0] Rd_data_B_ext;
wire [31:0] Rd_data_H_ext;
根据写地坿,实现截叿
always@(*)
begin
case(addr[1:0])
2'b00:Rd_data_B=Rd_data[7:0];
2'b01:Rd_data_B=Rd_data[15:8];
2'b10:Rd_data_B=Rd_data[23:16];
2'b11:Rd_data_B=Rd_data[31:24];
endcase
end
assign Rd_data_H=(addr[1])? Rd_data[31:16]:Rd_data[15:0];
///扩展丿32使
assign Rd_data_B_ext=(RW_type[2]) ? {24'd0,Rd_data_B} : {{24{Rd_data_B[7]}},Rd_data_B};
assign Rd_data_H_ext=(RW_type[2]) ? {16'd0,Rd_data_H} : {{16{Rd_data_H[15]}},Rd_data_H};
/
assign dout=(RW_type[1:0]==2'b00) ? Rd_data_B_ext : ((RW_type[1:0]==2'b01) ? Rd_data_H_ext : Rd_data );
endmodule
上面代码中出现的define.v文件里面定义了一些参数,该文件展示如下:
`define zero_word 32'd0
`define lui 7'b0110111
`define auipc 7'b0010111
`define jal 7'b1101111
`define jalr 7'b1100111
`define B_type 7'b1100011
`define load 7'b0000011
`define store 7'b0100011
`define I_type 7'b0010011
`define R_type 7'b0110011
`define ADD 4'b0001
`define SUB 4'b0011
`define SLL 4'b1100
`define SLT 4'b1001
`define SLTU 4'b1000
`define XOR 4'b0110
`define SRL 4'b1101
`define SRA 4'b1110
`define OR 4'b0101
`define AND 4'b0100
三.数据通路
1.寄存器堆
寄存器堆由快速的静态随机读写存储器(sram)实现,这种ram可以多路并发访问不同的寄存器。
寄存器堆由32个寄存器阵列组成,其中0号寄存器的值恒为0,只读不写。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
clk | 1bit | input | 时钟信号 |
Rs1 | 5bit | input | 源寄存器1 |
Rs2 | 5bit | input | 源寄存器2 |
W_en | 1bit | input | 读使能 |
Rd | 5bit | input | 目标寄存器 |
Wr_data | 32bit | input | 写入寄存器的数据 |
Rd_data1 | 32bit | output | 源寄存器1的数据 |
Rd_data2 | 32bit | output | 源寄存器2的数据 |
代码如下:
`include "define.v"
module registers(
clk,
W_en,
Rs1,
Rs2,
Rd,
Wr_data,
Rd_data1,
Rd_data2
);
input clk;
input W_en;
input [4:0]Rs1;
input [4:0]Rs2;
input [4:0]Rd;
input [31:0]Wr_data;
output [31:0]Rd_data1;
output [31:0]Rd_data2;
reg [31:0] regs [31:0];
///write
always@(posedge clk )
begin
if(W_en & (Rd!=0))
regs[Rd]<=Wr_data;
end
//read
assign Rd_data1=(Rs1==5'd0)?`zero_word: regs[Rs1];
assign Rd_data2=(Rs2==5'd0)?`zero_word: regs[Rs2];
endmodule
2.译码模块
该模块对指令存储器输出的32位指令进行译码,得到opcode,Rs1,Rs2,Rd,imme,func3,func7等信息。
按照以下格式进行译码:
这里补充以下上一篇文中中提到的问题:
对于jal指令(UJ-type)和条件跳转指令(SB-type),他们的立即数表示方法是[20:1]或[12:1],这里其实就是把最低位默认成0了。
如果按照最低位为0来扩展立即数,那么这个立即数直接与pc相加,即为跳转地址。如果最低位不进行补0,那么就如本篇刚开始提到的数据通路所示,必须将立即数左移一位之后与立即数相加。其最终效果是一样的。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
instr | 32bit | input | 32位指令 |
opcde | 7bit | output | 7位操作码 |
func3 | 3bit | output | func3 |
func7 | 1bit | output | func7的第6位 |
Rs1 | 5bit | output | 源寄存器1 |
Rs2 | 5bit | output | 源寄存器2 |
Rd | 5bit | output | 目标寄存器 |
imme | 32bit | output | 符号扩展后的32位立即数 |
代码如下:
`include "define.v"
module instr_decode(
input [31:0]instr,
output [6:0]opcode,
output [2:0]func3,
output func7,
output [4:0]Rs1,
output [4:0]Rs2,
output [4:0]Rd,
output [31:0]imme
);
wire I_type;
wire U_type;
wire J_type;
wire B_type;
wire S_type;
wire [31:0]I_imme;
wire [31:0]U_imme;
wire [31:0]J_imme;
wire [31:0]B_imme;
wire [31:0]S_imme;
assign opcode=instr[6:0];
assign func3=instr[14:12];
assign func7=instr[30];
assign Rs1=instr[19:15];
assign Rs2=instr[24:20];
assign Rd =instr[11:7];
assign I_type=(instr[6:0]==`jalr) | (instr[6:0]==`load) | (instr[6:0]==`I_type);
assign U_type=(instr[6:0]==`lui) | (instr[6:0]==`auipc);
assign J_type=(instr[6:0]==`jal);
assign B_type=(instr[6:0]==`B_type);
assign S_type=(instr[6:0]==`store);
assign I_imme={{20{instr[31]}},instr[31:20]};
assign U_imme={instr[31:12],{12{1'b0}}};
assign J_imme={{12{instr[31]}},instr[19:12],instr[20],instr[30:21],1'b0};
assign B_imme={{20{instr[31]}},instr[7],instr[30:25],instr[11:8],1'b0};
assign S_imme={{20{instr[31]}},instr[31:25],instr[11:7]};
assign imme= I_type?I_imme :
U_type?U_imme :
J_type?J_imme :
B_type?B_imme :
S_type?S_imme : 32'd0;
endmodule
3.ALU模块
ALU模块主要进行数据的运算,根据ALU的控制模块产生的控制信ALU_CTL决定ALU进行的运算类型。
ALU_CTL对应的运算关系如下:
ALU_CTL | 运算类型 | 具体的运算 |
---|---|---|
0000 | 加法运算 | + |
0011 | 加法运算 | - |
0100 | 逻辑运算 | & |
0101 | 逻辑运算 | 或 |
0110 | 逻辑运算 | ^ |
0111 | 逻辑运算 | 或非 |
1000 | 小于置一 | 无符号小于置一 |
1001 | 小于置一 | 有符号小于置一 |
1100 | 移位运算 | 逻辑左移 |
1101 | 移位运算 | 逻辑右移 |
1110 | 移位运算 | 算数右移 |
由以上表格可以看出,ALU的运算类型分为加法运算,逻辑运算,小于置一,移位运算,根据ALU_CTL的高两位便可以判断出要执行的运算类型。
(1)加法运算:由加法器实现,减法实质上也是加法。
对于A-B,可以看作A+(-B)。
对于补码表示的二进制数,天然地有如下关系:
A+(~A)=32’b1111_1111_1111_1111_1111_1111_1111_1111=-1;
因此,-A=(~A)+1,这便是我们常说的补码等于反码加1,其实更严谨的说法应该是,一个数的相反数,等于这个数按位取反再加一。
于是,对于减法运算,可以做如下转换:
A-B=A+(-B)=A+(B的补码)+1;
另外,加法运算还应检测是否溢出,检测依据就是:当数为负数时,最左侧的位为0,或者数为正数时,最左侧的位为1。
具体细分位以下四种情况:
a.正数+正数=负数,则溢出
b.负数+负数=正数,则溢出
c.正数-负数=负数,则溢出
d.负数-正数=正数,则溢出
(2)逻辑运算,用逻辑门阵列实现即可。
(3)小于置一,实质上是减法运算。根据加法器的运算结果,进一步判断是否小于。
(4)移位运算,这里直接使用移位运算符(>>,<<)。
以上就是ALU的主要运算部件的设计,可以看到,加法器和移位运算比较复杂,但这里直接用Verilog的运算符实现的,后续会尝试设计超前进位加法器代替“+”以及用位拼接运算代替“>>,<<”,以提升CPU的性能。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
ALU_DA | 32bit | input | ALU的操作数1 |
ALU_DB | 32bit | input | ALU的操作数2 |
ALU_CTL | 4bit | input | ALU的控制信号 |
ALU_ZERO | 1bit | output | ALU运算结果为0的标志 |
ALU_Overflow | 1bit | output | 溢出标志 |
ALU_DC | 32bit | output | ALU的运算结果 |
代码如下:
module alu(
ALU_DA,
ALU_DB,
ALU_CTL,
ALU_ZERO,
ALU_OverFlow,
ALU_DC
);
input [31:0] ALU_DA;
input [31:0] ALU_DB;
input [3:0] ALU_CTL;
output ALU_ZERO;
output ALU_OverFlow;
output reg [31:0] ALU_DC;
//********************generate ctr***********************
wire SUBctr;
wire SIGctr;
wire Ovctr;
wire [1:0] Opctr;
wire [1:0] Logicctr;
wire [1:0] Shiftctr;
assign SUBctr = (~ ALU_CTL[3] & ~ALU_CTL[2] & ALU_CTL[1]) | ( ALU_CTL[3] & ~ALU_CTL[2]);
assign Opctr = ALU_CTL[3:2];
assign Ovctr = ALU_CTL[0] & ~ ALU_CTL[3] & ~ALU_CTL[2] ;
assign SIGctr = ALU_CTL[0];
assign Logicctr = ALU_CTL[1:0];
assign Shiftctr = ALU_CTL[1:0];
//********************************************************
//*********************logic op***************************
reg [31:0] logic_result;
always@(*) begin
case(Logicctr)
2'b00:logic_result = ALU_DA & ALU_DB;
2'b01:logic_result = ALU_DA | ALU_DB;
2'b10:logic_result = ALU_DA ^ ALU_DB;
2'b11:logic_result = ~(ALU_DA | ALU_DB);
endcase
end
//********************************************************
//************************shift op************************
wire [4:0] ALU_SHIFT;
wire [31:0] shift_result;
assign ALU_SHIFT=ALU_DB[4:0];
Shifter Shifter(.ALU_DA(ALU_DA),
.ALU_SHIFT(ALU_SHIFT),
.Shiftctr(Shiftctr),
.shift_result(shift_result));
//********************************************************
//************************add sub op**********************
wire [31:0] BIT_M,XOR_M;
wire ADD_carry,ADD_OverFlow;
wire [31:0] ADD_result;
assign BIT_M={32{SUBctr}};
assign XOR_M=BIT_M^ALU_DB;
Adder Adder(.A(ALU_DA),
.B(XOR_M),
.Cin(SUBctr),
.ALU_CTL(ALU_CTL),
.ADD_carry(ADD_carry),
.ADD_OverFlow(ADD_OverFlow),
.ADD_zero(ALU_ZERO),
.ADD_result(ADD_result));
assign ALU_OverFlow = ADD_OverFlow & Ovctr;
//********************************************************
//**************************slt op************************
wire [31:0] SLT_result;
wire LESS_M1,LESS_M2,LESS_S,SLT_M;
assign LESS_M1 = ADD_carry ^ SUBctr;
assign LESS_M2 = ADD_OverFlow ^ ADD_result[31];
assign LESS_S = (SIGctr==1'b0)?LESS_M1:LESS_M2;
assign SLT_result = (LESS_S)?32'h00000001:32'h00000000;
//********************************************************
//**************************ALU result********************
always @(*)
begin
case(Opctr)
2'b00:ALU_DC<=ADD_result;
2'b01:ALU_DC<=logic_result;
2'b10:ALU_DC<=SLT_result;
2'b11:ALU_DC<=shift_result;
endcase
end
//********************************************************
endmodule
//********************************************************
//*************************shifter************************
module Shifter(input [31:0] ALU_DA,
input [4:0] ALU_SHIFT,
input [1:0] Shiftctr,
output reg [31:0] shift_result);
wire [5:0] shift_n;
assign shift_n = 6'd32 - Shiftctr;
always@(*) begin
case(Shiftctr)
2'b00:shift_result = ALU_DA << ALU_SHIFT;
2'b01:shift_result = ALU_DA >> ALU_SHIFT;
2'b10:shift_result = ({32{ALU_DA[31]}} << shift_n) | (ALU_DA >> ALU_SHIFT);
default:shift_result = ALU_DA;
endcase
end
endmodule
//*************************************************************
//***********************************adder*********************
module Adder(input [31:0] A,
input [31:0] B,
input Cin,
input [3:0] ALU_CTL,
output ADD_carry,
output ADD_OverFlow,
output ADD_zero,
output [31:0] ADD_result);
assign {ADD_carry,ADD_result}=A+B+Cin;
assign ADD_zero = ~(|ADD_result);
assign ADD_OverFlow=((ALU_CTL==4'b0001) & ~A[31] & ~B[31] & ADD_result[31])
| ((ALU_CTL==4'b0001) & A[31] & B[31] & ~ADD_result[31])
| ((ALU_CTL==4'b0011) & A[31] & ~B[31] & ~ADD_result[31])
| ((ALU_CTL==4'b0011) & ~A[31] & B[31] & ADD_result[31]);
endmodule
4.PC寄存器
PC寄存器用以更新pc的值。
顺序执行时,pc_new=pc+4,条件跳转时,pc_new=pc+imme。
jalr时,pc_new=data(Rs1)+imme。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
clk | 1bit | input | 时钟信号 |
rst_n | 1bit | input | 读出的指令 |
pc_new | 32bit | input | 下一个时钟周期的pc值 |
pc_out | 32bit | output | 更新后的pc值 |
代码如下:
`include "define.v"
module pc_reg(
clk,
rst_n,
pc_new,
pc_out
);
input clk;
input rst_n;
input [31:0]pc_new;
output reg [31:0]pc_out;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
pc_out<=`zero_word;
else
pc_out<=pc_new;
end
endmodule
5.数据通路
数据通路就是将以上的几个关键的部件进行连接,为了形成完整的数据通路,还必须添加一些多路选择器,作用是对不同信号的来源进行选择,并输出到相应的模块。
比如:写入寄存器的数据来源有5个,分别是:ALU的运算结果,数据存储器读出的数据,pc+4,lui的立即数 ,auipc的pc+imme。
pc的来源有3个:pc+4,pc+imme,Read_data1+imme。
另外,还需要加入两个加法器,分别计算pc+4和pc+imme。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
clk | 1bit | input | 时钟信号 |
rst_n | 1bit | input | 读出的指令 |
instr | 32bit | input | 指令存储器读出的指令 |
MemtoReg | 1bit | input | 写回寄存器的数据选择器控制信号 |
ALUSrc | 1bit | input | ALU的数据来源的数据选择器控制信号 |
RegWrite | 1bit | input | 寄存器的写使能控制信号 |
lui | 1bit | input | lui指令标志,写回寄存器的数据选择器的控制信号 |
U_type | 1bit | input | U-type指令标志,写回寄存器的数据选择器的控制信号 |
jal | 1bit | input | jal指令标志,选择pc的数据选择器的控制信号,同时也是写回寄存器的数据选择器的控制信号 |
jalr | 1bit | input | jal指令标志,选择pc的数据选择器的控制信号,同时也是写回寄存器的数据选择器的控制信号 |
beq | 1bit | input | beq指令标志,判断是否跳转的控制信号 |
bne | 1bit | input | bne指令标志,判断是否跳转的控制信号 |
blt | 1bit | input | blt指令标志,判断是否跳转的控制信号 |
bge | 1bit | input | bge指令标志,判断是否跳转的控制信号 |
bltu | 1bit | input | bltu指令标志,判断是否跳转的控制信号 |
bgeu | 1bit | input | bgeu指令标志,判断是否跳转的控制信号 |
ALUctl | 4bit | input | ALU的控制信号,决定ALU进行什么运算 |
Rd_mem_data | 32bit | input | 从数据存储器读出的数据,作为写回寄存器的数据来源之一 |
rom_addr | 8bit | output | 指令存储器的地址 |
Wr_mem_data | 32bit | output | 数据存储器的写数据 |
ALU_result | 32bit | output | ALU的运算结果,作为数据存储器的读(写)地址 |
opcode | 7bit | output | 7位操作码 |
func3 | 3bit | output | func3 |
func7 | 1bit | output | func7 |
代码如下:
module datapath(
input clk,
input rst_n,
input [31:0]instr,
input MemtoReg,
input ALUSrc,
input RegWrite,
input lui,
input U_type,
input jal,
input jalr,
input beq,
input bne,
input blt,
input bge,
input bltu,
input bgeu,
input [3:0]ALUctl,
input [31:0]Rd_mem_data,
output [7:0]rom_addr,
output [31:0]Wr_mem_data,
output [31:0]ALU_result,
output [6:0]opcode,
output [2:0]func3,
output func7
);
wire [4:0]Rs1;
wire [4:0]Rs2;
wire [4:0]Rd;
wire [31:0]imme;
wire [31:0] Wr_reg_data;
wire [31:0] Rd_data1;
wire [31:0] Rd_data2;
wire zero;
wire [31:0]pc_order;
wire [31:0]pc_jump;
wire [31:0]pc_new;
wire [31:0]pc_out;
wire jump_flag;
wire [31:0]ALU_DB;
wire [31:0]WB_data;
wire reg_sel;
wire [31:0]Wr_reg_data1;
wire [31:0]Wr_reg_data2;
wire [31:0]pc_jump_order;
wire [31:0]pc_jalr;
assign reg_sel=jal | jalr ;
assign Wr_mem_data=Rd_data2;
assign rom_addr=pc_out[9:2];
assign pc_jalr={ALU_result[31:1],1'b0};
pc_reg pc_reg_inst (
.clk(clk),
.rst_n(rst_n),
.pc_new(pc_new),
.pc_out(pc_out)
);
instr_decode instr_decode_inst (
.instr(instr),
.opcode(opcode),
.func3(func3),
.func7(func7),
.Rs1(Rs1),
.Rs2(Rs2),
.Rd(Rd),
.imme(imme)
);
registers registers_inst (
.clk(clk),
.W_en(RegWrite),
.Rs1(Rs1),
.Rs2(Rs2),
.Rd(Rd),
.Wr_data(Wr_reg_data),
.Rd_data1(Rd_data1),
.Rd_data2(Rd_data2)
);
alu alu_inst (
.ALU_DA(Rd_data1),
.ALU_DB(ALU_DB),
.ALU_CTL(ALUctl),
.ALU_ZERO(zero),
.ALU_OverFlow(),
.ALU_DC(ALU_result)
);
branch_judge branch_judge_inst (
.beq(beq),
.bne(bne),
.blt(blt),
.bge(bge),
.bltu(bltu),
.bgeu(bgeu),
.jal(jal),
.jalr(jalr),
.zero(zero),
.ALU_result(ALU_result),
.jump_flag(jump_flag)
);
//pc+4
cla_adder32 pc_adder_4 (
.A(pc_out),
.B(32'd4),
.cin(1'd0),
.result(pc_order),
.cout()
);
/pc+imme
cla_adder32 pc_adder_imme (
.A(pc_out),
.B(imme),
.cin(1'd0),
.result(pc_jump),
.cout()
);
/pc_sel
mux pc_mux (
.data1(pc_jump),
.data2(pc_order),
.sel(jump_flag),
.dout(pc_jump_order)
);
///pc_jalr
mux pc_jalr_mux (
.data1(pc_jalr),
.data2(pc_jump_order),
.sel(jalr),
.dout(pc_new)
);
ALUdata_sel
mux ALU_data_mux (
.data1(imme),
.data2(Rd_data2),
.sel(ALUSrc),
.dout(ALU_DB)
);
/ALU_result or datamem
mux WB_data_mux (
.data1(Rd_mem_data),
.data2(ALU_result),
.sel(MemtoReg),
.dout(WB_data)
);
Wr_data_sel
mux jalr_mux (
.data1(pc_order),
.data2(WB_data),
.sel(reg_sel),
.dout(Wr_reg_data2)
);
mux lui_mux (
.data1(imme),
.data2(pc_jump),
.sel(lui),
.dout(Wr_reg_data1)
);
mux Wr_reg_mux (
.data1(Wr_reg_data1),
.data2(Wr_reg_data2),
.sel(U_type),
.dout(Wr_reg_data)
);
endmodule
四.控制器
1.主控制器
由于不同类型的指令所经过的数据通路不同,所以需要有控制信号控制数据通路,使得数据经过正确的通路,得到正确的运算结果。
本设计将控制器分为两级控制,主控制器产生大部分的控制信号,子控制器是ALU控制器,产生控制ALU进行正确运算的信号。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
opcode | 7bit | input | 7位操作码 |
func3 | 3bit | input | func3 |
Memread | 1bit | output | 数据存储器读使能 |
ALUop | 2bit | output | 子控制器的控制信号 |
MemtoReg | 1bit | output | 写回寄存器的数据选择器控制信号 |
Memwrite | 1bit | output | 数据存储器写使能 |
ALUSrc | 1bit | output | ALU的数据来源的数据选择器控制信号 |
RegWrite | 1bit | output | 寄存器的写使能控制信号 |
lui | 1bit | output | lui指令标志,写回寄存器的数据选择器的控制信号 |
U_type | 1bit | output | U-type指令标志,写回寄存器的数据选择器的控制信号 |
jal | 1bit | output | jal指令标志,选择pc的数据选择器的控制信号,同时也是写回寄存器的数据选择器的控制信号 |
jalr | 1bit | output | jal指令标志,选择pc的数据选择器的控制信号,同时也是写回寄存器的数据选择器的控制信号 |
beq | 1bit | output | beq指令标志,判断是否跳转的控制信号 |
bne | 1bit | output | bne指令标志,判断是否跳转的控制信号 |
blt | 1bit | output | blt指令标志,判断是否跳转的控制信号 |
bge | 1bit | output | bge指令标志,判断是否跳转的控制信号 |
bltu | 1bit | output | bltu指令标志,判断是否跳转的控制信号 |
bgeu | 1bit | output | bgeu指令标志,判断是否跳转的控制信号 |
RW_type | 3bit | output | 数据存储器的读(写)类型 |
代码如下:
module main_control(
opcode,
func3,
MemRead,
MemtoReg,
ALUop,
MemWrite,
ALUSrc,
RegWrite,
lui,
U_type,
jal,
jalr,
beq,
bne,
blt,
bge,
bltu,
bgeu,
RW_type
);
input [6:0]opcode;
input [2:0]func3;
output MemRead;
output MemtoReg;
output [1:0]ALUop;
output MemWrite;
output ALUSrc;
output RegWrite;
output lui;
output U_type;
output jal;
output jalr;
output beq;
output bne;
output blt;
output bge;
output bltu;
output bgeu;
output [2:0]RW_type;
wire branch;
wire R_type;
wire I_type;
wire load;
wire store;
wire lui;
wire auipc;
assign branch=(opcode==`B_type)?1'b1:1'b0;
assign R_type=(opcode==`R_type)?1'b1:1'b0;
assign I_type=(opcode==`I_type)?1'b1:1'b0;
assign U_type=(lui | auipc)? 1'b1:1'b0;
assign load=(opcode==`load)?1'b1:1'b0;
assign store=(opcode==`store)?1'b1:1'b0;
assign jal=(opcode==`jal)?1'b1:1'b0;
assign jalr=(opcode==`jalr)?1'b1:1'b0;
assign lui=(opcode==`lui)?1'b1:1'b0;
assign auipc=(opcode==`auipc)?1'b1:1'b0;
assign beq= branch & (func3==3'b000);
assign bne= branch & (func3==3'b001);
assign blt= branch & (func3==3'b100);
assign bge= branch & (func3==3'b101);
assign bltu= branch & (func3==3'b110);
assign bgeu= branch & (func3==3'b111);
assign RW_type=func3;
enable
assign MemRead= load;
assign MemWrite= store;
assign RegWrite= jal| jalr | load | I_type |R_type | U_type;
MUX
assign ALUSrc=load | store |I_type | jalr; //select imme
assign MemtoReg= load; //select datamemory data
ALUop
assign ALUop[1]= R_type|branch; //R 10 I 01 B 11 add 00
assign ALUop[0]= I_type|branch;
endmodule
2.子控制器
子控制器根据主控制器产生的ALUop信号,结合func3和func7信号来产生ALUctl信号。
ALUop信号的设置如下:
ALUop | 子控制器的操作 |
---|---|
00 | 加法运算 |
10 | R-type指令,根据func3和func7判断运算类型 |
01 | R-type指令,根据func3和func7判断运算类型 |
11 | 条件跳转信号,根据func3判断运算类型 |
ALUctl信号设置如下:
ALUctl | 运算类型 | 具体的运算 |
---|---|---|
0000 | 加法运算 | + |
0011 | 加法运算 | - |
0100 | 逻辑运算 | & |
0101 | 逻辑运算 | 或 |
0110 | 逻辑运算 | ^ |
0111 | 逻辑运算 | 或非 |
1000 | 小于置一 | 无符号小于置一 |
1001 | 小于置一 | 有符号小于置一 |
1100 | 移位运算 | 逻辑左移 |
1101 | 移位运算 | 逻辑右移 |
1110 | 移位运算 | 算数右移 |
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
opcode | 7bit | input | 7位操作码 |
func3 | 3bit | input | func3 |
Memread | 1bit | output | 数据存储器读使能 |
ALUop | 2bit | output | 子控制器的控制信号 |
MemtoReg | 1bit | output | 写回寄存器的数据选择器控制信号 |
代码如下:
module alu_control(
ALUop,
func3,
func7,
ALUctl
);
input [1:0]ALUop;
input [2:0]func3;
input func7;
output [3:0]ALUctl;
wire [3:0]branchop;
reg [3:0]RIop;
assign branchop=(func3[2] & func3[1])? `SLTU : (func3[2] ^ func3[1])? `SLT : `SUB;
always@(*)
begin
case(func3)
3'b000: if(ALUop[1] & func7) //R
RIop=`SUB;
else //I
RIop=`ADD;
3'b001: RIop=`SLL;
3'b010: RIop=`SLT;
3'b011: RIop=`SLTU;
3'b100: RIop=`XOR;
3'b101: if(func7)
RIop=`SRA;
else
RIop=`SRL;
3'b110: RIop=`OR;
3'b111: RIop=`AND;
default:RIop=`ADD;
endcase
end
assign ALUctl=(ALUop[1]^ALUop[0])? RIop:(ALUop[1]&ALUop[0])?branchop:`ADD;
endmodule
3.控制器
将主控制模块和子控制模块进行实例化,得到控制模块。
代码如下:
`include "define.v"
module control(
opcode,
func3,
func7,
MemRead,
MemtoReg,
MemWrite,
ALUSrc,
RegWrite,
lui,
U_type,
jal,
jalr,
beq,
bne,
blt,
bge,
bltu,
bgeu,
RW_type,
ALUctl
);
input [6:0]opcode;
input [2:0]func3;
input func7;
output MemRead;
output MemtoReg;
output MemWrite;
output ALUSrc;
output RegWrite;
output lui;
output U_type;
output jal;
output jalr;
output beq;
output bne;
output blt;
output bge;
output bltu;
output bgeu;
output [2:0]RW_type;
output [3:0]ALUctl;
wire [1:0]ALUop;
main_control main_control_inst(
.opcode(opcode),
.func3(func3),
.MemRead(MemRead),
.MemtoReg(MemtoReg),
.ALUop(ALUop),
.MemWrite(MemWrite),
.ALUSrc(ALUSrc),
.RegWrite(RegWrite),
.lui(lui),
.U_type(U_type),
.jal(jal),
.jalr(jalr),
.beq(beq),
.bne(bne),
.blt(blt),
.bge(bge),
.bltu(bltu),
.bgeu(bgeu),
.RW_type(RW_type)
);
alu_control alu_control_inst(
.ALUop(ALUop),
.func3(func3),
.func7(func7),
.ALUctl(ALUctl)
);
endmodule
五.CPU的实现
1. RISC-V核心
将数据通路和及控制信号连接。
模块的输入输出端口定义如下:
信号名 | 位宽 | 输入输出 | 说明 |
---|---|---|---|
clk | 1bit | input | 系统时钟 |
rst_n | 1bit | input | 复位信号 |
instr | 32bit | input | 来自指令存储器的指令 |
Rd_mem_data | 32bit | input | 来自数据存储器的读数据 |
rom_addr | 8bit | output | 指令存储器的读地址 |
Wr_mem_data | 32bit | output | 写入数据存储器的数据 |
R_en | 1bit | output | 数据存储器的读使能信号 |
W_en | 1bit | output | 数据存储器的写使能信号 |
ram_addr | 32bit | output | 数据存储器的读(写)地址 |
RW_type | 3bit | output | 数据存储器的读写类型 |
代码如下:
module riscv(
input clk,
input rst_n,
input [31:0]instr,
input [31:0]Rd_mem_data,
output [7:0]rom_addr,
output [31:0]Wr_mem_data,
output W_en,
output R_en,
output [31:0]ram_addr,
output [2:0]RW_type
);
wire [6:0]opcode;
wire [2:0]func3;
wire func7;
wire MemtoReg;
wire ALUSrc;
wire RegWrite;
wire lui;
wire U_type;
wire jal;
wire jalr;
wire beq;
wire bne;
wire blt;
wire bge;
wire bltu;
wire bgeu;
wire [3:0]ALUctl;
control control_inst (
.opcode(opcode),
.func3(func3),
.func7(func7),
.MemRead(R_en),
.MemtoReg(MemtoReg),
.MemWrite(W_en),
.ALUSrc(ALUSrc),
.RegWrite(RegWrite),
.lui(lui),
.U_type(U_type),
.jal(jal),
.jalr(jalr),
.beq(beq),
.bne(bne),
.blt(blt),
.bge(bge),
.bltu(bltu),
.bgeu(bgeu),
.RW_type(RW_type),
.ALUctl(ALUctl)
);
datapath datapath_inst (
.clk(clk),
.rst_n(rst_n),
.instr(instr),
.MemtoReg(MemtoReg),
.ALUSrc(ALUSrc),
.RegWrite(RegWrite),
.lui(lui),
.U_type(U_type),
.jal(jal),
.jalr(jalr),
.beq(beq),
.bne(bne),
.blt(blt),
.bge(bge),
.bltu(bltu),
.bgeu(bgeu),
.ALUctl(ALUctl),
.Rd_mem_data(Rd_mem_data),
.rom_addr(rom_addr),
.Wr_mem_data(Wr_mem_data),
.ALU_result(ram_addr),
.opcode(opcode),
.func3(func3),
.func7(func7)
);
endmodule
2.完整CPU
接入指令存储器和数据存储器。
代码如下:
module riscv_top(
input clk,
input rst_n,
output [7:0]rom_addr
);
// wire [7:0]rom_addr;
wire [31:0]ram_addr;
wire [31:0]instr;
wire [31:0]Rd_mem_data;
wire [31:0]Wr_mem_data;
wire W_en;
wire R_en;
wire [2:0]RW_type;
instr_memory instr_memory_inst (
.addr(rom_addr),
.instr(instr)
);
riscv riscv_inst (
.clk(clk),
.rst_n(rst_n),
.instr(instr),
.Rd_mem_data(Rd_mem_data),
.rom_addr(rom_addr),
.Wr_mem_data(Wr_mem_data),
.W_en(W_en),
.R_en(R_en),
.ram_addr(ram_addr),
.RW_type(RW_type)
);
data_memory data_memory_inst (
.clk(clk),
.rst_n(rst_n),
.W_en(W_en),
.R_en(R_en),
.addr(ram_addr),
.RW_type(RW_type),
.din(Wr_mem_data),
.dout(Rd_mem_data)
);
endmodule
3.整体预览
最终的代码结构如下:
FPGA工具生成的RTL图如下:
总结
以上就是今天要讲的内容,本文仅仅简单介绍了单周期处理器的设计,后续还需要在此基础上不断改善。 下一篇文章介绍对这个处理器的整体仿真。
版权声明:本文为CSDN博主「不学无术的小胖子.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45677520/article/details/122386632
暂无评论