MIPS-单周期CPU设计

设计一个单周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:



实验原理
单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。时钟周期一般也称振荡周期(如果晶振的输出没有经过分频就直接作为CPU的工作时钟,则时钟周期就等于振荡周期。若振荡周期经二分频后形成时钟脉冲信号作为CPU的工作时钟,这样,时钟周期就是振荡周期的两倍。) CPU在处理指令时,一般需要经过以下几个步骤:

(1) 取指令(IF):根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,PC根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入PC,当然得到的“地址”需要做些变换才送入PC。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
单周期CPU,是在一个时钟周期内完成这五个阶段的处理。


实验器材
电脑一台、Xilinx ISE 软件一套。

实验分析与设计
1、 根据数据通路图,将整个cpu设计分为六个模块。

分别为指令读取模块,控制单元,指令寄存器,扩展模块,运算模块和数据存储器。用一个顶层代码将cpucode.v将所有的模块统一管理,还有必要的wire线声明。
写完这几行代码后ctrl+s就会自动生成子模块,add source即可,记得命名一致

//操作码给ControlUnit,zero用来bne和beq指令的判断跳转
ControlUnit controlunit (operation, zero, PCWre, ALUSrcB, ALUM2Reg, RegWre, InsMemRW, DataMemRW, ExtSel, PCSrc, RegOut,ALUOp);RegisterFile registerfile (rs, rt, rd, write_data, RegWre, RegOut,clk,readData1,readData2);Extend extend(immediate_16, ExtSel, immediate_32);ALU alu(readData1, readData2, immediate_32, ALUSrcB, ALUOp, zero, result);DataSaver datasaver(result, readData2, DataMemRW, ALUM2Reg, write_data);

除此之外,我将pc模块直接写在了主模块中,因为pc的操作涉及到初始化,所以分模块对于变量的修改会比较麻烦一些。

//执行下一条指令
always@(posedge clk) beginif  ( PCWre == 1)PC <= (PCSrc == 0)? PC + 4 : PC + 4 + immediate_32 * 4;else PC <= PC;
end
initial beginPC = 0;clk = 0;
endalways #500clk = ~clk;

2、 对于instructionSave模块,只需要申请一块mem空间,将指令读入并存储起来即可。并在PC改变的时候将新的指令赋值给指令线。
注意:由于mem是直接拿到一条指令的,所以PC加4不对,为了解决这个问题,将PC**右移两位**即可。

module InstructionSave(input [31:0] PC,output reg [31:0] instruction);reg [31:0] mem [0:64];initial begin$readmemb("my_test_rom.txt", mem);endalways@(PC) begininstruction <= mem[PC >> 2];//$display("the instruction now is %d", instruction);end
endmodule

3、 对于controlUnit模块,要负责产生信号。对各个不同的操作数要产生不同的信号值。
首先我们先列出各个信号各个值的作用。


接着列出各个指令下(操作码)应该有的值。

最初分析肯定是传入参数直接赋值。后来经过同学指点,发现有更简单的方法:如下

    always@(operation) begin//初始化这些变量i_add = 0;i_addi = 0;i_sub = 0;i_ori = 0;i_and = 0;i_or = 0;i_move = 0;i_sw = 0;i_lw = 0;i_beq = 0;i_halt = 0;//如果是对应操作码则将对应变量名置为1case(operation)ADD: i_add = 1;ADDI: i_addi = 1;SUB: i_sub = 1;ORI: i_ori = 1;AND: i_and = 1;OR: i_or = 1;MOVE: i_move = 1;SW: i_sw = 1;LW: i_lw = 1;BEQ: i_beq = 1;HALT: i_halt = 1;endcaseend//有点类似数字电路中学到的真值表。将各个信号直接用操作码的变量表示。assign PCWre = !i_halt;assign ALUSrcB = i_addi || i_ori || i_sw || i_lw;assign ALUM2Reg = i_lw;assign RegWre = !(i_sw || i_beq);assign InsMemRW = 0;assign DataMemRW = i_sw;assign ExtSel = !i_ori;   //除了ori是0扩展,其他的也可以为符号扩展assign PCSrc = (i_beq && zero); //beq且相减之后值为0assign RegOut = !(i_addi || i_ori || i_lw);assign ALUOp = {i_and, i_ori || i_or, i_sub || i_ori || i_or || i_beq};

即用或操作进行管理,减少了许多重复的代码量。

