封面与内容无关!侵权必删!
这是笔者学校暑期学期的必修课,难度并不高(仅追求及格的话),但相当耗时,对于在硬件设计方面完全不感冒的同学更是一场避不开的折磨,所以笔者想通过自己的成果和被折磨经验来帮助有此方面需求的同学,或是对于CPU设计有浓厚兴趣的人。
这是笔者在CSDN上第一篇博客,若有不足请多海涵
若有源码需求或对本文章有所见解者,请不吝赐教
如果在编写Verilog这类硬件编程语言时出现没有思路等现象时,可尝试反思对于设计所要求的理论知识是否牢固。
编写CPU的预备知识如下:
我们知道CPU要取出指令,所依靠的计数器就是PC,通过PC的值pc,CPU可确定取指令的位置,所以此模块负责传输pc值。
在单周期模型中,PC需要在每个时钟上升沿到来之际进行相应更新,我们可以把这个逻辑单元单独拿出来,向PC模块传输下一个pc的值npc,同时此模块也接受一些后序模块数据,因为这些数据可能影响到如何选择npc的正确值。
该模块使用vivado平台提供的IP核实现,通过预先存入16进制指令序列,并且接受PC模块的pc值作为地址寻址取指令,并将取出的指令inst传送至后序模块(inst可以说是CPU最重要的变量了,没有inst一切工作都不可能进行)。
控制单元(control unit)的简称,CU是CPU的“大脑”,负责解析从IROM模块传来的inst指令,并且生成各类控制信号,用于调遣其余部件进行工作。
寄存器堆(register file)的简称,寄存器堆是CPU负责高速存储运算结果以及一些常用数据的存储设备,有读\写两种功能,为正确读写数据并且尽可能使CPU实际性能提示,采用组合逻辑读,时序逻辑写操作。
符号扩展单元(sign extend),负责对risv-v指令的“可能”立即数位置所表示的立即数进行符号扩展为32位宽的数据,该行为受CU单元的sext_op信号所控制。
ALU是算数逻辑单元,负责CPU内部绝大部分运算任务(所以主频的提高关键跟ALU性能有着偌大的关系),接受前面IF阶段和ID阶段的各类信号以及数据输入,通过CU单元的控制信号选择第一、第二运算数以及运算类型。
DRAM模块是使用vivado平台提供的IP核搭建的模拟主存,读写接口会有所提示。
其实单周期CPU中的写回模块就是一个多路选择器,通过CU单元的控制信号wd_sel决定写回数据的选择,备选的数据有:ALU运算结果,pc值,符号扩展结果imm,主存读取输出ramdout。
此模块是为了实际下板而设立的,主要负责控制开发板上的LED灯,拨码开关和数码管。
module PC(
input clk ,//CPU时钟
input rst ,//复位信号,高电平有效
input [31:0] npc ,//NPC模块的npc值
output reg [31:0] pc //PC模块的输出值pc
);
always @(posedge clk, posedge rst)
begin
if(rst) pc <= 32'h0ffff_fffc;//方便复位之后的第一个pc将为全0
else pc <= npc; //不复位时将npc作为pc的新值
end
endmodule
module NPC(
input [31:0] imm ,//符号扩展结果
input [31:0] rD1 ,//寄存器的第一输出
input pc_sel ,//来自CU单元,用于分支和跳转指令
input npc_op ,//同pc_sel来源相同
input [31:0] pc ,
output [31:0] npc
);
//npc_op为0时pc+4,为1时有跳转(pc_sel为0时pc + imm,为1时pc = rD1 + imm)
assign npc = npc_op ? (pc_sel ? rD1 + imm : pc + imm) : pc + 4;
endmodule
module inst_mem(
input [15:2] addr,//是pc信号的部分截取,可以思考一下为什么只需要2~15bit位?
output [31:0] inst //32位risv-v架构的指令
);
//program为vivado对应IP核的实例化,大小为16384*32bit
program U0_irom
(
.a (addr[15:2]),
.spo(inst )
);
module CU(
input fun7 ,//接口是inst[30]
input [ 2:0] fun3 ,//inst[14:12]
input [ 6:0] opcode ,//inst[6:0]
input [ 1:0] b_flag ,//branch的大小判断结果
//output reg debug_wb_ena,//debug信号
output reg npc_op ,//用于NPC模块确认npc值
output reg pc_sel ,//同样用于确认npc值
output reg rf_we ,//寄存器堆的写使能
output reg [ 1:0] wd_sel ,//写回模块的选择信号
output reg [ 2:0] sext_op ,//符号扩展单元的操作选择信号
output reg [ 3:0] alu_op ,//ALU单元的操作选择信号
output reg alua_sel,//ALU单元的第一操作数选择信号
output reg alub_sel,//ALU单元的第二操作数选择信号
output reg branch ,//分支信号,分支、跳转指令的标志
output reg dram_wr //主存的写使能
);
//以下带`的如`R,`DFT均为宏定义的常量
always @(*)
begin
case(opcode)
`R ://R-type
begin
//debug_wb_ena = 1;
npc_op = 0 ;
pc_sel = 0 ;
sext_op = `DFT ;
rf_we = 1 ;
wd_sel = 2'b00 ;
alua_sel = 0 ;
alub_sel = 0 ;
branch = 0 ;
dram_wr = 0 ;
case(fun3)
3'b000:
case(fun7)
1'b0: alu_op = `ADD ;//add
1'b1: alu_op = `SUB ;//sub
default:alu_op = `DFT ;
endcase
3'b001: alu_op = `LSHIFT ;//sll
3'b010: alu_op = `CMP ;//slt
3'b011: alu_op = `UCMP ;//sltu
3'b100: alu_op = `XOR ;//xor
3'b101:
case(fun7)
1'b0: alu_op = `RSHIFT ;//srl
1'b1: alu_op = `ARSHIFT;//sra
default:alu_op = `DFT ;
endcase
3'b110: alu_op = `OR ;//or
3'b111: alu_op = `AND ;//and
default: alu_op = `DFT ;
endcase
end
default://unknown-type
begin
/*...*/
end
endcase
end
endmodule
module RF(
input clk ,
input rst ,
input [ 4:0] rR1_addr,//寄存器堆第一输出的地址
input [ 4:0] rR2_addr,//寄存器堆第二输出的地址
input [ 4:0] wR ,//写寄存器堆的寄存器号
input rf_we ,
input [31:0] wD ,//写寄存器堆的写回数据
output reg [31:0] rD1 ,
output reg [31:0] rD2
);
reg [31:0] rf [1:31]; //用二维信号定义了寄存器堆,实际上只有31个,x0恒为0不需要定义
always @(*)
begin
rD1 <= rR1_addr ? rf[rR1_addr] : 32'h0;
rD2 <= rR2_addr ? rf[rR2_addr] : 32'h0;
end
//写
always @(posedge clk, posedge rst)
begin
if(rst)
begin
/*...全部置零操作*/
end
else if(rf_we)
begin
rf[wR] <= wR ? wD : 32'h0;
end
else;
end
endmodule
module SEXT(
input [31:7] inst ,
input [ 2:0] sext_op ,
output reg [31:0] imm
);
always @(*)
begin
case(sext_op)
`I_ext ://I-type
if(inst[31]) imm = {20'h0fffff, inst[31:20]};
else imm = {20'h000000, inst[31:20]};
default: imm = 32'b0;
endcase
end
endmodule
module ALU(
input [31:0] rD1 ,
input [31:0] pc ,
input [31:0] rD2 ,
input [31:0] imm ,
input [ 3:0] alu_op ,
input alub_sel,
input alua_sel,
input branch ,
output reg [ 1:0] b_flag ,
output reg [31:0] alu_c //ALU单元的运算结果
);
wire [31:0] alu_a; //第一操作数
wire [31:0] alu_b; //第二操作数
reg [31:0] compare; //比较计算结果
assign alu_a = alua_sel ? pc : rD1; //1:pc,0:rD1
assign alu_b = alub_sel ? imm : rD2;//1:imm,0:rD2
always @(*)
begin
if(branch)
begin//risc-v参与运算的数均为补码形式,通过减法结果符号位可判断大小
case(alu_op)
`SUB://此情况均为有符号数比较
begin
compare = alu_a + ~alu_b + 1;//用a - b来判断大小关系
if(compare[31]) b_flag = `LESS ;//a < b
else
if(compare) b_flag = `MORE ;//a > b
else b_flag = `EQUAL ;//a = b
end
/*...*/
default: b_flag = `DFT ;
endcase
end
else
case(alu_op)//至少9种操作
/*...*/
endcase
end
endmodule
module data_mem(
input clk ,
input [15:0] addr ,//alu单元运算结果就是地址
input dram_wr ,
input [31:0] din ,//输入数据就是RF的输出信号rD2
output [31:0] ramdout
);
wire ram_clk = ~clk;// 因为芯片的固有延迟,DRAM的地址线来不及在时钟上升沿准备好,
// 使得时钟上升沿数据读出有误。所以采用反相时钟,使得读出数据比地址准备好要晚大约半个时钟,
// 从而能够获得正确的数据。
wire [31:0] addr_tmp = addr - 16'h4000;
dram U_dram
(
.clk (ram_clk) , // input wire clka
.a (addr_tmp[15:2]) , // input wire [15:0] addra
.spo (ramdout) , // output wire [31:0] douta
.we (dram_wr) , // input wire [0:0] wea
.d (din) // input wire [31:0] dina
);
endmodule
module WB(
input [31:0] alu_c ,
input [31:0] pc ,
input [31:0] ramdout ,
input [31:0] imm ,
input [ 1:0] wd_sel ,
output reg [31:0] wD
);
always @(*)
begin
case(wd_sel)
2'b00: wD = alu_c ;
2'b01: wD = ramdout;
2'b10: wD = pc+4 ;
2'b11: wD = imm ;
default:wD = 32'h0 ;
endcase
end
endmodule
测试汇编代码:(包含算数逻辑运算,访存,跳转)
图1.3.2
图1.3.2中所执行的指令为lui x1,0x21212至jal start部分,其中bge的条件被满足会分支跳转,jal不会被执行,下一条指令将是sw x3,0(x0),寄存器x1在第一条指令后变为0x21212000,第二条指令后变为0x21212121;x2的值在第三条指令后值为0xffffffe1;x3在第四条指令后变为0x42424242,即x1内容左移1位的结果;x4在第五条指令后变为0x10909090,即为x1内容算术右移1位后的结果;同时第六条分支指令bge的b_flag结果为2,即表示x3>x2,进行跳转;目前为止所有指令均按照预期进行。
图1.3.3
图1.3.3展示从sw x3,0(x0)到lw x4,0(x0)部分,可见在执行完xor指令后,x1, x2, x3寄存器的值全部清零,ramdout的值变为x3的值,表示指令sw执行成功
图1.3.4
图1.3.4展示最后一条指令执行结果,x4在短暂清零后获得了存入内存的x3的值,至此,所有指令均成功执行。
构思这项工程时苦于自己近乎弱智的资料检索能力以及本届要求进一步严格化,只能“白手起家”从零开始搭建框架、实现模块、拼接连线以及逐步Debug,实际上板更是在若干次修改代码,写bitstream的噩梦循环中度过的,但总而言之对于设计硬件能力而言,确实有了长足的进步和质的提升。
最后附上一句激励我坚持完整个暑期学期的话语,伟人的言语总是充满力量与希望,在人心中种下信念的火种。
一步一步走下去
一点一滴做下去
世间事最怕的就是认真
——教员