verilog搭建单周期CPU与流水线CPU
目录
- 实现功能与搭建环境介绍
- 单周期CPU
- 整体框图
- 具体代码
- 顶层模块
- 取值
- 译码
- 执行
- 访存
- 写回
- 流水线CPU
- 整体框图
- 前置知识及思路探讨
- 如何让流水线流起来~
- Hazard_detect模块
- Jump_CU模块
实现功能与搭建环境介绍
本项目基于miniRISC-V指令集,实现其中的18条指令。
工具:vivado2018.3
最终实现单周期CPU频率为25MHz,流水线CPU停留在理论阶段(呃),欢迎探讨:
单周期CPU
整体框图
单周期CPU就是说一个时钟周期内完成CPU的所有动作:
各模块具体功能:
具体代码
顶层模块
module cpu(input clk,input reset,output [31:0] segment);wire clk_lock;wire p11_clk;wire [31:0] inst; // 指令wire [31:0] next_pc; // 下一条指令位置wire [31:0] current_pc; // 当前指令位置 wire [1:0] npcOp; // 下一条指令选择信号wire [1:0] WSel; // 写回内容选择信号wire RegWEn; // 控制寄存器堆读写wire [2:0] ImmSel; // 立即数选择信号wire BSel; // B操作数选择信号wire BrLt; // 分支控制判断信号:是否小于wire BrEq; // 分支控制判断信号:是否等于 wire [3:0] ALUSel; // ALU选择信号wire MemRW; // 控制数据存储器的读写wire [31:0] imm; // 输入的扩展后的立即数wire [31:0] data1; // rs1存储的内容wire [31:0] data2; // rs2存储的内容wire [31:0] dataW; // 将要存入rd的内容wire [31:0] data_b; // 操作数Bwire [31:0] data_a; // 操作数Awire [31:0] result; //ALU计算结果wire [31:0] data_from_mem;//来自存储器的数据// dataW三路选择器:来自DRAM/ALU/PC+4assign dataW = (WSel == 2'b00)?(data_from_mem):((WSel == 2'b01)?(result):((WSel == 2'b10)?(current_pc+4):2'b00));// 选择操作数B:来自data2/immassign data_b = (BSel == 0)?data2:imm;assign data_a = data1;// 分频模块100MHz->25MHzcpuclk UCLK (.clk_in1 (clk),.locked (clk_lock),.clk_out1 (p11_clk));// NPC单元npc NPC (.clk(p11_clk),.reset(reset), .npcOp_i(npcOp), .imm_i(imm), .ra_i(data1), .next_pc_o(next_pc),.current_pc_o(current_pc) ); // IMprgrom U0_irom (.a (current_pc[15:2]),.spo (inst));// RFrf RF (.clk(p11_clk),.reset(reset),.RegWEn_i(RegWEn), .rs1_i(inst[19:15]),.rs2_i(inst[24:20]),.rd_i(inst[11:7]),.data_w_i(dataW),.data1_o(data1),.data2_o(data2));// IMMEimme IMME (.clk(p11_clk),.reset(reset),.imm_i(inst[31:7]),.ImmSel_i(ImmSel), .imm_o(imm));// ALUalu ALU (.clk(p11_clk),.reset(reset),.data_a_i(data_a),.data_b_i(data_b),.ALUOp_i(ALUSel), .BrEq_o(BrEq), .BrLt_o(BrLt), .data_r_o(result) );// DRAM data_mem dram (.clk(p11_clk),.addr_i(result),.data_w_i(data2),.MemRW(MemRW),.data_r_o(data_from_mem),.segment(segment)
);// CUcu CU (.clk(p11_clk),.reset(reset),.inst(inst),.BrLt_i(BrLt),.BrEq_i(BrEq),.NPCOp_o(npcOp),.RegWEn_o(RegWEn),.WSel_o(WSel),.ImmSel_o(ImmSel),.BSel_o(BSel),.ALUSel_o(ALUSel),.MemRW_o(MemRW));endmodule
取值
NPC模块:
module npc(input clk,input reset, // 复位信号input [1:0] npcOp_i, // npc选择信号input [31:0] imm_i, // 输入的立即数,用于jal,jalr,B型指令的跳转input [31:0] ra_i, // rs1,用于jalr的跳转output wire [31:0] next_pc_o,output reg [31:0] current_pc_o);//下一条pc有三种选择://npc = pc+4 'b00//npc = pc+imm 'b01//npc = rs1+imm 'b10assign next_pc_o = (npcOp_i == 2'b00)?(current_pc_o+4):((npcOp_i == 2'b01)?(current_pc_o+imm_i):((npcOp_i == 2'b10)?(ra_i+imm_i):32'b0));always@ (posedge clk) beginif (reset == 0) begincurrent_pc_o <= -4;end else begincurrent_pc_o <= next_pc_o;endendendmodule
IM模块:取指单元包含了存放miniRV-1汇编程序的程序ROM (Instructioin ROM, IROM)。我们需要使用Vivado自带的存储IP核Distributed Memory Generator来定义IROM。(也可以自己写)
译码
RF模块:
module rf(input clk,input reset,input RegWEn_i, // 写使能信号。高电平写,低电平读input [4:0] rs1_i,input [4:0] rs2_i,input [4:0] rd_i,input [31:0] data_w_i,output [31:0] data1_o,output [31:0] data2_o);reg [31:0] regFile[0:31]; // 32个寄存器integer i;assign data1_o = regFile[rs1_i];assign data2_o = regFile[rs2_i];always@ (posedge clk) beginif (reset == 0) beginfor (i = 0;i < 32;i = i+1)regFile[i] <= 0;end else if (RegWEn_i != 0 && rd_i != 0) beginregFile[rd_i] <= data_w_i;endend endmodule
IMME模块:
module imme(input clk,input reset,input [24:0] imm_i,input [2:0] ImmSel_i, output wire [31:0] imm_o);wire [31:0] imm_wire;assign imm_wire = (ImmSel_i == 3'b000)?({{11{imm_i[24]}},imm_i[24],imm_i[12:5],imm_i[13],imm_i[23:14],1'b0}):((ImmSel_i == 3'b001)?({{20{imm_i[24]}},imm_i[24:13]}):((ImmSel_i == 3'b010)?({{20{imm_i[24]}},imm_i[24:18],imm_i[4:0]}):((ImmSel_i == 3'b011)?({{12{imm_i[24]}},imm_i[24:5]}):((ImmSel_i == 3'b100)?({{19{imm_i[24]}},imm_i[24],imm_i[0],imm_i[23:18],imm_i[4:1],1'b0}):32'b0))));assign imm_o = imm_wire; endmodule
CU模块:
module cu(input clk,input reset,input [31:0] inst,input BrLt_i,input BrEq_i,output reg [1:0] NPCOp_o,output reg RegWEn_o,output reg [1:0] WSel_o,output reg [2:0] ImmSel_o,output reg BSel_o,output reg [3:0] ALUSel_o,output reg MemRW_o);wire [6:0] funct7;wire [2:0] funct3;wire [6:0] opcode;assign funct7 = inst[31:25];assign funct3 = inst[14:12];assign opcode = inst[6:0];always @(inst or BrEq_i or BrLt_i) beginif (reset == 0) beginRegWEn_o = 0;WSel_o = 0;ImmSel_o = 0;BSel_o = 0;MemRW_o = 0;NPCOp_o = 0;ALUSel_o = 0;end else begincase (opcode)7'b0110011 : begin // R型指令case(funct3) // 使用funct3与funct7判断ALUSel3'b000:beginif (funct7[5] == 1) beginALUSel_o = 4'b0001;end else beginALUSel_o = 4'b0000;endend3'b111:ALUSel_o = 4'b0010;3'b110:ALUSel_o = 4'b0011;3'b100:ALUSel_o = 4'b1000;3'b001:ALUSel_o = 4'b0100;3'b101:beginif (funct7[5] == 0) beginALUSel_o = 4'b0101;end else beginALUSel_o = 4'b0110;endendendcaseNPCOp_o = 2'b00;RegWEn_o = 1;WSel_o = 2'b01;ImmSel_o = 3'b000;BSel_o = 0;MemRW_o = 0;end7'b0010011 : begin // I型指令case(funct3) // 使用funct3与funct7判断ALUSel3'b000:ALUSel_o = 4'b0000;3'b111:ALUSel_o = 4'b0010;3'b110:ALUSel_o = 4'b0011;3'b100:ALUSel_o = 4'b1000;3'b001:ALUSel_o = 4'b0100;3'b101:beginif (funct7[5] == 0) beginALUSel_o = 4'b0101;end else beginALUSel_o = 4'b0110;endendendcaseNPCOp_o = 2'b00;RegWEn_o = 1;WSel_o = 2'b01;ImmSel_o = 3'b001;BSel_o = 1;MemRW_o = 0;end7'b0000011 : begin // load指令NPCOp_o = 2'b00;RegWEn_o = 1;WSel_o = 2'b00;ImmSel_o = 3'b001;BSel_o = 1;ALUSel_o = 4'b0000;MemRW_o = 0;end7'b1100111 : begin // jalrNPCOp_o = 2'b10;RegWEn_o = 1;WSel_o = 2'b10;ImmSel_o = 3'b001;BSel_o = 0;ALUSel_o = 4'b0000;MemRW_o = 0;end7'b0100011 : begin // S型指令NPCOp_o = 2'b00;RegWEn_o = 0;WSel_o = 2'b00;ImmSel_o = 3'b010;BSel_o = 1;ALUSel_o = 4'b0000;MemRW_o = 1;end7'b1100011 : begin // B型指令case (funct3)3'b000:NPCOp_o = (BrEq_i == 1)?2'b01:2'b00;3'b001:NPCOp_o = (BrEq_i == 0)?2'b01:2'b00;3'b100:NPCOp_o = (BrLt_i == 1)?2'b01:2'b00;3'b101:NPCOp_o = (BrLt_i == 0)?2'b01:2'b00;endcaseRegWEn_o = 0;WSel_o = 2'b00;ImmSel_o = 3'b100;BSel_o = 0;ALUSel_o = 4'b0001;MemRW_o = 0;end7'b0110111 : begin // U型指令NPCOp_o = 2'b00;RegWEn_o = 1;WSel_o = 2'b01;ImmSel_o = 3'b011;BSel_o = 1;ALUSel_o = 4'b0111;MemRW_o = 0;end7'b1101111 : begin // J型指令NPCOp_o = 2'b01;RegWEn_o = 1;WSel_o = 2'b10;ImmSel_o = 3'b000;BSel_o = 0;ALUSel_o = 4'b0000;MemRW_o = 0;enddefault: NPCOp_o <= 2'b00;endcaseendendendmodule
执行
ALU模块:
module alu(input clk,input reset,input [31:0] data_a_i,input [31:0] data_b_i,input [3:0] ALUOp_i, // ALU选择信号output wire BrEq_o, // 分支判断,判断是否相等output wire BrLt_o, // 分支判断,判断是否小于output wire [31:0] data_r_o // 计算结果);// ADD-000,SUB/B-001,AND-010,OR-011,SLL-100,SRL-101,SRA-110,U型指令,左移12位-111,xor-1000
assign data_r_o = (ALUOp_i == 4'b0000)?(data_a_i + data_b_i):((ALUOp_i == 4'b0001)?(data_a_i + (~data_b_i + 1'b1)):((ALUOp_i == 4'b0010)?(data_a_i & data_b_i):((ALUOp_i == 4'b0011)?(data_a_i | data_b_i):((ALUOp_i == 4'b0100)?(data_a_i << (data_b_i[4:0])):((ALUOp_i == 4'b0101)?(data_a_i >> (data_b_i[4:0])):((ALUOp_i == 4'b0110)?($signed($signed(data_a_i) >>> (data_b_i[4:0]))):((ALUOp_i == 4'b0111)?(data_b_i <<12):((ALUOp_i == 4'b1000)?(data_a_i^data_b_i):32'b0))))))));assign BrEq_o = (ALUOp_i == 4'b0001 && data_r_o == 0)?1:0;
assign BrLt_o = (ALUOp_i == 4'b0001 && $signed(data_r_o) < 0)?1:0;endmodule
访存
DRAM模块:
module data_mem(input clk,input [31:0] addr_i,input [31:0] data_w_i,input MemRW,output [31:0] data_r_o,output reg [31:0] segment // 外设:显示管);wire [31:0] addr;assign addr = (addr_i == 32'hFFFFF000)?addr_i:(addr_i - 16'h4000); // 0xFFFFF000为外设地址。数据存储器与指令存储器采用统一编址,因此数据存储器地址需要减去0x4000// DRAM dram U_dram (.clk (clk), // input wire clka.a (addr[15:2]), // input wire [13:0] addra.spo (data_r_o), // output wire [31:0] douta.we (MemRW), // input wire [0:0] wea.d (data_w_i) // input wire [31:0] dina
);always @(posedge clk) beginsegment <= (addr_i == 32'hFFFFF000 && MemRW)?data_w_i:segment;endendmodule
这里访存模块写了一个简陋的总线,连接外设便于上板显示。
其中数据存储器模块dram使用了IP核,同样也使用Distributed Memory Generator来实现。
写回
执行目的寄存器的写入。
流水线CPU
整体框图
流水线整体设计:
整体框图:
冲突解决模块具体设计:
跳转控制模块具体设计:
前置知识及思路探讨
如何让流水线流起来~
啊这个其实我是实现了的,就是指令跳转方面有点问题,不涉及指令跳转倒是正常。
本项目采用五级流水线,一个时钟周期执行一个阶段,所以需要在每级之间加入寄存器保存数据。即有四个流水线寄存器,因为每一级分别运行不同的指令,每条指令在各级之间呈阶梯型传递(所以叫流水线哈哈),所以我们需要考虑每级之间通过寄存器要传递什么数据。比如ID阶段得到的控制信号,在EX阶段与MEM阶段也要使用,而有些数据在此阶段已经使用完毕,所以不用再向下传递。
尝试在理论层面解决数据冲突与指令跳转问题↓
Hazard_detect模块
因为每一级分别运行不同指令,所以指令间难免冲突。
数据冒险:
数据冒险是指因无法提供指令执行所需数据而导致指令不能再预期的时钟周期内执行的情况。即之前指令的目的寄存器等于当前指令的源寄存器,ID阶段从源寄存器中所取内容不是期待的内容,所需数据可能停留在之前指令的EX,MEM或WB阶段,还未来得及更新。
需要解决的三种数据冒险:
① R-R1型:期待内容是之前指令ALU的计算结果。
② Load-use型:期待内容是之前指令从DRAM中读出数据。
③ R-R2型:期待内容是之前指令WB阶段的写回数据。
条件判定分别如下:
① R-R1型:EX.RegWEn && EX.rd ≠ 0 && (EX.rd == ID.rs1 || EX.rd == ID.rs2)
② Load-use型:MEM.RegWEn && MEM.rd ≠ 0 && (MEM.rd == ID.rs1 || MEM.rd == ID.rs2)
③ R-R2型:WB.RegWEn && WB.rd ≠ 0 && (WB.rd == ID.rs1 || WB.rd == ID.rs2)
解决方法分别如下:
① R-R1型:前递,运算结果直接作为操作数再次运算。
② R-R2型:前递,要写入寄存器堆中的数据直接作为读出数据。
③ Load-use型:前递+停顿一周期。停顿时需要保存PC,IF-ID寄存器模块中的内容,清除ID-EX寄存器中的内容。
控制冒险:
接收来自Jump_Cu模块的NPCOp信号,若为高电平则代表发生控制冒险,清除IF-ID,ID-EX寄存器中的内容,确保流水线正常运行。
Jump_CU模块
删除单周期流水线中的NPC模块,把涉及jal,jalr,B型指令的npc计算转移至Jump_CU模块。
输入信号Jump[0]区分是否为跳转指令,Jump[1]区分是否为无条件跳转,Jump[2:4]区分为jal,jalr或B型指令中的一种。若输入Jump[0]为0,则当前指令不涉及指令跳转,输出NPCOp为低电平,反之进一步判断;若Jump[1]为1,则为jal,jalr的无条件跳转,输出NPCOp为高电平;反之Jump[1]为0,则为B型指令的有条件跳转,进一步根据ALU模块计算得来的BrLt信号与BrEq信号判定是否需要跳转,若是则输出NPCOp为高电平,反之为低电平。
输出的NPCOp信号交给Hazard_Detect模块,为高电平则代表发生控制冒险,清除IF-ID,ID-EX寄存器中的内容,将计算出的正确npc指令传递给PC模块,运行正确指令。
但是B型指令需要ALU的执行结果判断是否满足条件跳转,jal与jalr可以不用在EX阶段才判断,可以在ID阶段就进行跳转。
verilog搭建单周期CPU与流水线CPU相关推荐
- 使用logisim搭建单周期CPU与添加指令
使用logisim搭建单周期CPU与添加指令 搭建 总设计 借用高老板的图,我们只需要分别做出PC.NPC.IM.RF.EXT.ALU.DM.Controller模块即可,再按图连线,最后进行控制信号 ...
- 单周期31条指令CPU设计---bug总结
单周期31条指令CPU设计bug-总结 vivado 2016.2 verilog modelsim Mars标准 -声明:该篇总结的bug是在编写代码,并进行测试过程中遇到问题,并及时记录.并不具有 ...
- 计算机组成原理单周期mips,计算机组成原理CPU单周期数据通路(MIPS)
计算机组成原理CPU单周期数据通路(MIPS) [计算机组成原理]CPU:单周期数据通路(MIPS) 寄存器传送语言RTL 1)R(r)表示寄存器r的内容 2)M(address)表示主存储器地址ad ...
- 手搓单周期、流水线CPU
实验指导书连接实验概述 - 计算机设计与实践(2022夏季) | 哈工大(深圳) (gitee.io) 实现的18条指令如下: [注释:(r)表示寄存器r的值,Mem[addr]表示地址为addr的存 ...
- 使用Verilog搭建一个单周期CPU
使用Verilog搭建一个单周期CPU 搭建篇 总体结构 其实跟使用logisim搭建CPU基本一致,甚至更简单,因为完全可以照着logisim的电路图来写,各个模块和模块间的连接在logisim中非 ...
- 基于RISC-V指令集架构的单周期CPU与五级流水线的实现(一)——分析
本文是为完全不了解CPU的朋友所写的入门级教程,对于较为精通的朋友,多数章节均为赘述,完整代码在下一篇博客中,请见谅哈 一.实现功能 实现了部分RV32I指令集中的部分指令类型,如下表 具体指令如下( ...
- (Verilog)单周期CPU设计
(Verilog)单周期CPU设计 首先是基础资料部分(借用学校资料): 一.实验内容 设计一个单周期CPU,该CPU至少能实现以下指令功能操作.需设计的指令与格式如下: ==> 算术运算指令 ...
- 【计算机组成原理】实验4:单周期CPU(Verilog)中海大
[计算机组成原理]实验4 使用Verilog语言实现一个单周期CPU,测试平台:Vivado ①部分代码: single_cycle_cpu.v: `timescale 1ns / 1ps`defin ...
- 31条指令单周期cpu设计(Verilog)-(十)上代码→顶层模块设计总结
说在前面 开发环境:Vivado 语言:Verilog cpu框架:Mips 控制器:组合逻辑 设计思路 按照预先设计好的数据通路图将各个模块连接起来 `timescale 1ns / 1ps mod ...
最新文章
- 在gem5的full system下运行 x86编译的测试程序 running gem5 on ubuntu in full system mode in x86...
- [HAOI2015][loj2127]按位或
- Android Sqlite 数据初始化
- Gradle打包命令记录
- 【专升本计算机】2021年甘肃省专升本计算机全真模拟试题(五)
- HH SaaS电商系统的物流单设计
- linux挂接u盘视频,LINUX挂接U盘
- Mongodb 与 MySQL对比
- Implement Stack using Queues
- 开票接口系统能够解决的十大问题
- MySQL Workbench 8.0 CE 汉化包下载
- oracle建表及授权
- GAN合成语音相关论文
- c语言的关键字母大小写表示,英语26个字母大小写标准写法
- 电脑声控 电脑机器人功能
- 选择结构与分支结构 计算器的实现
- 屌丝码农该怎么过周末
- win10系统要求配置_观察者系统还原游戏配置要求高吗?Observer: System Redux硬件一览!...
- 三天学会MySQL - MySQL数据库章节练习
- win32面试题总结
热门文章
- 各系统的未知数个数都小于方程个数,但所有未知数仍可能可以求解
- linux中 ~是什么意思。 cd ~ - ./ ../的区别
- 华为防火墙技术-防火墙基础
- Wisdom RESTClient 工具
- Tomcat8.0安装包与解压包
- 工欲善其事必先利其器,网红营销找对工具才是营销王道
- Java作业多线程的应用 三人抢票
- 信奥一本通1314:【例3.6】过河卒(Noip2002)
- NSX-T 系列:第 18部分 - 配置内联负载均衡服务(LB)
- contenteditable富文本编辑器支持emoji插入表情