4、 对于registerFile模块,根据老师讲课给的寄存器行为代码稍加修改即可。
实现取数,存数的功能

module RegisterFile(input    [4:0]   rs,rt,rd,input   [31:0] write_data,input       RegWre,RegOut,clk,  output [31:0] readData1,readData2);
//  integer i;wire [4:0] rin;assign rin = (RegOut == 0) ? rt : rd;//声明31个寄存器,不需要0号寄存器因为后面会判断reg     [31:0]  register [1:31]; // r1 - r31integer i;initial beginfor (i = 0; i < 32; i = i + 1)register[i] = 0;end//0号寄存器值固定为0assign readData1 = (rs == 0)? 0 : register[rs]; // 取数assign readData2 = (rt == 0)? 0 : register[rt]; // 取数//当时钟上升沿到来,将计算得到的值或者拿到的值存入指定寄存器always @(posedge clk) beginif ((rin != 0) && (RegWre == 1)) begin // RegWre==1时写入register[rin] <= write_data;endend
endmodule

5、 对于extend模块,较为简单,甚至可以一句话说清。三种情况,ExtSel == 0 零扩展, == 1,符号扩展

assign immediate_32 = (ExtSel)? {{16{immediate_16[15]}}, immediate_16[15:0]} : {{16{1'b0}}, immediate_16[15:0]};

6、 对于alu模块
要根据输入的ALUop进行计算,初次之外,如果计算结果为0,要将zero变量置1,因为要考虑beq的影响。Beq是相当于减法,如果zero等于1且操作码为beq时会进行跳转。

    wire [31:0] alub;//ALUSrcB == 1 要计算立即数assign alub = (ALUSrcB == 0) ? readData2 : immediate_32;always@( readData1 or alub or ALUOp) begin$display("here");case (ALUOp)3'b000: result <= readData1 + alub;3'b001: result <= readData1 - alub;3'b010: result <= alub - readData1;3'b011: result <= readData1 | alub;3'b100: result <= readData1 & alub;3'b101: result <= ~readData1 & alub;3'b110: result <= (~readData1 & alub) | (readData1 & ~alub);3'b111: result <= (readData1 & alub) | (~readData1 & ~alub);endcaseendassign zero = (result == 0) ? 1 : 0;

7、对于DataSave数据存储模块,有存储数据,获得数据,以及选择是运算输出还是lw输出几个功能。

always@(result or DataMemRW) beginif (DataMemRW == 0) //lwDataOut = DataMem[result];else   //swDataMem[result] = readData2;
end// == 0 是alu运算输出, == 1是数据存储输出  数据选择器assign write_data = (ALUM2Reg == 0) ? result : DataOut;

至此,模块分析完毕,下面是数据测试。
编写简单的测试程序段:

my_rom_test.txt 如下:

0000000000000000000000000000000
00000100000000010000000000000100
00000100000000100000000000001000
10011000010000100000000000000000
00000000010000010001100000000000
00001000011000010001100000000000
11000000010000111111111111111110
01000000001000010000000000000001
01001000010000010001100000000000
10000000010000000001100000000000
01000100011000100000100000000000
10011100010001000000000000000000
11111100000000000000000000000000

下面对每条指令进行分析:选中cpucode.v,右键仿真运行,Memory可查看寄存器的值







实验心得
问题一、不理解wire reg 这些的具体用法,经常出现语法错误:
解决:wire是线,在某一模块定义后,可传入其子模块。当变量改变时能够互相传递。wire在该定义模块不能作为表达式的左值。reg 则可以作为左值。在需要修改变量的时候要用reg声明。如将wire传到子模块后,需要修改值可以用output reg …
问题二、<= 和 = 的区别?
解决:<= 是非阻塞式赋值, = 是阻塞式赋值。当 = 这个赋值语句执行的时候是不允许有其它语句执行的,这就是阻塞的原因。而非阻塞赋值,例如a<=b;当这个赋值语句执行的时候是不阻碍其它语句执行的。
问题三、主模块将immediate_32这个变量声明拼写错误后,传入拼写正确,但是不报错,能运行。
解决:这绝对是我debug最久的一个问题。第一个addi语句一直没法成功执行。Immediate_32在主模块的值是zzzzzzzz高阻态。而子模块extend却能够得出正确的结果。Alu拿不到值,后面的运算没法进行,整个程序都动不了。找了很久才发现变量声明错误。我只想知道为什么verilog不支持报这种语法错误。

心得:
不管怎么说花了好长时间写出第一个cpu还是相当兴奋的,虽然简单,但是通过整个过程能够理解到cpu工作的一些基本原理,再和理论课的知识结合起来,印象就十分深刻了。测试过程中一步一步地改善自己的代码,一步一步调试自己的错误。希望自己以后还是要多细心点吧,不要再犯低级的拼写错误了。verilog和之前学的语言不一样,整个并发式执行开始的时候有点难理解,整个思维都要跟着变化,现在一个简单的mips cpu下来,感觉也挺好玩的吧。受益良多。

完整代码

cpucode.v

`timescale 1ns / 1ps`timescale 1ns / 1psmodule CPU_CPU_sch_tb();//此模块中需要赋值的变量wire [5:0] operation;wire [4:0] rs;wire [4:0] rt;wire [4:0] rd;wire [15:0] immediate_16;reg clk;wire [31:0] result;wire [31:0] write_data;//controlunit产生的控制信号线wire PCWre;wire ALUSrcB;wire ALUM2Reg;wire RegWre;wire InsMemRW;wire DataMemRW;wire ExtSel;wire PCSrc;wire RegOut;wire [2:0] ALUOp;//其他的模块相互传递的线wire [31:0] instruction;reg [31:0] PC;wire [31:0] immediate_32;wire [31:0] readData1;wire [31:0] readData2;wire zero;initial beginPC = 0;clk = 0;
endalways #500clk = ~clk;//输入PC地址,需要InsMemRW的值来确定是读指令还是写指令,得到的值存在instruction
InstructionSave instructionsave(PC, instruction);//分解拿到的指令instruction各个组块
assign operation[5:0] = instruction[31:26];
assign rs = instruction[25:21];
assign rt = instruction[20:16];
assign rd = instruction[15:11];
assign immediate_16 = instruction[15:0];//操作码给ControlUnit,zero用来bne和beq指令的判断跳转
ControlUnit controlunit (operation, zero, PCWre, ALUSrcB, ALUM2Reg, RegWre, InsMemRW, DataMemRW, ExtSel, PCSrc, RegOut,ALUOp);RegisterFile registerfile (rs, rt, rd, write_data, RegWre, RegOut,clk,readData1,readData2);Extend extend(immediate_16, ExtSel, immediate_32);ALU alu(readData1, readData2, immediate_32, ALUSrcB, ALUOp, zero, result);DataSaver datasaver(result, readData2, DataMemRW, ALUM2Reg, write_data);//执行下一条指令
always@(posedge clk) beginif  ( PCWre == 1)PC <= (PCSrc == 0)? PC + 4 : PC + 4 + immediate_32 * 4;else PC <= PC;
endendmodule

instructionSave.v

`timescale 1ns / 1psmodule InstructionSave(input [31:0] PC,output reg [31:0] instruction);reg [31:0] mem [0:64];initial begin$readmemb("my_test_rom.txt", mem);endalways@(PC) begininstruction <= mem[PC >> 2];//$display("the instruction now is %d", instruction);end
endmodule

controlUnit.v

`timescale 1ns / 1psmodule ControlUnit(input [5:0] operation,input zero,output  PCWre,output  ALUSrcB,output  ALUM2Reg,output  RegWre,output  InsMemRW,output  DataMemRW,output  ExtSel,output  PCSrc,output  RegOut,output  [2:0] ALUOp);parameter ADD = 6'b000000, ADDI = 6'b000001, SUB = 6'b000010, ORI = 6'b010000,AND = 6'b010001, OR = 6'b010010, MOVE = 6'b100000, SW = 6'b100110,LW = 6'b100111, BEQ = 6'b110000, HALT = 6'b111111;reg i_add, i_addi, i_sub, i_ori, i_and, i_or, i_move, i_sw, i_lw, i_beq, i_halt;always@(operation) begin//初始化这些变量i_add = 0;i_addi = 0;i_sub = 0;i_ori = 0;i_and = 0;i_or = 0;i_move = 0;i_sw = 0;i_lw = 0;i_beq = 0;i_halt = 0;//如果是对应操作码则将对应变量名置为1case(operation)ADD: i_add = 1;ADDI: i_addi = 1;SUB: i_sub = 1;ORI: i_ori = 1;AND: i_and = 1;OR: i_or = 1;MOVE: i_move = 1;SW: i_sw = 1;LW: i_lw = 1;BEQ: i_beq = 1;HALT: i_halt = 1;endcaseend//有点类似数字电路中学到的真值表。将各个信号直接用操作码的变量表示。assign PCWre = !i_halt;assign ALUSrcB = i_addi || i_ori || i_sw || i_lw;assign ALUM2Reg = i_lw;assign RegWre = !(i_sw || i_beq);assign InsMemRW = 0;assign DataMemRW = i_sw;assign ExtSel = !i_ori;   //除了ori是0扩展,其他的也可以为符号扩展assign PCSrc = (i_beq && zero); //beq且相减之后值为0assign RegOut = !(i_addi || i_ori || i_lw);assign ALUOp = {i_and, i_ori || i_or, i_sub || i_ori || i_or || i_beq};endmodule

registerFile.v

`timescale 1ns / 1psmodule RegisterFile(input    [4:0]   rs,rt,rd,input   [31:0] write_data,input       RegWre,RegOut,clk,  output [31:0] readData1,readData2);
//  integer i;wire [4:0] rin;assign rin = (RegOut == 0) ? rt : rd;//声明31个寄存器,不需要0号寄存器因为后面会判断reg     [31:0]  register [1:31]; // r1 - r31integer i;initial beginfor (i = 0; i < 32; i = i + 1)register[i] = 0;end//0号寄存器值固定为0assign readData1 = (rs == 0)? 0 : register[rs]; // 取数assign readData2 = (rt == 0)? 0 : register[rt]; // 取数//当时钟上升沿到来,将计算得到的值或者拿到的值存入指定寄存器always @(posedge clk) beginif ((rin != 0) && (RegWre == 1)) begin // RegWre==1时写入register[rin] <= write_data;endend
endmodule

extend.v

`timescale 1ns / 1psmodule Extend(input [15:0] immediate_16,input ExtSel,output [31:0]immediate_32 );assign immediate_32 = (ExtSel)? {{16{immediate_16[15]}}, immediate_16[15:0]} : {{16{1'b0}}, immediate_16[15:0]};endmodule

alu.v

module ALU( input [31:0] readData1,input [31:0] readData2,input [31:0] immediate_32,input ALUSrcB,input [2:0] ALUOp,output wire zero,output reg [31:0] result);wire [31:0] alub;//ALUSrcB == 1 要计算立即数assign alub = (ALUSrcB == 0) ? readData2 : immediate_32;always@( readData1 or alub or ALUOp) begin$display("here");case (ALUOp)3'b000: result <= readData1 + alub;3'b001: result <= readData1 - alub;3'b010: result <= alub - readData1;3'b011: result <= readData1 | alub;3'b100: result <= readData1 & alub;3'b101: result <= ~readData1 & alub;3'b110: result <= (~readData1 & alub) | (readData1 & ~alub);3'b111: result <= (readData1 & alub) | (~readData1 & ~alub);endcaseendassign zero = (result == 0) ? 1 : 0;endmodule

datasaver.v

module DataSaver(input [31:0] result,input [31:0] readData2,input DataMemRW,input ALUM2Reg,output [31:0] write_data);reg [31:0] DataMem [0:63];reg [31:0] DataOut;//初始化integer i;initial beginfor (i = 0; i < 64; i = i + 1)DataMem[i] = 0;end//通过判断DataMemRW的值来判断要进行读操作还是写操作always@(result or DataMemRW) beginif (DataMemRW == 0) //lwDataOut = DataMem[result];else   //swDataMem[result] = readData2;end// == 0 是alu运算输出, == 1是数据存储输出  数据选择器assign write_data = (ALUM2Reg == 0) ? result : DataOut;
endmodule

MIPS-单周期CPU设计相关推荐

  1. MIPS单周期CPU设计(24条指令)

    MIPS单周期可执行24条指令CPU 实验要求 本实训项目帮助学生构建支持 24 条指令的 MIPS 单周期 CPU ,最终实现的处理器能运行 benchmark 测试程序.另外希望学有余力的同学能为 ...

  2. 【Computer Organization笔记10】单周期CPU设计:基于7条MIPS指令的数据通路

    本次笔记内容: P19 计算机组成原理(19) P20 计算机组成原理(20) 本节课对应幻灯片: 组成原理24 singlecycle.pptx 基于上节课的7条MIPS指令的数据通路,分别针对7条 ...

  3. 【中山大学计算机组成原理实验】单周期CPU设计与实现

    实验一 : 单周期CPU设计与实现 一. 实验目的 (1) 掌握单周期CPU数据通路图的构成.原理及其设计方法: (2) 掌握单周期CPU的实现方法,代码实现方法: (3) 认识和掌握指令与CPU的关 ...

  4. (Verilog)单周期CPU设计

    (Verilog)单周期CPU设计 首先是基础资料部分(借用学校资料): 一.实验内容 设计一个单周期CPU,该CPU至少能实现以下指令功能操作.需设计的指令与格式如下: ==> 算术运算指令 ...

  5. 单周期CPU设计与实现原理分析

    文章目录 单周期CPU设计与实现原理分析 一.单周期CPU的设计思路 二.单周期CPU的模块实现 ① Instruction Memory指令存储器的设计 ② ALU算术逻辑单元的设计 ③ PC程序计 ...

  6. Risc-V单周期CPU设计思考

    Risc-V单周期CPU设计思考 今年学校课程改革,计算机组成课开始教学Risc-V,写单周期CPU的时候发现网上好像大多都是MIPS的CPU设计,所以就把自己关于设计Verilog的一些思路整理出来 ...

  7. 31条指令单周期cpu设计(Verilog)-(十)上代码→顶层模块设计总结

    说在前面 开发环境:Vivado 语言:Verilog cpu框架:Mips 控制器:组合逻辑 设计思路 按照预先设计好的数据通路图将各个模块连接起来 `timescale 1ns / 1ps mod ...

  8. 31条指令单周期cpu设计(Verilog)-(二)总体设计

    目录 31条指令单周期cpu设计(Verilog)-(一)相关软件 31条指令单周期cpu设计(Verilog)-(二)总体设计 31条指令单周期cpu设计(Verilog)-(三)指令分析      ...

  9. 计算机组成原理实验单周期处理,计算机组成原理实验实验报告-单周期cpu设计...

    计算机组成原理实验实验报告-单周期cpu设计 (16页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 计算机组成原理实验计算机组成原理实验 ...

  10. 北航2021届计组 -- 单周期CPU设计

    单周期CPU设计 写在前面的话 ​ 首先需要强调的是,这是一篇考后反思,所以相比于之前的HDLBit的以力证道,或者MIPS的技之巅峰(不要笑,我的技巧最高水平就那样了).这篇文章要温吞很多.这篇文章 ...

最新文章

  1. docker-ce-17.09 网络基础配置
  2. 文巾解题 793. 阶乘函数后 K 个零
  3. 在python语言中下列是二进制整数_Python从菜鸟到高手(5):数字
  4. 台湾大学林轩田机器学习基石课程学习笔记14 -- Regularization
  5. 第20课 孔融让梨 《小学生C++趣味编程》
  6. 揭秘计算机之间互发数据的关键原理!
  7. 移动端高清适配方案(解决图片模糊问题、1px细线问题)
  8. 【数据结构】二叉树的python实现
  9. php扫雷算法,扫雷游戏算法 - osc_qv1fwke0的个人空间 - OSCHINA - 中文开源技术交流社区...
  10. [小甲鱼]汇编语言笔记 基础知识
  11. 微信小程序开发者工具详解
  12. 易辅客栈网页游戏脚本实战(绝世仙王)
  13. 泪目!这篇博士论文致谢走红:感谢博一与我结婚的妻子...
  14. FINAUNCE金融业增速反弹信贷投放创新高叠加股市回暖
  15. Hi3861网络通信——UDP收发
  16. 23种常见设计模式详解
  17. win10管理员权限怎么获得_实用技巧:如何在win10中安装没有管理员权限的软件...
  18. 松下A6伺服驱动器MADLN15SE与欧姆龙PLC的连接和试运行
  19. 面试官:为啥要axios 的二次封装呢 及其使用是干啥的
  20. idea spark java,IntelliJ Idea 搭建spark 开发环境

热门文章

  1. 有些钱,即便不脏,但也有毒。
  2. Java之日志打印占位符
  3. 3d可视化虚拟建模vr展示三维模型方案
  4. 英语日常用语900句(5)
  5. 关于“Connection refused: connect”错误
  6. c语言函数大全 pdf,C语言标准库函数大全.pdf
  7. 我是怎么在千氪上一天赚到5000氪金的?
  8. Android10 SystemUI状态栏网络图标流程分析
  9. 飞秒激光脉冲的产生过程
  10. 软件默认安装在C盘解决方案 eg:google浏览器、XMind脑图工具等