目录
Verilog与vivado基础
1.1设计要求
熟悉并掌握 Verilog HDL 与 vivado 的使用[1]。
请使用 Verilog 完成4选1 多路选择器(MUX) 、4 位全加器、8 位比较器、74138 译码器等模块设计,然后编写测试文件进行仿真验证。
1.2方案设计
1.2.1 4选1多路选择器(MUX)的设计
多路选择器(MUX)是一种在多路数据传送过程中,能够根据需要将其中任意一路选出来的电路,其原理图如图 1.1所示。
多路选择器根据输入的select信号,选择输入信号并输出,真值表如表 1.1所示。
表 1.1 4 选 1 多路选择器的真值表
数据输入 | 输出 | ||||
select | in1 | in2 | in3 | in4 | out |
00 | in1 | x | x | x | in1 |
01 | x | in2 | x | x | in2 |
10 | x | x | in3 | x | in3 |
11 | x | x | x | in4 | in4 |
MUX模块的功能描述如表 1.2所示,4选1多路选择器由4位输入信号和2位选择信号作为输入,一个4位信号作为输出。
输入 | 4位输入信号in1、in2、in3、in4和2位选择信号select |
输出 | 4位输出信号out |
功能 | 根据选择信号select的值吧相应输入信号赋值给out输出 |
- 4选1多路选择器的Verilog关键代码实现
在模块的实现上,通过检测选择信号sel,判断信号类型(二进制数),未检测到信号则输出默认值,即未定义4'bx,从而输出相应的输入信号,模块的verilog 代码如下:
module mux4to1( input wire [3:0] in1, in2, in3, in4, input wire [1:0] sel, output reg [3:0] out); always @* begin case(sel) 2'b00: out = in1; 2'b01: out = in2; 2'b10: out = in3; 2'b11: out = in4; default: out = 4'bx; endcase end endmodule |
- 4选1多路选择器的测试文件(TestBench)关键代码实现
开始时初始化四个输入口,然后在每隔十个单位的时间内按照字典序依次改变sel值,具体代码如下:
module simmux4to1; reg [3:0] in1, in2, in3, in4; reg[1:0] sel; wire [3:0] out; initial begin in1 = 4'b0001; in2 =4'b0011; in3 = 4'b0111; in4 = 4'b1111; sel = 2'b00; #10 sel = 2'b01; #10 sel = 2'b10; #10 sel= 2'b11; #10 $stop; end mux4to1 uut( .in1(in1),.in2(in2),.in3(in3),.in4(in4),.sel(sel), .out(out) ); endmodule |
1.2.2四位全加器的设计
![]() |
图 1.2 4 位全加器的原理图 |
全加器能够实现两个操作数的加法运算,而四位全加器能够进行两个四位二进制操作数的加法运算,其原理图如图 1.2所示。
四位全加器根据输入的操作数和进位信息输出加法运算后的结果和相应的进位信息,由于四位全加器涉及的操作数位数过多,相应的真值表较为庞大,限于篇幅,在此不再给出其真值表。
表 1.3 4 选 1 多路选择器的真值表
输入 | 4位输入信号in1、in2和1位进位信号c_1 |
输出 | 4位输出信号sum和1位进位信号c1 |
功能 | 4位二进制数的加法运算 |
具体而言,如所描述的,四位全加器输入信号有两个四位的信号,表示两个四位二进制操作数,一位进位信号,表示低位的进位;输出信号为一个四位的信号,表示运算后的四位二进制数结果,以及一位的进位信号,表示运算结果是否产生进位,即超过四位二进制数的范围。
- 四位全加器的Verilog 关键代码实现
四位全加器虽然在硬件的设计上较为复杂,需要涉及半加器等部件,但是在逻辑的实现上思路较为清晰,在Verilog 中利用判断语句即可实现,其中由于sum是四位的,当加法的结果超过了四位的话,就会溢出,此时只要判断是否溢出,如果溢出则使c1被置为1,同时sum减16。
module adder( input wire[3:0] cin1,cin2, input wire c_1, output reg[3:0] sum, output reg c1, ); initial c_1 = 0; end always @* sum = cin1 + cin2+c_1; if (sum >= 16) sum = sum - 16;c_1 = 1; endmodule |
- 四位全加器的测试文件(TestBench)关键代码描述
每间隔5个时间单位为全加器的输入信号赋值,根据加法运算的逻辑即可判断程序设计的正确性。
module simadder(); wire[3:0] sum;wire c1; reg[3:0] cin1,cin2;reg c_1; initial begin #0 cin1 = 4'b0001; cin2 = 4'b0010; c_1 = 1'b0; #5 cin1 = 4'b1010; cin2 =4'b1010; c_1 = 1'b0; #5 cin1 = 4'b0010; cin2= 4'b1010; c_1 = 1'b0; #5 cin1 =4'b1011; cin2 =4'b1100; c_1 = 1'b0; #5 cin1 = 4'b0101; cin2= 4'b1001; c_1 = 1'b1; #5 cin1 = 4'b0101; cin2 = 4'b1100; c_1 = 1'b1; #5 cin1 = 4'b0111; cin2 =4'b1100; c_1 = 1'b1; #5 cin1 = 4'b1011; cin2 =4'b1111; c_1 = 1'b1; #5$stop; end adder uut(.cin1(cin1),.cin2(cin2),.c_1(c_1),.sum(sum),.c1(c1)); endmodule |
1.2.3 8位比较器的设计(图 1.3)
![]() |
图 1.3 8 位比较器的原理图
比较器能够实现对输入的操作数进行大小比较,并输出比较的结果。
8位比较器能够实现对8位二进制操作数的比较(表 1.4)。
输入 | 8位输入信号a、b |
输出 | 1位输出信号equal |
功能 | 比较两个输入信号是否相等 |
8位比较器能够比较输入的两个操作数的大小关系,由此可以定义多个输出信号分别表示相应的大小关系,在此通过一个输出信号判断两个操作数是否相等。
- 8位比较器的 Verilog 关键代码实现
在软件描述上,8位比较器只需通过判断语句判断输入信号的大小关系,如果相等则输出高电平,反之低电平。
module compare_8bit(equal, a, b); input wire [7:0]a, b; output reg equal; reg equal; always @(a or b) if(a == b) equal = 1; else equal = 0; endmodule |
- 8位比较器的测试文件(TestBench)关键代码描述
以十个时间单位为间隔分别为两个输入信号同时赋值为满足小于、等于、大于关系的操作数,其中涉及到了<=的赋值操作,在此处实现同时赋值的效果,上文中使用=进行赋值在当时的语句环境下的操作实现与采用<=赋值并未有很大的区别。
module siCompare; reg [7:0]a, b; wire equal; initial begin a<=4'b0001; b<=4'b0011; #10a<= 4'b0011; b<= 4'b0011;#10 a<= 4'b0011; b<=4'b0001; #10 $stop; end compare_8bit uut( .a(a), .b(b), .equal(equal) ); endmodule |
1.2.4 74138译码器的设计
![]() |
图 1.4 74138译码器的原理图
译码器与选择器类似,通过识别输入的二进制编码来实现相应端口的输出,从而实现译码的功能(图 1.4)。
74138译码器则能够识别3位的二进制编码,最终能从八个输出口中输出一个对应的十进制译码结果。
输入 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |||||
0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |||||
1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | |||||
2 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | |||||
3 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | |||||
4 5 6 7 | 1 1 1 1 | 1 1 1 1 | 1 1 1 1 | 1 1 1 1 | 0 1 1 1 | 1 0 1 1 | 1 1 0 1 | 1 1 1 0 |
对应真值表如表 1.5所示。
输入 | 3位二进制输入信号in |
输出 | 8位输出信号out |
功能 | 完成二进制转十进制的译码 |
描述表 1.6给出了74138译码器的输入输出信号种类与位数,不难理解,74138译码器需要3位二进制数作为输入,8个信号位作为输出,以实现二进制译码位十进制的效果。
(1) 74138译码器的Verilog关键代码实现
通过for循环语句遍历每一个输出口,如果输出口的编号与输入的二进制数大小相同,则输出低电平,否则输出高电平。
module ecoder3to8( input [2:0] in, output reg [7:0] out); integer k; always @* begin out = 8'b11111111; for(k = 0;k<= 7;k =k+1) if (in == k) out[k]= 0; else out[k]= 1; end endmodule |
(2) 74138译码器的测试文件(TestBench)关键代码描述
以10个单位的间隔时间为输入口按照字典顺序赋值,以观察最终输出的译码后的波形。
module simecoder3to8; reg [2:0] in; wire [7:0] out; initial begin #10 in = 3'b000; #10 in = 3'b001; #10 in = 3'b010; #10 in = 3'b011; #10 in = 3'b100; #10 in = 3'b101; #10 in = 3'b110; #10 in = 3'b111; #10 $stop; end ecoder3to8 uut( .in(in), .out(out) ); endmodule |
1.3实验步骤
1.3.1 vivado 的安装
vivado的安装过程中,一直按照默认的安装环节与安装配置进行安装,除了软件庞大的占用空间外,并未出现太多的问题,其中的大致流程与注意事项如下:
(1)首先,如果安装的 vivado并未最新的版本时,软件会提示是否使用最新的
安装版本,为节省时间,在此可以跳过,继续使用当前安装包;
(2)在安装的过程中最好关闭杀毒软件,以免出现重要文件的丢失;
1.3.2ModelSim的安装
由于vivado的体积过于庞大,功能过于繁杂,对于初学者不太友好。所以我的实验首先是在ModelSim的环境下编写完成并运行通过。在掌握了基本的硬件设计知识和技巧之后。再下载、安装vivado在vivado的环境下进行调试和完善。自我感觉,这样的学习的效果较好。
安装应注意以下两点:
- 最后一步要点否,不然会导致电脑蓝屏重启
- patch_dll执行的时候直接运行
1.3.3 vivado的使用和基本电路的实现
- 选择Quick Start中的Create Project创建项目设置文件名和路径,然后勾选对钩勾选RTL Project因为没有已有文件,所以选择Creat File,如图 1.5
![]() |
图 1.5 文件的创建
- 设定好文件名,选择语言为Verilog,选择文件路径点击OK,可以看到文件已经添加到项目当中了,然后单击Next,接下来会提示加入约束文件,可以跳过这一步,直接点击Next,如图 1.6
![]() |
图 1.6 文件的创建
- 然后需要选择实验所用的芯片型号或者硬件平台如图 1.7
![]() |
- 等待配置完成后会提示定义模块端口,Port Name 表示端口名称,Direction 表示端口方向,可以选择input、output 或 inout,MSB 表示信号最高位,LSB 表示信号最低位,如图 1.8。
![]() |
- 可以在Source模块看到源文件,双击就可以看到代码窗口,如图 1.9。
![]() |
![]() |
图 1.10 测试代码的添加
添加测试代码点击“+ ” ,然后按照类似源代码的添加方式进行添加,如图 1.10。
1.4故障与调试
1.4.1故障1
故障现象:
如图 1.11。
![]() |
图 1.11安装故障
原因分析:
安装过程可能出现问题
解决方案:
这时点击Retry,如果还是不行的话多点几次,还是不行的话,建议去官网下载
1.4.2故障2
故障现象:
正常流程到最后安装弹出了窗口然后就停止安装
原因分析:
安装路径有中文
安装路径选择非系统盘
解决方案:
安装路径全部设置为英文
并且关闭所有杀毒软件
1.4.3故障3
需要注意的是,最后结果可能是16进制的结果,这个时候我们需要自己把他转换成二进制的结果,如图 1.12所示:
![]() |
1.5仿真及分析
1.2中测试文件的编写已经给出了逻辑电路的输入信号配置,在此以表格的形式予以汇总,并给出相应的结果和波形图(图 1.13)。
1.5.1 4选1多路选择器(MUX)的仿真及分析
![]() |
从波形结果中不难看出,当sel信号被设置后,与其相对应的二进制数被选择,并输出最终的结果,实现了4选1的选择效果(表 1.7)。
# | in1 | in2 | in3 | in4 | select | out |
/ | 0001 | 0011 | 0111 | 1111 | 01 | 0001 |
10 | 0001 | 0011 | 0111 | 1111 | 01 | 0011 |
10 | 0001 | 0011 | 0111 | 1111 | 10 | 0111 |
10 | 0001 | 0011 | 0111 | 1111 | 11 | 1111 |
1.5.2 四位全加器的仿真及分析(图 1.14)
![]() |
可以看出,实验结果符合预期,输出信号为两个输入信号之和,且当两个输入信号之和超过15时,会产生进位信号,同时输出正确的数值。由此可见4位全加器的设计无误(表 1.8)。
# | cin1 | cin2 | c_1 | sum | c1 |
/ | 1 | 2 | 0 | 3 | 0 |
5 | 10 | 10 | 0 | 4 | 1 |
5 | 2 | 10 | 0 | 12 | 0 |
5 5 5 5 5 | 11 5 5 7 11 | 12 9 12 12 15 | 0 1 1 1 1 | 7 15 2 4 1 | 1 0 1 1 1 |
1.5.3 8位比较器的仿真及分析
![]() |
图 1.15 8位比较器的波形图
从仿真图像中可以看出结果和预期的相同,设计正确(表 1.9)。
# | a | b | equal |
/ | 01 | 03 | 0 |
10 | 03 | 03 | 1 |
10 | 03 | 01 | 0 |
![]() |
图 1.16 74138译码器波形图
1.5.4 74138译码器的仿真及分析
可以看到,当输入信号依次从0到7变换时,输出信号的电平也一次发生变换,进行译码后,对应的输出口会被置为低电平(表 1.10)。
# | in | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |||||
/ | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |||||
10 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | |||||
10 | 2 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | |||||
10 | 3 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | |||||
10 10 10 10 | 4 5 6 7 | 1 1 1 1 | 1 1 1 1 | 1 1 1 1 | 1 1 1 1 | 0 1 1 1 | 1 0 1 1 | 1 1 0 1 | 1 1 1 0 |
单周期CPU设计与实现——单指令CPU实验
2.1设计要求
通过设计并实现支持一条指令的CPU,理解和掌握CPU设计的基本原理和过程。
2.2方案设计
本次实验按照实验指导书(计算机组成原理实验教程 -2019年)进一步实验的要求,在单指令CPU实验的基础上,添加立即数加法指令ADDI、存数指令STA、取数指令LDA,为了增加实验难度,本次实验将加法指令中的一个操作数改为从存储器中获取,另一个为寄存器R1,同时还增加了减法指令SUB。
单周期 CPU 是指所有指令均在一个时钟周期内完成的 CPU。CPU 由数据通路及其控 制部件两部分构成,因而要完成一个支持若干条指令 CPU 的设计,需要依次完成以下两件事:
1)根据指令功能和格式设计 CPU 的数据通路;
2)根据指令功能和数据通路设计控制部件。
根据功能和格式完成 CPU 的数据通路设计
本实验需要设计的 CPU 只需要支持一条加法指令,而该指令的功能是在一个时钟周期内从寄存器组中 r1 和 r2 中取出两个操作数,然后送到 ALU 进行加法运算,最后把计算结果保存到 r1 寄存器中。下图给出了改加法指令的数据通路图(图 2.1)。
此外,还需要确定各个部件的位数,为了简单起见,我们假设目标 CPU 的机器字长、存储字长和指令字长相等均为 16 位,存储单元个数假设为 256,按字寻址,并取 PC 位数为 8。
根据指令功能、数据通路完成控制单元的设计
控制单元的功能是为当前要执行的指令产生微操作命令从而完成该指令的执行。为了能够完成加法指令的执行,结合图 2.1,控制单元需要在取出指令后根据指令操作码(本例中是 加法指令),控制 ALU(参考实验二)做加法(通过给 alu_op 信号线相应赋值),并把结果 写回寄存器组(参考实验三)中(通过给 wr_en 赋值为 true)。图 2.2给出了整合控制单元后 目标 CPU 的原理图,系统时钟信号也已标注。
2.2.1指令设计
本次实验支持5条指令,分别为:
2.2.2 CPU的数据通路和控制单元的设计
由于本次实验涉及多条指令,在设计的过程中需根据每条指令设计其数据通路,并采用合并的方式构建支持所有指令的CPU 数据通路,然后在进行控制单元的设计。
对于加法指令,该指令需要从寄存器组中Rl寄存器里取出操作数,并根据指令中给出的地址到存储器中取一个操作数,然后送到ALU进行加法运算,最后把计算结果保存到Rl寄存器中。
对于立即数加法指令,无需访问存储器,直接在寄存器组中Rl寄存器里取出操作数,并和指令中给出的立即数一起送到ALU进行加法运算,最后把计算结果保存到Rl寄存器中;对于减法指令,与加法指令类似,送入ALU后进行减法运算,并送回R1寄存器中;对于存数指令,需要从寄存器组中R1寄存器里取出操作数,并根据指令中给出的地址送到存储器中;对于取数指令,与取数指令相反,需要根据指令中给出的地址从存储器中取出相应的操作数,并送到指定的寄存器中。
根据定义的指令功能,控制单元需要分别发出寄存器写信号regw、存储器写信号
2.4 寄存器结构图
memw、ALU操作控制信号 alu_op等多个信号。根据上述设计的数据通路和控制信号,将其与系统时钟信号整合,即可得到支持五条指令CPU的原理图,如图 2.3所示。
2.2.3 PC 模块的设计
输入 | 时钟信号clk、重置信号rst |
输出 | 指令地址 pc(8位) |
功能 | 每个始终上升沿PC的值自动加1,并输出 |
|
图 2.5 时钟信号安置
由功能描述表 2.1可知,PC模块的功能实现较为简单,只需根据输入的时钟信号不断加1即可,当遇到复位信号后则从头开始加1。其Verilog 代码如下:
module PC( input clk, input rst, output reg [7:0] pc ); initial pc=8'd0; always@(posedge clk) begin begin if(rst==1) pc<=8'd0; else pc<=pc+1; end end endmodule |
2.2.4 指令存储器模块的设计
输入 | 8 位指令地址 Addr,时钟信号clk |
输出 | 16位指令 Ins |
功能 | 每存放待执行的指令,并根据地址输出指令 |
表 2.2 指令寄存器模块功能描述
根据指令的功能设计,我们采用16位的指令,由于规定输入的指令地址为8位,则在指令存储器中可以同时存放256条指令,因此内部的存储单元在定义的时候为256
个。在此使用多条指令以满足测试的需要。
详细 Verilog 代码如下:
module ROM ( input wire[7:0] addr, input wire clk, output wire [15:0] Ins ); reg[15:0] units[255:0]; initial begin units[0] = 16'h0000; units[1] = 16'h0120; units[2] = 16'h1120; units[3] = 16'h2221; units[4] = 16'h3369; units[5] = 16'h4498; end assign Ins = units[addr]; endmodule |
2.2.5 寄存器堆模块的设计
输入 | 操作数地址addr(4位)、时序信号clk,ALU输入in(16位)、输出信号out(16位)、读写控制信号regw(1位) |
输出 | 输出信号(16位) |
功能 | 在读写控制信号regw和时序信号clk的控制下,在固定的周期存放ALU计算所需要的数据和计算结果 |
表 2.3 寄存器堆模块功能描述
在上述指令定义时,寄存器的地址是3位,即寄存器堆中含有16个,寄存器的
定义和RAM基本相同,在clk和读写控制信号的控制下进行输入和输出。具体的Verilog代码如下:
图 2.6 RAM架构
module REG( input wire [3:0] addr, input wire regw, clk, input wire [15:0] in, output wire [15:0] out ); reg[15:0] units[15:0]; initial begin units[0] <= 16'h0000; units[1] <= 16'h0020; units[2] <= 16'h0021; units[3] <= 16'h0069; units[4] <= 16'h0098; end
always@(*) begin if(regw == 0 ) units[addr] <= in;
end assign out = regw ? units[addr]:0; endmodule |
2.2.6 数据存储器的设计
输入 | 操作数地址addr(4位)、时序信号clk,ALU输入in(16位)、输出信号out(16位)、读写控制信号regw(1位) |
输出 | 输出信号(16位) |
功能 | 在读写控制信号regw和时序信号clk的控制下,在固定的周期存放ALU计算所需要的数据和计算结果 |
表 2.4 RAM模块功能描述
设计和寄存器堆基本相同,具有输入和输出信号,并在存储单元内存放了将要使用的数据,工作原理是要在控制器cu的控制下在一个周期内发出两种信号实现在一个周期内的存取。
module RAM ( input wire[7:0] addr, input wire clk, input wire we, input wire[15:0] in, output wire[15:0] out ); reg[15:0] units[255:0]; initial begin units[0] <=16'h0000; units[32] <= 16'h0020; units[33] <= 16'h0010; units[105] <= 16'h0020; units[152] <= 16'h0000; end always @(clk) begin if(we == 0) units[addr] <= in; end assign out = we ? units[addr]:0; endmodule |
2.2.7 ALU 模块的设计
输入 | 操作数in1(16位)、in2(16位)、imm(8位)、时序信号clk(1位)、操作码alu_op(4位) |
输出 | 输出信号result(16位) |
功能 | 在操作码alu_op的控制下,在clk的下降沿进行计算,共5种操作。 |
表 2.5 ALU模块功能描述
在clk的下降沿完成计算,可以保证存储器在高电平时把数据取入ALU中,操作码有四位根据操作码的不同进行不同的操作,并且由于加立即数的指令,指令的操作数只有8位少于16位,所以要对立即数进行拓展。
module ALU ( input wire clk, input wire[3:0] alu_op, input wire[15:0] in1, in2, input wire[7:0] imm, output reg[15:0] result ); initial begin result = 0; end always@(negedge clk)begin case (alu_op) 4'b0000: result = in1+in2; 4'b0001: result = in1+{{8{imm[7]}},imm}; 4'b0010: result = in1-in2; 4'b0011: result = in2; 4'b0100: result = in1; default: result = 16'hffff; endcase end endmodule |
2.2.8 控制单元的设计
表 2.6 控制单元模功能描述
输入 | 操作码alu_op(4位) |
输出 | 输出信号result(16位) |
功能 | 在操作码alu_op的控制下,在clk的下降沿进行计算,共5种操作。 |
根据操作法发出存储器的控制信号,在ALU进行计算的时候控制存储器进行存取,将运算的输入从存储器和寄存器中读入,并将结果从ALU中存入存储器和寄存器。
module CU( input wire clk, input wire [3:0] cmd, output wire regw, memw ); reg [1:0]units; initial begin units = 0; end always@(*) begin case(cmd) 4'b0000: begin if(clk == 1) units =2'b11;//ADD else units =2'b10;//ADD end 4'b0001: begin if(clk == 1) units =2'b11;//ADD else units =2'b10;//ADD end 4'b0010: begin if (clk == 1) units =2'b11;//SUB else units =8'b10;//SUB end 4'b0011: begin if (clk == 1) units =2'b11;// else units =8'b10;// end//LDA 4'b0100: begin if (clk == 1) units =2'b11;// else units =8'b01;// end//LDA//STA default: units = 2'b11; endcase end assign regw = units[0]; assign memw = units[1]; endmodule |
2.2.9 CPU顶层文件封装实现
定义了各个模块后,知道了各个模块的输入与输出,顶层文件的编写变得简单,只需根据设计好的数据通路进行连线即可,此外还需要输入时钟信号和复位信号。
module CPU( input clk, input rst ); wire [7:0] pc; wire [15:0] in1, in2; wire [15:0] cmd; wire regw; wire memw; wire [15:0] result;
PC pc1(.clk(clk),.rst(rst), .pc(pc)); CU cu(.cmd(cmd[15:12]), .regw(regw), .memw(memw), .clk(clk)); ROM rom(.addr(pc), .Ins(cmd), .clk(clk)); RAM ram(.clk(clk),. we(memw), .addr(cmd[7:0]), .out(in2), .in(result)); ALU alu(.alu_op(cmd[15:12]), .in1(in1), .in2(in2), .imm(cmd[7:0]), .result(result), .clk(clk)); REG reg1(.addr(cmd[11:8]), .regw(regw), .clk(clk), .in(result), .out(in1)); endmodule |
2.2.10 测试文件(TestBench)关键代码描述
对于整个CPU而言,只有时钟信号和复位信号两种,因此只需要设置时钟信号和复位信号即可。具体关键代码如下:
module simCPU; reg clk, rst; initial begin clk =1;rst =1; #5 rst = 0; clk = 0; forever #5 clk = ~clk; end
CPU uut( .clk(clk), .rst(rst)); endmodule |
2.3实验步骤
- 确定CPU的指令和指令格式,本次实验采用五条指令;
- 设计各个指令的数据通路然后合并,得到整个合并,得到整个CPU的数据通路;
- 分析指令需要的模块,给出控制单元的控制信号;
- 根据数据通路和控制信号得到CPU的原理图;
- 根据各个模块的功能描述,自底向上设计模块;
- 进行顶层封装;
- 测试CPU。
连线说明:
uM单元:S0、S1、S2、S3(JP1) | —— | ALU单元:S0、S1、S2、S3(JP18) |
uM单元:wA、wB、rALU、CN_I(JP4) | —— | ALU单元:wA、wB、rALU、CN_I(JP19) |
uM单元:rRi、wRi | —— | ALU单元:rR0、wR0 |
uM单元:rRdi | ALU单元:rRd0 | |
uM单元:wIR(JP10) | —— | IR单元:wIR(JP32) |
uM单元:M_nIO、nRD、nWR、nINTA(JP2) | —— | CBus单元:M_nIO、nRD、nWR、nINTA(JP42) |
ALU单元:IN0..7(JP22) | —— | iDBus单元:iD0..7(JP38) |
ALU单元:ALU_D0..ALU_D7(JP25) | —— | iDBus单元:iD0..7(JP37) |
ALU单元:iD0..iD7(JP24) | —— | iDBus单元:iD0..7(JP36) |
IR单元:D0..D7(JP35) | —— | iDBus单元:iD0..7(JP41) |
IR单元:IR_A0..IR_A7(JP30) | —— | uPC单元:IR_A0..IR_A7(JP12) |
IN单元:IN0..IN7(JP101) | —— | DBus单元:D0..D7(JP52) |
IN单元:nCS、nRD(JP100) | —— | CBus单元:IO_nCE0、nIO_RD(JP49) |
OUT单元:nCS、nWR(JP68) | —— | CBus单元:IO_nCE0、nIO_WR(JP48) |
OUT单元:I0..I7(JP69) | —— | DBus单元:D0..D7(JP54) |
OUT单元:OUT0..OUT7(JP70) | —— | 扩展区单元:JP67 |
MEM单元:A0..A7(JP72) | —— | ABus单元:A00..A07(JP56) |
MEM单元:D0..D7(JP73) | —— | DBus单元:D0..D7(JP53) |
MEM单元:M_nRD、M_nWR(JP71) | —— | CBus单元:nM_RD、nM_WR(JP44) |
uM单元:nPCOE、wPC、rPC、PC+1(JP3) | —— | PC单元:nPCOE、wPC、rPC、PC+1(JP16) |
uM单元:nMAROE、wMAR(JP9) | —— | MAR单元:nMAROE、wMAR(JP13) |
ABus单元:A00..A07(JP55) | —— | 扩展区单元:JP63 |
DBus单元:D0..D7(JP105) | —— | 扩展区单元:JP65 |
打开实验仪电源,按CON单元的nRST按键,复位
对实验程序的写入、校验、运行,分手动、联机两种方式。
(一) 手动方式
(1) 手动写入微程序
①如果EXEC键上方指示灯熄灭,表示实验仪在uM、MEM编辑状态,转②;否则,按一次EXEC键,使EXEC键上方指示灯熄灭
②如果uM/M键上方指示灯亮,表示处于uM编辑状态,直接转③;否则,按uM/M键,使uM/M键上方指示灯点亮
③IN单元开关给出uM的首地址,按一次ADDR键,地址写入uPC
④IN单元开关给出该控存单元数据的低八位,按一次nWR键,将IN单元的数据写到该单元的低8位;按一次+1键,IN单元给出中八位数据,按一次nWR键,将IN单元的数据写到该单元的中8位;按一次+1键,IN单元给出高八位数据,按一次nWR键,将IN单元的数据写到该单元的高8位;按一次+1键,IN单元给出最高八位数据,按一次nWR键,将IN单元的数据写到该单元的最高8位;按一次+1键,uPC+1,准备写下一单元
⑤重复③、④步,将微代码表的数据写入6116/2816芯片中。
(2) 手动校验微程序
①确认EXEC键上方指示灯熄灭;uM/M键上方指示灯点亮
②IN单元开关给出uM的首地址,按一次ADDR键,地址写入uPC
③按一次nRD键,uM单元的指示灯uM7-uM0显示该单元的低八位数据;按一次+1键,按一次nRD键,uM单元的指示灯uM15-uM8显示该单元的中八位数据;按一次+1键,按一次nRD键,uM单元的指示灯uM23-uM16显示该单元的高八位数据;按一次+1键,按一次nRD键,uM单元的指示灯uM31-uM24显示该单元的最高八位数据;按一次+1键,uPC+1,准备读下一单元
④重复②、③步,完成对微代码的校验。如校验的微指令出错,则返回输入操作,修改该单元的数据后再进行校验,直至确认输入的微代码全部准确无误为止,完成对微指令的输入。
(3) 手动写入机器程序
①确认EXEC键上方指示灯熄灭;uM/M键上方指示灯熄灭,表示处于MEM编辑状态。
②IN单元开关给出MEM的首地址,按一次ADDR键,地址写入到地址总线A0..A7,在扩展区单元的左边二个数码管上显示当前地址(高位在前)。
③IN单元给出该单元应写入的数据,按一次nWR键,将IN单元的数据写到该MEM单元中去;在按nWR键时,扩展区单元的右边二个数码管上显示写入数据(高位在前);按一次+1键,地址+1,准备写下一单元
④重复②、③步,将机器程序写入存贮器MEM单元的6116芯片中。
(4) 手动校验机器程序
①确认EXEC键上方指示灯熄灭;uM/M键上方指示灯熄灭,表示处于MEM编辑状态。
②IN单元开关给出MEM的首地址,按一次ADDR键,地址写入到地址总线A0..A7,在扩展区单元的左边二个数码管上显示当前地址(高位在前)。
③按住nRD键,从MEM中读出当前地址对应的单元中的数据到数据总线DBus上,扩展区单元的右边二个数码管上显示该数据(高位在前);按一次+1键,地址+1,准备读下一单元
④重复②、③步,完成对机器码的校验。如果校验出错,重新写入、校验,直至确认机器码的输入无误为止。
(5) 本机运行
(1)按CON单元的nRST键一次,复位实验仪;如果EXEC键上方指示灯不亮,请按一次EXEC键,点亮指示灯,表示实验仪在运行状态。
(2)在下边的各步操作中,按CON单元的uSTEP按键时,体会系统在T1、T2、T3节拍中各做的工作;如果按一次STEP键,即可单步运行一条微指令;对照微程序流程图,观察微地址uPC指示灯是否与流程一致;观察DBus总线、ABus总线,对照数据通路图,分析总线上数据是否正确。
当模型机每次执行到JMP指令时,观察OUT单元显示的数据是否为IN单元数据的2倍;改变IN单元的值,使用iSTEP键,按一次,执行一条完整的机器指令,循环运行,从OUT单元显示的数值判别程序执行是否正确。
(二) 联机方式
(1) 文件格式
星研集成环境软件要求微程序、机器程序文件的扩展名为PuM,微程序和机器程序的格式如下:
图 2.7 微程序和机器程序格式
分号“;”是注释符,从分号开始到该行结束,不影响微程序、机器程序的生成。
(2) 星研软件的使用
1、运行星研集成环境软件
2、新建源文件
下面我们建立源文件,执行 [主菜单 » 文件 » 新建],(或者点击图标正在上传…重新上传取消)打开窗口。
首先选择存放源文件的目录,输入文件名,注意:文件名后缀必须是“PuM”。本实例文件名为sCPU.PuM。
按“确定”即可。然后即出现文件编辑窗口。
输入源程序,本实例的源程序如下:
$P | 00 | A8 | ;START:IN R0 | 从IN单元读入数据送 R0 |
$P | 01 | 00 | ; ADD R0,R0 | R0 + R0 -> R0 |
$P | 02 | AC | ; OUT R0 | R0的值送OUT单元显示 |
$P | 03 | E7 | JMP START | 跳转至00H地址 |
$P | 04 | 00 | ||
$uM | 00 | 6F0ED001 | ;取指(MEM->IR)、PC+1 | |
$uM | 08 | 3B0AF009 | ;R0->A, uPC=09 | |
$uM | 09 | 5D0AF00A | ;R0->B, uPC=0A | |
$uM | 0A | 7E4AF800 | ;A+B->R0, uPC=0 | |
$uM | 14 | 7F49C000 | ;IN->R0, uPC=0 | |
$uM | 16 | 7D09A000 | ;R0->OUT, uPC=0 | |
$uM | 4E | 7F8AD000 | ;MEM->PC, uPC=0 |
表 2.7 源程序
3.编译文件
首先选择一个PuM源文件,然后可以编译文件了。对文件编译,如果没有错误,生成代码文件: 微程序代码文件扩展名为“uDob”、程序代码文件扩展名为“Dob”。编译、连接文件的方法有如下二种:(1)使用[ 主菜单 » 项目 » 编译、连接 ]或[主菜单 » 项目 » 重新编译、连接 ]”。(2)点击图标来“编译、连接”或“重新编译连接”。
“编译连接”与“重新编译、连接”区别:“重新编译、连接”不管源文件是否修改,对源文件编译,如果没有错误,生成代码文件(uDob、Dob文件)。编译、连接过程中产生的信息显示在信息窗的“建立”视中。编译没有错误的信息如下:
若有错误则出现如下信息框:
有错误、警告信息,用鼠标左键双击错误、警告信息或将光标移到错误、警告信息上,回车,系统自动打开对应的出错文件,并定位于出错行上。
这时用户可以作相应的修改,直到编译文件通过。
如果编译、连接正确后,可以开始调试程序。进入调试状态方法有:
执行[ 主菜单 » 运行 » 进入调试状态];点击工具条的
执行[ 主菜单 » 运行 » 装载DOB、HEX、BIN文件]
在信息窗的“装载”视中,显示装载的代码文件,装载的字节数,装载完毕后,显示启始地址,结束地址。这种船坞化的窗口比通常的窗口显示的内容更多,移动非常方便。用鼠标左键点住窗口左边或上方的标题条,移动鼠标,将窗口移到您认为合适的位置;将鼠标移到窗口的边上,鼠标的图标变成可变化窗口时的形状,用鼠标左键点住,移动鼠标,变化一个或一组窗口的大小。
在“存贮窗1”的工具条中选择“微程序”;在星研软件的工具条上“选择实验”下拉框中选择“简单模型机实验”;在信息窗中选择“微程序”标签视。
您可以使用以下命令调试您的程序:
单节拍
单节拍命令,实验仪完成一个节拍工作。
单周期
单周期命令,实验仪完成当前机器周期工作。
单步(功能键F7)
单步执行当前指令,实验仪执行完当前机器指令后停止工作。
连续单步(功能键Ctrl + F7)
连续执行“单步”,用鼠标点击
按任意键后,执行完当前机器指令后停止运行。
全速运行(功能键Ctrl + F10)
从当前地址开始全速运行用户程序。用鼠标点击
执行完当前机器指令后停止运行。
停止运行
终止微机与实验仪之间通信(功能键 ESC)。
刷新
从实验仪读回数据,刷新所有窗口。
节拍频率
可选择3Hz、30Hz、300Hz、3KHz。节拍频率越高,模型机执行越快。
使用各种运行命令,调试机器程序、微程序:
使用单节拍命令,对于每一条微指令,体会系统在T1、T2、T3节拍中各做的工作;对于调试过的微指令,可使用单周期命令,快速运行;对照微程序流程图,观察微地址uPC指示灯是否与流程一致;观察DBus总线、ABus总线,对照数据通路图,分析总线上数据是否正确;微指令显示和实验仪是否一致。
当模型机执行到JMP指令时,观察OUT单元显示的数据是否为IN单元数据的2倍;改变IN单元的值,使用单步命令,点一次,模型机执行一条完整的机器指令,循环运行,从OUT单元显示的数判别程序执行是否正确。
使用运行命令,这时,不再刷新星研软件的各个窗口,模型机在全速执行程序;通过OUT单元判断执行是否正确。点击,模型机执行完当前机器指令后停止运行。星研软件从实验仪读取数据,刷新所有窗口。
2.4故障与调试
2.4.1存取故障
故障现象: 在一个周期内不能够对同一个存储单元先存储再取出,导致存储出现错误。
原因分析: 在一个周期只对存储器发送了一次读写控制信号,所以导致无法同时存取。
解决方案:在控制单元中,判断当先电平信号是高电平发取指令,在clk低电平的是否发存指令,具体代码见2.2.8 控制单元的设计
2.4.2立即数加指令
故障现象: ADDI指令的第二个操作数只有8位,与ALU所需16位?
原因分析: 由于指令的长度限制所以操作数的长度短于机器字长
解决方案:在要使用位拓展将8位拓展为16位{{8{imm[7]}},imm}这条代码可将最高位拓展8位,具体代码
2.4.3 周期问题
![]() |
图 2.8 周期故障
故障现象: 本欲想在同一个时钟周期的控制下进行各种操作,但是发现结果不理想。
原因分析: 由于硬件的性质传输具有延时,导致某些操作慢于预想的时间使得alu取数与存
解决方案:考虑到硬件综合的不同,对一些寄存器类型的输出改为线网类型,因为线网类型的硬件综合是使两个部件直接通过线连接起来。这样就可以使得信号传输无延迟或延迟减少,经测试结果令人满意。
![]() |
图 2.9 vivado仿真结果
2.5仿真及分析
![]() |
图 2.10 ModelSim仿真结果
![]() |
图 2.11 vivado仿真结果(含存储单元)
![]() |
图 2.12 ModelSim仿真结果(含存储单元)
总结与心得
3.1实验总结
本次实验主要完成了如下几点工作:
- 本学期的计算机组成原理实验借助vivado学会了vivado的基本使用操作,学习了Verilog 的基本语法,完成了CPU部件与CPU的设计,得到了支持五条指令的单周期CPU。
- 设计的CPU支持加法指令ADD,立即数加法指令ADDI,减法指令SUB,
取数指令LDA,存数指令STA等五条指令,包含256个存储单元,16位机器字长、存储字长和指令字长,8位程序计数器PC。
3.2 实验心得
- 本次实验在几周的时间内结束了,一开始做实验的时候有无从下手的感觉,对于实验所需要的软件和语言并不熟悉,此外感觉实验的内容在课内并未介绍得很细致,换句话说,课内大部分都是在介绍部件的原理,具体的实现过程需要我们在实验中摸索,比如寄存器堆的设计。
- 纸上得来终觉浅。虽然课内已经介绍了计算机的五大部件,并且对计算机指令的设计也有了很细致的介绍,但实际上机实现起来还是有一定的难度,同时容易出现很多的问题。
- 用Verilog语言实现CPU并没有像《数字逻辑》课程中从数字电路的角度出发,而是通过代码的逻辑实现,比如设计的4选1多路选择器,只需通过 if语句判断即可,最终的代码量并不多,并且各个部件本身也并不复杂,确定了输入输出和要实现的功能就可以实现,但是关键就在于各个部件之间的联动,设计一个部件的同时还要考虑它的作用,和其他部件的联系等,这样才能确保数据通路的实现,比如,如何统一各部件时序,如何将各种控制信号转化为部件的动作。通过这次实验,我对于CPU的设计与实现有了一定的认识,对课内介绍的计算机组成原理有了更加深刻的把握,这也为下一个学期的系统硬件综合实践打下了一定的基础。
- 由于vivado的体积过于庞大,功能过于繁杂,对于初学者不太友好。所以我的实验首先是在ModelSim的环境下编写完成并运行通过。在掌握了基本的硬件设计知识和技巧之后。再下载、安装vivado在vivado的环境下进行调试和完善。自我感觉,这样的学习的效果较好。
- 计算机组成原理是计算机专业本科生必修的硬件课程中重要核心课程之一。基本要求是使我们掌握计算机常用的逻辑器件、部件的原理、参数及使用方法,学懂简单、完备的单台计算机的基本组成原理,学习计算机设计中的入门性知识,掌握维护、使用计算机的技能。
- 课程主要内容:常用的组合逻辑器件,如译码器、数据选择器、编码器、alu原理;常用的同步时序电路,如寄存器、移位寄存器、计数器的原理、参数及使用方法;可编程逻辑阵列:rom,pla,pal及门阵列的原理与使用。数字化编码,数制及数制转换,数据表示,检错纠错码;数据的算术与逻辑运算,运算器的功能、组成与设计;教学机的运算器实例。计算机指令系统综述,指令格式与寻址方式;教学计算机的指令系统与汇编语言程序设计;控制器的功能、组成与设计,教学机的控制器实例。多级结构的存储系统综述,主存储器的组成与设计,教学机的内存储器实例,cache存储器的运行原理,虚拟存储器的概念与实现,磁盘设备的组成与运行原理,磁盘阵列技术;光盘机的组成与运行原理,磁带机的组成与运行原理。计算机输入/输出设备与输入/输出系统综述,显示器设备,针式打印机设备,激光印字机设备;计算机总线的功能与组成,输入/输出系统的功能与组成;教学机的总线与输入/输出系统实例。几种常用的输入/输出方式,中断与dma的请求、响应和处理。
- 计算机组成原理是计算机专业的基础课。这门课对于使我们了解现代计算机的各个组成部分及其工作原理具有重要作用,对于我们后续课程的学习无疑也具有积极的意义。