-
设计要求
- 设计一个基于mips指令集子集的单周期cpu。
- 子集指令如下:
addu,subu,add,and,or,slt,addi,addiu,andi,ori,lui ,lw,sw,beq,j,jal,jr。
- 所有指令都不考虑溢出。
- 最终实现的单周期处理器能够通过Modelsim功能仿真。
- 设计说明
- 单周期处理器由 datapath(数据通路)和 controller(控制器)组成。
-
- 数据通路由如下 module 组成:pc(程序计数器)、gpr (通用寄存器组)、alu(算术逻辑单元)、im(指令存储器)、dm(数据存储器)等。
-
- im:容量为4KB(32bit×1024 字)。
-
- dm:容量为 4KB(32bit×1024 字)。
- 如下是供你参考的数据通路示意图。
Figure1 数据通路参考图
- clock上升沿有效;reset低电平有效,异步复位;控制信号高电平有效。
- reset信号有效时,复位pc寄存器为0x0000_3000。
- 整个 project 必须采用模块化设计。
- 每个 module 由一个独立的 Verilog HDL 文件组成。
- 所有mux模块(包括不同位数、不同端口数等)都建模在一个mux.v中。
- 为使得代码更加清晰可读,建议多使用宏定义,并将宏定义组织在header.v中。
- ctrl.v文件中定义控制模块。
- s_cycle_cpu.v定义顶层模块。
- code.txt文本文件是需要加载到指令存储器中的机器代码。
- 模块定义要求
- 模块定义必须满足下表中的要求。
- 特别注意:im模块的输入pc为32位,但指令存储器只有4kB大小,所以取指令时只取pc的低12位作为地址。
不直接用32位pc值作为指令存储器地址的原因是mars工具设置的代码段的起始地址是0x0000_3000,但是仿真的时候存储器不能定义太大,否则仿真速度会很慢。所以取pc的低12位作为指令地址,这样CPU就是从指令存储器的地址0开始运行。(当然是在你的程序大小不超过4kB的前提下)
文件 |
模块定义要求 |
s_cycle_cpu.v |
//顶层模块 module s_cycle_cpu(clock,reset); input clock; //时钟信号 input reset; //复位信号 |
pc.v |
//pc模块 module pc(pc,clock,reset,npc); output [31:0] pc; //当前指令地址 input clock; input reset; input [31:0] npc; //下条指令地址 |
im.v |
//指令存储器 module im(instruction,pc); output [31:0] instruction; input [31:0] pc; //指令存储器只有4k大小,所以pc低12位地址有效(屏蔽掉高位) reg [31:0] ins_memory[1023:0]; //4kB指令存储器 |
gpr.v |
//通用寄存器 module gpr(a,b,clock,reg_write,num_write,rs,rt,data_write); output [31:0] a; //寄存器1的值 output [31:0] b; //寄存器2的值 input clock; input reg_write; //写使能信号 input [4:0] rs; //读寄存器1编号 input [4:0] rt; //读寄存器2编号 input [4:0] num_write; //写寄存器编号 input [31:0] data_write; //写数据 //32个寄存器 reg [31:0] gp_registers[31:0]; |
dm.v |
//数据存储器 module dm(data_out,clock,mem_write,address,data_in); output [31:0] data_out; input clock; input mem_write; input [31:0] address; input [31:0] data_in; //4KB数据存储器 reg [31:0] data_memory[1023:0]; |
alu.v |
//alu模块 module alu(c, a, b); output [31:0] c; input [31:0] a, b; |
-
实验过程
-
基本模块
- PC模块
-
基本模块
1实验思路
Pc记录当前正在执行的指令的地址,并且能正确在时钟周期上升沿指到下一条指令的地址,所以设计时只需要定义reg,并且在时钟周期上升沿正确赋值即可.
2具体实现
对模块进行仿真,在时钟周期上升沿,pc正确得到npc结果.
3遇到的问题及解决
注意本模块要求初值位置为00003000h,正确赋初值即可.
2. IM模块
1实验思路
本模块需要定义一个指令存储器,并且能根据输入的pc地址正确查找到指令.设计时,只需要根据pc的地址,正确输出指令内容即可.
2具体实现
3遇到的问题及解决
注意pc的位宽为32位,故地址的后两位永远是零,即四个字节为一个单位.
3. GPR模块
1实验思路
Gpr模块需要定义一个寄存器堆,该寄存器堆需要两个读出数据的端口,故需要两个数据地址的输入,还要能够实现数据写入,即需要数据 , 地址 , 写使能的输入信号,并且要求该模块能根据这些信号正确的读写. 设计时,需要根据读写信号正确读写即可.
2具体实现
对GPR进行仿真,观察波形图,在正确的写入下,陈宫根据rs,rt 读出了相应的值.
3遇到的问题及解决
在设计之初,使用了initial语句实现对零号寄存器的写操作,并且在后面通过判断决定不能再向0号寄存器内部写入数据 .
然而 后来学习得知,这种写法是无法综合的,故应该更换写法,在输出时判断,如果是零号,就直接输出零.
4实验反思及思考
在写实例模块时,不能使用initial语句,该语句只能在testbench中使用.
4. ALU模块
1实验思路
该模块需要根据不同的alu_op操作码的值,确定不同的操作,并且正确计算,得出结果.
2具体实现
经过仿真,可以得出正确的加法结果 c = a + i ;
3遇到的问题及解决
在该模块的编写过程中,
1.曾把无符号指令和有符号指令分开书写,后来学习得知,这样会综合出两个加法器,会增加电路复杂度,所以应该统一有符号和无符号的器件,只是在特殊标识位有区别.
2.曾不理解slt的作用,而不知道从何下手,后来上网查询了指令功能后,的值需要使用减法器来判断,相减后判断正负即可判断大小.
4实验反思及思考
要注意具体仿真出的电路的简洁性,写出优的代码.2.要注意对不同的功能进行聚类,统一写出一个模块,减少电路仿真量.
5. dm模块
1实验思路
该模块需要定义内存(即数据存储器),并且能正确进行读写操作. 编写时,只需要根据输入的地址和写使能信号正确读写,并输出结果即可.
2具体实现
3遇到的问题及解决
注意pc的大小是4字节,需要忽视掉地址后两位即可.
4实验反思及思考
该模块和寄存器类似,正确赋值即可.
-
- 能够执行addu指令的单周期CPU
1实验思路
初步只需要实现一个指令,故不需要太多的控制信号,只要建立正确的完整的数据通路即可.书写时,根据老师给出的数据通路,对每一条数据线进行定义,并且正确定义每个模块,并且将线接正确.
2具体实现
3遇到的问题及解决
1.实验时,reg_write这种定值的线不知道该如何接,最初先定义了一个寄存器,然后将寄存器接入.最后得知可以直接接入定值线,后来接入后可以正确运行. (调试过程仅在J指令时说明)
2.实验时,遇到过接口类型不对应的报错,发生错误的原因是线宽度定义错误或者缺失该线的定义,应该在一开始就注意避免这样的错误,先定义后使用.
4实验反思及思考
在具体的实例化过程中,如果使用位置对应进行接线,就需要按照一定的规律,以防止自己接线接乱.比如,严格按照前面输出信号,后面定义输入信号,并且对具体的线的定义要有意义,这样在接线时才能知晓自己在接哪里.
6. 能够执行R型指令的单周期CPU
1实验思路
要想增加R型指令,只需要增加一条aluop控制信号即可.目前为止,控制信号还很少,控制模块还很好写. 书写控制模块,需要根据instruction的op字段和funct字段确定执行的具体操作.
2具体实现
Ctrl单元:
顶层模块接线:(仅增加一个ctrl单元)
3遇到的问题及解决
在该模块的编写过程中,1.曾把无符号指令和有符号指令分开书写,后来学习得知,这样会综合出两个加法器,会增加电路复杂度,所以应该统一有符号和无符号的器件,只是在特殊标识位有区别. (调试过程仅在J指令时说明)
4实验反思及思考
1.接线时要先定义后使用.
2.使用位置关联进行接线,要明确每根线的意义,防止接错.
3.对ctrl单元的分类要精细.
- 添加I型指令
1实验思路
实现I型指令时,就需要用到立即数字段.由于立即数在指令中位宽不够,则需要进行扩展,所以要写一个扩展单元,该单元要根据输入的扩展控制信号确定是符号扩展还是零扩展. 2.由于指令变成了I型指令,则输入地址字段变成了rt字段,故需要一个选择器判断regfile的写寄存器地址是rs还是rt.同样,此时出现了立即数直接相加,因此ALU的另一个端口b也需要加一个选择器判断是经过扩展的立即数还是直接从寄存器里读出的值.
增加了这三个元器件,控制信号又需要三个,因此还要根据指令的op字段确定指令类型,从而细分这些控制信号的值.
2具体实现
1.扩展器
2.顶层模块新加的定义:(图片贴出了最终版本)
3.控制模块新加的判断语句: (图片贴出了最终版本)
3遇到的问题及解决
1.实验过程中,发现线接错,后来重接. (调试过程仅在J指令时说明)
2.随着控制信号的增多,如何分类成了一个难题,通过回忆数字逻辑相关知识,经过分析,发现不同指令之间的区别,比如R与I的区分只需要判断op==5’h00即可.仅使用了几个选择器就将指令大体分类完成.
4实验反思及思考
2. 添加MEM型指令
1实验思路
增加MEM指令需要增加一个数据存储器,并且在读出数据存储器时加一个选择器判断是alu的输出还是数据存储器的输出.
因此还需要增加一个控制信号来控制数据读出端口.
2具体实现
1.顶层模块:
2.ctrl单元:
3遇到的问题及解决
1.在该模块,遇到读出和写入不正确,后来调试发现控制信号不对,最后改正(调试过程仅在J指令时说明)
4实验反思及思考
1.随着控制信号的增多,对不同的分类要明确细致.
2.在分出新的类别时,要把旧的已存在的控制信号定义清楚.否则延续旧的控制信号值可能导致出错.
3.随着数据线的增多,接线要明确意义,防止混乱.
3. 添加J型指令
1实验思路
按照新的指令要求,需要更改alu能输出zero信号.还要增加几个选择器来判断.数据的组合可以直接用线来完成.具体实现如下:
2具体实现
1.alu的增加:
2.过程中使用的{}的定义:
3.顶层模块的增加:
4.控制模块的增加:
5.控制模块的总体分类方式:
3遇到的问题及解决
实验中,总是遇到仿真到一半直接跳转到最后的情况,反复排查后发现还是自己的控制信号有问题.
排查过程:
1.使用mars单步执行指令,判断寄存器情况;
2.使用Modelsim仿真cpu,一个一个周期看哪个信号线出错,
3.最后发现指令总是在3028h的位置直接跳转到304c(应该是302c),观察mars发现是beq指令,故判断是s_npc选择了错误的端口,是控制信号写错导致.最后更改,正确.
4.经过调试,最后得到正确的仿真结果.
最终版数据流图:
- 实验总结
1.实验过程中,经常犯一些低级错误,原因是对verilog 的语法不熟悉,写出错误代码(比如把组合逻辑块写入时序逻辑内,此时还需要额外定义reg , 在非tb的模块中使用initial语句等)通过实验强化了自己的编码能力.
2. 随着控制信号的增多,对不同的分类要明确细致.在分出新的类别时,要把旧的已存在的控制信号定义清楚.否则延续旧的控制信号值可能导致出错.
3. 随着数据线的增多,接线要明确意义,防止混乱.
4.实验伊始,还不知mars的具体功能和modelsim 可以看具体的寄存器值,可以看定义的每根线的值,后来经过老师的讲解和与同学的讨论,学会了这两个辅助工具的绝妙用处.mars单步执行具体观察寄存器值和执行过程,modelsim将时间设置为100ns,单步执行,具体观察线的值和寄存器的值,判断出错位置. 通过学习这些软件的使用,大大加快了我差错的速度和编写的能力.文章来源:https://www.toymoban.com/news/detail-443697.html
项目源码
TODO文章来源地址https://www.toymoban.com/news/detail-443697.html
到了这里,关于单周期CPU设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!