首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >单周期CPU

单周期CPU

作者头像
ttony0
发布2022-12-26 18:30:05
发布2022-12-26 18:30:05
2.8K0
举报

一. 实验目的

1、理解 MIPS 常用的指令系统并掌握单周期 CPU 的工作原理与逻辑功能实现。

2、通过对单周期CPU 的运行状况进行观察和分析,进一步加深理解。

二. 实验内容

利用 HDL 语言,基于 Xilinx FPGA basys3 实验平台,用 Verilog HDL 语言或 VHDL 语言来编写,实现单周期CPU 的设计,这个单周期 CPU 至少能够完成 20 条MIPS 指令,至少包含以下指令:

  • 支持基本的内存操作如 lw,sw 指令;
  • 支持基本的算术逻辑运算如 add,sub,and,ori,slt,addi 指令;
  • 支持基本的程序控制如 beq,j 指令;

并将其中的 alu 运算结果在开发板数码管上显示出来。

三. 实验原理

单周期CPU在每一个时钟周期会读取并执行一条指令,然后开始下一条指令的执行。时钟周期根据时钟边沿控制,两个时钟上升沿之间的时间间隔为一个时钟周期,在每一个时钟周期,CPU执行如下操作:

  1. 取指令IF:根据程序计数器PC中的指令地址,从指令存储器中取出一条指令,并且指令自增为PC+4,等待下一步操作;如果有跳转或者分支指令,PC指针也会随之修改。
  2. 译码ID:对上一步中得到的指令进行译码,确定这条指令需要完成的操作,从而产生相应的控制信号,并得到指令所访问的寄存器的位置。
  3. 执行EX:根据指令译码得到的控制信号,进行算术逻辑运算。
  4. 访问存储器MEN:根据数据存储器的数据地址,把数据写入到对应的存储单元或者从存储器中的存储单元中取出数据。
  5. 写回寄存器WB:将指令执行的结果或者数据存储器中得到的数据写回相应的目的寄存器中。

单周期CPU包含控制单元、程序计数器PC、指令存储器、数据存储器、ALU、寄存器堆、加法器与多个复用器,数据通路图如下:

具体设计时,我们使用模块化的方法,对控制单元、存储器、ALU、寄存器堆等各个部分作为各个模块分别进行设计,再利用一个顶层文件将其组合起来,模块化的结构便于各个部件的设计与检查。

我们设计的单周期CPU所执行的MIPS指令,是长度为32 bit的指令格式,分为R型、J型、I型三种类型:

R类型

31-26

25-21

20-16

15-11

10-6

5-0

op

rs

rt

rd

shamt

func

操作码

第1个源操作数寄存器

第2个源操作数寄存器

目的操作数寄存器

位移量

功能码

6位

5位

5位

5位

5位

6位

I类型

31-26

25-21

20-16

15-0

op

rs

rt

immediate

操作码

第1个源操作数寄存器

第2个源操作数寄存器

16位立即数

6位

5位

5位

16位

J类型

31-26

25-0

op

address

操作码

26位地址

6位

26位

四. 实验器材

电脑一台,Xilinx Vivado 软件一套,Basys3板一块。

五. 实验过程与结果

1、组件构成

各个模块的构成如下:

模块名称

模块功能

top

顶层模块,连接各个模块组件,管理其输入输出信号,并进行程序计数器PC运算。

ctr

控制单元模块,通过解析指令op段得到控制信号,将其输出。

ALUctr

ALU控制模块,通过解析指令funct段与控制信号aluOP,得出alu操作码。

ALU

ALU模块,根据alu操作码进行算数逻辑运算,输出结果与零标志位。

Imem

指令存储器模块,根据输入的PC地址读取对应的指令并输出,传递给其他模块进行后续处理。

Dmem

数据存储器模块,使用256x8的模式模拟内存,用于内存读写指令的实现。

regFile

寄存器堆模块,用于存储32个寄存器,根据指令对寄存器进行读写操作。

Shifter

移位模块,用于处理移位指令,得到移位结果。

signext

符号扩展模块,用于立即数扩展。

display

数码管显示模块,用于将ALU结果/移位结果译码并显示到四位数码管上。

2、各模块实现

(1)ctr.v:
输入信号:

信号

功能

opCode

指令op段,第31-26位。

funct

指令func段,第5-0位。

控制信号表(输出信号):

控制单元模块主要得出以下控制信号,各控制信号的功能和相关指令如下表所示:

控制信号

功能

相关指令

regDst

写寄存器的地址,状态0时写寄存器为rt,状态1时写寄存器为rd。

aluSrcA

ALU的第二个输入,状态0时为寄存器rt,状态1时为扩展的立即数。

memToReg

写回寄存器的值,状态0时为ALU运算结果,状态1时为从内存取出的值。

regWrite

是否写回寄存器,状态0时写入寄存器无效,状态1时寄存器堆写使能。

memRead

是否从内存读取数据,状态0时读取无效,状态1时内存读取有效。

memWrite

是否将数据写入内存,状态0时写入无效,状态1时内存写入有效。

imm_expand

立即数扩展方式,状态0时为0扩展,状态1时为符号扩展。

branch

是否分支指令。

aluop

ALU控制信号,3位,用于与funct段结合得出ALU操作码。

jmp

是否跳转指令。

jal

是否跳转链接指令,

jr

是否跳转寄存器指令。

lui

是否立即数高位取指令。

shift

是否移位指令

bne

是否不等分支指令。

bltz

是否小于0分支指令。

bgtz

是否大于0分支指令。

flag

内存操作字节数,2位,00-> byte,01->half word,1x->word

代码实现:
代码语言:javascript
复制
module ctr(
    input [5:0] opCode,//
    input [5:0] funct,//
    output reg regDst,//
    output reg aluSrcA,//
    output reg [1:0] aluSrcB,//
    output reg memToReg,//
    output reg regWrite,//
    output reg memRead,//
    output reg memWrite,//
    output reg branch,//
    output reg [2:0] aluop,//
    output reg jmp,
    output reg jal,
    output reg jr,
    output reg lui,
    output reg shift,
    output reg bne,
    output reg bltz,
    output reg bgtz,
    output reg imm_expand,
    output reg [1:0] flag
);

always @(opCode,funct) 
begin
    case(opCode)
    
        6'b001001: 
        begin
            regDst = 0;    
            aluSrcA = 1'b1;//
            aluSrcB = 2'b01;//
            memToReg = 0;  //
            regWrite = 1;  //
            memRead = 0;   //
            memWrite = 0;  //
            branch = 0;    //
            aluop = 3'b000;//
            jmp = 0;
            jal=0;
            jr=0;
            lui=0; 
            bne=0;
            bltz=0;
            bgtz=0;   
            shift=0;
            imm_expand=0; 
            flag=2'b11;       
        end             
       /*
       以上为控制单元对某一条指令得出控制信号的代码示例,根据输入的opCode,funct得出各个控制信号并输出。
       */
end
endmodule
(2)aluctr.v:
引脚说明:

引脚信号

功能

ALUop

输入信号,ALU控制信号。

funct

输入信号,指令func段,第5-0位。

ALUctr

输出信号,ALU操作码,4位,用于确定不同类型的算术逻辑运算。

ALU一共会执行加、减、与、或、异或、或非、小于设置、lui(左移16位)、左移、逻辑右移、算术右移共11种操作,我们使用四位编码对不同的操作进行表示,ALUctr模块根据指令得到最终的ALU操作码,操作码将传入ALU模块中进行算术逻辑运算。

ALU功能表:

操作

ALU操作码

功能

相关指令

0010

A + B

add、addi、addiu、lw、sw

0110

A - B

sub、beq、bne、bgtz、bltz

0000

A & B

and、andi

0001

A | B

or、ori

异或

0100

A ^ B

xor、xori

或非

0101

~ ( A | B )

nor、nori

小于设置

0111

A < B

slt、slti

左移16位

1100

A « 16

lui

左移

0011

A « B

sllv

逻辑右移

1000

A » B

srlv

算术右移

1111

(sign) A » B

srav

代码实现:
代码语言:javascript
复制
module aluctr(
    input [2:0] ALUOp,
    input [5:0] funct,
    output reg [3:0] ALUCtr
);
always @(ALUOp or funct)  
    casex({ALUOp, funct}) 
        //非R型
        9'b000xxxxxx: ALUCtr = 4'b0010; // add
        9'b001xxxxxx: ALUCtr = 4'b0110; // sub
        9'b010xxxxxx: ALUCtr = 4'b0000; // and
        9'b011xxxxxx: ALUCtr = 4'b0001; // or
        9'b100xxxxxx: ALUCtr = 4'b0100; // xor
        9'b101xxxxxx: ALUCtr = 4'b0111; // slt
        9'b110xxxxxx: ALUCtr = 4'b1100; // lui
        //R型
        9'b111_100000: ALUCtr = 4'b0010; // add
        9'b111_100001: ALUCtr = 4'b0010; // addu
        9'b111_100010: ALUCtr = 4'b0110; // sub
        9'b111_100100: ALUCtr = 4'b0000; // and
        9'b111_100111: ALUCtr = 4'b0101; // nor
        9'b111_100101: ALUCtr = 4'b0001; // or
        9'b111_100110: ALUCtr = 4'b0100; // xor
        9'b111_101010: ALUCtr = 4'b0111; // slt  
                                          
        9'b111_000100: ALUCtr = 4'b0011; // sllv
        9'b111_000110: ALUCtr = 4'b1000; // srlv
        9'b111_000111: ALUCtr = 4'b1111; // srav  
        
        default:ALUCtr = 4'b0010;
    endcase
endmodule
(3)ALU.v:
引脚说明:

引脚信号

功能

input1

输入信号,第一个操作数。

input2

输入信号,第二个操作数。

ALUctr

输入信号,ALU操作码,用于确定不同类型的算术逻辑运算。

ALUres

输出信号,ALU运算结果。

Zero

输出信号,零标志位。

ALU模块用于完成算术逻辑运算,输出有运算结果与零标志,零标志主要用于beq、bne、bltz等指令的判断。

代码实现:
代码语言:javascript
复制
module alu( 
input [31:0] input1, 
input [31:0] input2, 
input [3:0] aluCtr, 
output reg[31:0] aluRes, 
output reg zero 
); 
always @(input1 or input2 or aluCtr)
begin 
case(aluCtr) 

4'b0110: // 减
begin 
  aluRes = input1 - input2; 
  if(aluRes == 0) 
  zero = 1; 
  else 
  zero = 0; 
end 
  
4'b1100: // lui
aluRes =input2 << 16;

4'b0010: // 加
aluRes = input1 + input2; 
  
4'b0100: // 异或
aluRes = input1 ^ input2; 
  
4'b0000: // 与
aluRes = input1 & input2; 
  
4'b0001: // 或
aluRes = input1 | input2; 
  
4'b0101: // 或非
aluRes = ~(input1 | input2); 
  
4'b0011: // 左移
aluRes = input1 << input2; 
  
4'b1000: // 逻辑右移
aluRes = input1 >> input2;
  
4'b1111: // 算术右移
aluRes = $signed(input1) >> input2; 
  
4'b0111: // 小于设置
begin 
  if(input1<input2) 
  aluRes = 1; 
  else
  aluRes = 0; 
end 
  
default: 
aluRes = 0; 
endcase 
end 
  
endmodule 
(4)regFile.v:
引脚说明:

引脚信号

功能

clk

输入信号,时钟信号。

reset

输入信号,重置信号。

regWriteData,

输入信号,写入写寄存器的值,32位。

regWriteAddr

输入信号,写寄存器的地址,5位。

regWriteEn

输入信号,写寄存器使能

RsAddr

输入信号,RS 寄存器地址,5位。

RtAddr

输入信号,RT 寄存器地址,5位。

RsData

输出信号,RS 段寄存器的值,32位。

RtData

输出信号,RT 段寄存器的值,32位。

寄存器堆模块,用于储存32个32位寄存器,并根据寄存器地址对寄存器组进行读写。

输入信号包括寄存器地址、数值与控制信号,其中控制信号regWriteEn控制是否写入寄存器。因为只有32个寄存器,则寄存器的地址只需要5位就可以表示。

同时,对寄存器堆的所有操作需要在时钟和复位信号控制下操作。

代码实现:
代码语言:javascript
复制
module regFile( 
	input clk, 
	input reset, 
  input [31:0] regWriteData, //写寄存器的值
  input [4:0] regWriteAddr, //写寄存器时寄存器的地址
  input regWriteEn,	//写寄存器使能
  input [4:0] RsAddr,	//RS寄存器地址
  input [4:0] RtAddr,		//RT寄存器地址
	output [31:0] RsData,	//RS寄存器的值
  output [31:0] RtData  //RT寄存器的值
);
  
reg[31:0]regs[0:31]; //寄存器组
  
//根据地址读出 Rs、Rt 寄存器数据
assign RsData = (RsAddr == 5'b0 ) ? 32'b0 : regs[RsAddr]; 
assign RtData = (RtAddr == 5'b0 ) ? 32'b0 : regs[RtAddr]; 
integer i;
  
always @( posedge clk ) //时钟上升沿操作
begin
    if(!reset) 
    begin
        if(regWriteEn == 1) //写使能信号为1时写操作
        begin
         regs[regWriteAddr] = regWriteData; //写入数据
        end
    end
    else 
    begin
         for(i = 0; i < 32; i = i + 1)
         regs[i] = 0; //重置时所有寄存器赋值为0,复位       
    end
end
endmodule
(5)Imem.xci:
引脚说明:

引脚信号

功能

clk

输入信号,时钟信号。

ena

输入信号,使能信号。

addra

输入信号,指令所在地址。

douta(inst)

输出信号,根据地址所取指令。

功能实现:

指令存储器通过IP核进行实现,设置为单通道ROM,数据宽度为32位,数据深度为32,即一共可以存储32条32位指令。

指令存储器中每一个地址为1个字节,所以每一条指令对应4个地址,一共4x8=32位;PC每次跳转到下一条指令时,都会将其地址+4;每次取出指令就是将这四个地址单元分别取出,取出后在其它模块中对指令进行分析、操作。

指令存储器的模块接口如下:

代码语言:javascript
复制
irom imem (
  .clka(clka),    // 时钟输入
  .ena(ena),      // 使能信号
  .addra(addra),  // 指令所在地址
  .douta(douta)  // 对应指令输出
);
(6)Dmem.v:
引脚说明:

引脚信号

功能

clk

输入信号,时钟信号。

reset

输入信号,重置信号。

flag

输入信号,选择内存操作的字节数。

addr

输入信号,内存地址。

we

输入信号,控制是否将数据写入内存。

re

输入信号,控制是否读取内存数据。

wd

输入信号,写入内存的数据。

memreaddata

输出信号,从内存读取的数据。

数据存储器模块, 用于模拟内存,MIPS指令中只有lw、sw指令会直接对内存进行操作,将数值存进相应地址的内存中或根据取值地址将内存数据取出。用255大小的8位寄存器数组模拟内存,地址采用小端模式。

输入信号包括内存地址、数值,和控制信号we、re分别控制写入、读取使能;而flag作为字节或字、半字操作的标志,所对应存取字大小如下:

flag

存取字大小

00

8位byte

01

16位half word

1x

32位word

代码实现:
代码语言:javascript
复制
module dram (
  input clk, 
  input we,
  input re,
  input reset,
  input [1:0] flag,//00-> byte 01->half word  1x->word
  input [7:0] addr, 
  input [31:0] wd,
  output [31:0] rd
);
			 	 
reg [7:0] RAM[255:0]; 

//read
assign rd=re && flag[1]? 
{ {RAM[a+3]},{RAM[a+2]},{RAM[a+1]},{RAM[a+0]}} : //字
( 
    re && flag[0]?
    { {16{RAM[a+1][7]}} ,{RAM[a+1]},{RAM[a]} }://半字
    { {24{RAM[a][7]}} ,RAM[a]} //字节
);

//write
integer i;
always @ (posedge clk,posedge reset)
begin
    if(reset)begin
        for(i = 0; i < 256; i = i + 1) 
            RAM[i]=0;       
    end	
    else if (we) begin
        if(flag==2'b00)//字节
        begin
            RAM[a]=wd[7:0];
        end
        else if(flag==2'b01 )//半字
        begin
            { {RAM[a+1]},{RAM[a]} }=wd[15:0];
        end
        else if(flag[1])//字
        begin
            { {RAM[a+3]},{RAM[a+2]},{RAM[a+1]},{RAM[a+0]}}=wd;
        end           
    end  
end
endmodule
(7)shifter.v:
引脚说明:

引脚信号

功能

SHIN

输入信号,操作数。

Sa

输入信号,位移量。

ShiDir

输入信号,移位方向。

Arith

输入信号,逻辑移位/算术移位。

SHOUT

输出信号,移位结果。

移位模块用于得到移位结果,实现方式较为简单。

移位指令有sll、srl、sra与sllv、srlv、srav两类,其中sllv、srlv、srav可以使用ALU实现。

但在sll、srl、sra类指令中,由于ALU的第一个操作数只设置为寄存器RS,而移位指令的两个操作数分别是RD与SHAMT,无法同时写入ALU,只能另外启用一个模块实现,并增加一个选择器,用以选择ALU运算结果或移位结果,将结果写回寄存器中。

代码实现:
代码语言:javascript
复制
module Shifter(
		input [31:0] SHIN,
		input [4:0]  Sa,//sa
		input        ShiDir,
		input        Arith,
		output reg [31:0] SHOUT
    );

	always @(*) begin
		if (!ShiDir)      SHOUT = SHIN << Sa ;//逻辑左移
		else if (!Arith) SHOUT = SHIN >> Sa;//逻辑右移
		else       SHOUT = $signed(SHIN) >>> Sa;//算数右移
	end	

endmodule
(8)signext.v:
引脚说明:

引脚信号

功能

inst

输入信号,立即数输入。

imm_expand

输入信号,立即数扩展方式。

data

输出信号,扩展后的立即数。

符号扩展模块,实现也很简单。只需根据符号与立即数扩展方式,将立即数的高位全部补1或者全部补0即可。

代码实现:
代码语言:javascript
复制
module signext( 
input [15:0] inst, // 输入16位
input imm_expand,
output [31:0] data // 输出32位
); 
// 根据符号与立即数扩展方式补充符号位
assign data = imm_expand && inst[15:15]?{16'hffff,inst}:{16'h0000,inst}; 
endmodule 
(9)display.v:
引脚说明:

引脚信号

功能

clk

输入信号,时钟信号。

data

输入信号,用于输出的数据信息。

sm_wei

输出信号,对应四位数码管的某一位。

sm_duan

输出信号,对应当前位数码管的七段码显示。

为了更好地观察实验结果,利用显示模块将运算结果显示到数码管上。将待显示的数据输入显示模块,Basys3板会根据指令显示ALU结果或移位结果。

同时,由于数码管是高频扫描显示,我们输入的时钟信号为高频的CPU时钟,而每条指令的时钟周期经过了分频,停留足够的时间让我们可以观察到,用于显示的时钟周期与CPU的时钟周期并不是相同的时钟信号

代码实现:
代码语言:javascript
复制
module display(clk,data,sm_wei,sm_duan); 

input clk;
input [15:0] data; 
output [3:0] sm_wei; 
output [6:0] sm_duan;
//分频 

integer clk_cnt;
reg clk_400Hz; 
always @(posedge clk)

if(clk_cnt==32'd100000)
begin 
    clk_cnt <= 1'b0; 
    clk_400Hz <= ~clk_400Hz;
end 
else 
clk_cnt <= clk_cnt + 1'b1; 

//位控制
 
reg [3:0]wei_ctrl=4'b1110; always @(posedge clk_400Hz)
wei_ctrl <= {wei_ctrl[2:0],wei_ctrl[3]}; 

//段控制 

reg [3:0]duan_ctrl;
always @(wei_ctrl)
case(wei_ctrl) 
4'b1110:duan_ctrl=data[3:0]; 
4'b1101:duan_ctrl=data[7:4]; 
4'b1011:duan_ctrl=data[11:8];
4'b0111:duan_ctrl=data[15:12];
default:duan_ctrl=4'hf;
 endcase
 
//解码模块 

reg [6:0]duan;
 
always @(duan_ctrl) 
case(duan_ctrl) 
4'h0:duan=7'b100_0000;//0 
4'h1:duan=7'b111_1001;//1 
4'h2:duan=7'b010_0100;//2 
4'h3:duan=7'b011_0000;//3 
4'h4:duan=7'b001_1001;//4 
4'h5:duan=7'b001_0010;//5 
4'h6:duan=7'b000_0010;//6 
4'h7:duan=7'b111_1000;//7 
4'h8:duan=7'b000_0000;//8 
4'h9:duan=7'b001_0000;//9 
4'ha:duan=7'b000_1000;//a 
4'hb:duan=7'b000_0011;//b 
4'hc:duan=7'b100_0110;//c 
4'hd:duan=7'b010_0001;//d 
4'he:duan=7'b000_0111;//e       
4'hf:duan=7'b000_1110;//f
default : 
duan = 7'b100_0000;//0
endcase

assign sm_wei = wei_ctrl; 
assign sm_duan = duan; 
endmodule
(10)top.v:
引脚说明:

引脚信号

功能

clk

输入信号,时钟信号。

reset

输入信号,复位信号。

seg

输出信号,对应当前位数码管的七段码显示。

sm_wei

输出信号,对应哪一个数码管。

顶层模块将整个CPU的各个部件连接起来,并实现了复用器和PC更新的功能。

实际写入板子时,使用了两个时钟信号,第一个时钟信号Clk为CPU系统时钟,频率高,使用这一时钟作为周期无法观察到结果。但Clk需要用来作为数码管显示的周期,因为四位数码管的显示为高频脉冲显示。

所以分频得到了一个时间约为1.5s的时钟信号clkin,作为单周期CPU的周期,这样一来,结果才能在数码管上成功显示。

复用器:

代码语言:javascript
复制
assign mux1 = reg_dst ? inst[15:11] : inst[20:16]; 
//得到写回寄存器rd/rt
assign mux2 = alu_srcA ? expand : RtData; 
//得到立即数/寄存器rd
assign mux6 = shift ? shiftRe : aluRes;
//得到移位结果/ALU结果
assign mux3 = memtoreg ? memreaddata : mux6; 
//得到写回寄存器的数值来自内存/来自计算结果
assign mux7 = jr ? {RsData, 2'b00} : jmpaddr;
//得到跳转地址来自寄存器/指令内26位地址
assign mux5 = jmp ? mux7 : mux4;
//得到跳转地址/分支地址
assign choose5 = bne ? ~zero : zero;
//得到bne的判断结果
assign choose4 = branch & choose5; 
//得到是否分支指令
assign mux4 = choose4 ? address : add4; 
//得到是否分支指令

PC功能:

代码语言:javascript
复制
assign jmpaddr = {add4[31:28], inst[25:0], 2'b00}; 
//指令内26位地址
assign expand2 = expand << 2; 
assign address = pc + expand2; 
//PC+4
代码实现:
代码语言:javascript
复制
module top( 
input Clk, 
input reset,
output [6:0] seg,
output [3:0] sm_wei
); 
// 指令寄存器pc 
reg[31:0] pc;
reg[31:0] add4; 
wire choose4, choose5; 

// 复用器信号线
wire[31:0] expand2, mux2, mux3, mux4, mux5,mux6,mux7,mux8,mux9, address, jmpaddr; 
wire[4:0] mux1; 
wire [31:0] inst;

// CPU控制信号线
wire reg_dst, jmp, branch, memread, memwrite, memtoreg; 
wire regwrite; 
wire[2:0] aluop; 
wire alu_srcA;

// ALU信号线
wire zero; 
wire[31:0] aluRes; 

// ALU控制信号线
wire[3:0] aluCtr;

// 内存信号线
wire[31:0] memreaddata; 

// 寄存器信号线
wire[31:0] RsData, RtData; 

// 扩展信号线
wire[31:0] expand; 

//拓展
wire jr;   
wire shift;
wire lui;
wire bne;
wire [31:0] shiftRe;
wire imm_expand;
wire [1:0] flag;

//分频得到clkin  
integer clk_cnt;
reg clkin;
always @(posedge Clk)
if(clk_cnt==32'd75_000_000) 
begin
clk_cnt <= 1'b0; 
clkin <= ~clkin;
end 
else
clk_cnt <= clk_cnt + 1'b1;

always @(negedge Clk) // 时钟下降沿操作
    begin 
    if(!reset) 
    begin
        pc = mux5; // 计算下一条pc,修改pc 
        add4 = pc + 4; 
    end 
else 
    begin
    pc = 32'b0; // 复位时pc写0 
    add4 = 32'h4; 
    end 
end 

// 实例化控制器模块
ctr mainctr( 
.opCode(inst[31:26]), 
.funct(inst[5:0]), 
.regDst(reg_dst), 
.aluSrcA(alu_srcA), 
.memToReg(memtoreg), 
.regWrite(regwrite), 
.memRead(memread), 
.memWrite(memwrite), 
.branch(branch), 
.aluop(aluop), 
.jmp(jmp),
.jal(jal),
.jr(jr),
.lui(lui),
.shift(shift),
.bne(bne),
.bltz(bltz),
.bgtz(bgtz),
.imm_expand(imm_expand),
.flag(flag)
); 

// 实例化ALU模块
alu alu(
.input1(RsData), 
.input2(mux2), 
.aluCtr(aluCtr), 
.zero(zero), 
.aluRes(aluRes));

// 实例化ALU控制模块
aluctr aluctr1( 
.ALUOp(aluop), 
.funct(inst[5:0]), 
.ALUCtr(aluCtr)
); 

// 实例化dmem模块
dram dmem( 
.clk(!clkin), 
.we(memwrite), 
.re(memread),
.reset(reset),
.flag(flag),
.a(aluRes[7:0]), 
.wd(RtData), 
.rd(memreaddata) 
); 

// 实例化imem模块
irom imem( 
.clka(!clkin),    // input wire clka
.ena(1'b1),      // input wire ena
.addra(pc[6:2]),  // input wire [4 : 0] addra
.douta(inst)  // output wire [31 : 0] douta
); 

// 实例化寄存器模块
regFile regfile( 
.RsAddr(inst[25:21]), 
.RtAddr(inst[20:16]), 
.clk(!clkin), 
.reset(reset), 
.regWriteAddr(mux1), 
.regWriteData(mux3), 
.regWriteEn(regwrite), 
.RsData(RsData), 
.RtData(RtData) 
); 

//实例化移位模块
Shifter shifte(
.SHIN(RtData),//rt
.Sa(inst[10:6]),//shamt
.ShiDir(inst[1:1]),
.Arith(inst[0:0]),
.SHOUT(shiftRe)//结果->rd
 );

// 实例化符号扩展模块
signext signext(
.inst(inst[15:0]), 
.imm_expand(imm_expand),
.data(expand)); 

//复用器
assign jmpaddr = {add4[31:28], inst[25:0], 2'b00}; 
assign mux1 = reg_dst ? inst[15:11] : inst[20:16]; 
assign mux2 = alu_srcA ? expand : RtData; 
assign mux6 = shift ? shiftRe : aluRes;; 
assign mux3 = memtoreg ? memreaddata : mux6; 
assign mux7 = jr ? {RsData, 2'b00} : jmpaddr;
assign mux5 = jmp ? mux7 : mux4;
assign choose5 = bne ? ~zero : zero;
assign choose4 = branch & choose5; 
assign mux4 = choose4 ? address : add4; 
assign expand2 = expand << 2; 
assign address = pc + expand2; 

//实例化数码管显示模块
smg_ip_model display (
.clk(Clk),
.sm_wei(sm_wei),
.data(mux6[15:0]),
.sm_duan(seg)
);
  
endmodule 

3、仿真检验

将指令转换成二进制代码,一共27类指令。为了测试方便,我们将代码分为三个表格,分别对应运算存取指令、分支指令、跳转指令,同时也为了保证测试时代码数量不会溢出(超过32条)。下面是三类代码对应的表格:

运算、存取测试:

指令

代码

op

rs

rt

rd/imm

addiu $1,$0,-8

2401fff8

001001

00000

00001

11111 11111 111000

addi $1,$0,8

20010008

001000

00000

00001

00000 00000 001000

addu $2,$0,$1

00011021

000000

00000

00001

00010 00000 100001

add $2 $2 $1

00411020

000000

00010

00001

00010 00000 100000

sub $3,$2,$1

00411822

000000

00010

00001

00011 00000 100010

ori $4,$0,3

34040003

001101

00000

00100

00000 00000 000011

or $4,$4,$1

00812025

000000

00100

00001

00100 00000 100101

andi $5,$1,10

3025000a

001100

00001

00101

00000 00000 001010

and $5,$5,$1

00a12824

000000

00101

00001

00101 00000 100100

xori $6,$1,2

38260002

001110

00001

00110

00000 00000 000010

xor $6,$6,$1

00c13026

000000

00110

00001

00110 00000 100110

nor $7,$2,$1

00413827

000000

00010

00001

00111 00001 000000

lui $9,12

3c09000c

001111

00000

01001

00000 00000 001100

slt $10,$4,$3

0083502a

000000

00100

00011

01010 00000 101010

slti $11,$6,4

28cb0004

001010

00110

01011

00000 00000 000100

sll $8,$8,1

00084040

000000

00000

01000

01000 00001 000000

srl $8,$8,1

00084042

000000

00000

01000

01000 00001 000010

sra $8,$8,1

00084043

000000

00000

01000

01000 00001 000011

sllv $8,$1,$8

01014004

000000

01000

00001

01000 00000 000100

srlv $8,$1,$8

01014006

000000

01000

00001

01000 00000 000110

srav $8,$1,$8

01014007

000000

01000

00001

01000 00000 000111

sw $4,4($2)

ac440004

101011

00010

00100

00000 00000 000100

lw $5,4($2)

8c450004

100011

00010

00101

00000 00000 000100

分支测试:

地址

指令

代码

op

rs

rt

rd/imm

00000000

addi $2,$0,-1

2002ffff

001000

00000

00010

11111 11111 111111

00000004

nop

00000000

000000

00000

00000

00000 00000 000000

00000008

lab:nop

00000000

000000

00000

00000

00000 00000 000000

0000000c

addi $2,$2,1

20420001

001000

00010

00010

00000 00000 000001

00000010

bne $2,$0,lab

1440fffb

000101

00010

00000

11111 11111 111011

00000014

beq $2,$0,lab

1040fffd

000100

00010

00000

11111 11111 111101

跳转测试:

地址

指令

代码

op

rs

rt

rd/imm

00000000

addi $1,$0,4

20010004

001000

00000

00001

00000 00000 000100

00000004

jr $1

00200008

000000

00001

00000

00000 00000 001000

00000008

nop

00000000

000000

00000

00000

00000 00000 000000

0000000c

lab:nop

00000000

000000

00000

00000

00000 00000 000000

00000010

nop

00000000

000000

00000

00000

00000 00000 000000

00000014

j lab

0c000003

000011

00000

00000

00000 00000 000011

将二进制代码写入coe文件,存入指令存储器中:

op.coe:

代码语言:javascript
复制
memory_initialization_radix=16;
memory_initialization_vector=
2401fff8 20010008 00011021 00411020 00411822 34040003 00812025 3025000a 00a12824 38260002 00c13026 00413827 3c09000c 
20080008 20010002 00084040 00084042 00084043 01014004 01014006 01014007 
ac440004 8c450004;

b.coe:

代码语言:javascript
复制
memory_initialization_radix=16;
memory_initialization_vector=2002ffff 00000000 00000000 20420001 1440fffb 1040fffd 00000000 0000000;

j.coe:

代码语言:javascript
复制
memory_initialization_radix=16;
memory_initialization_vector=20010004 00200008 00000000 00000000 0c000003;

仿真模块代码如下:

代码语言:javascript
复制
module topsim;
// Inputs 
reg clkin;
reg reset;
// output
wire [31:0] inst;
wire [31:0] pc;
wire [2:0] aluop;
wire alu_srcA;
wire [31:0] add4;
wire choose4;
wire reg_dst; 
wire jmp; 
wire branch; 
wire memread; 
wire memwrite;
wire memtoreg; 
wire regwrite;
wire zero;
wire [31:0] aluRes;
wire [3:0] aluCtr;
wire [31:0] memreaddata;
wire [31:0] RsData;
wire [31:0] RtData;
wire [31:0] expand2,expand;
wire[31:0] shiftRe;
wire[31:0]  mux2, mux3, mux4, mux5,mux6, address, jmpaddr; 
wire[4:0] mux1; 
wire imm_expand;
wire shift;
wire [1:0] flag;
// Instantiate the Unit Under Test (UUT) 

//调用top模块
top uut( 
.Clk(clkin),
.reset(reset),
.inst(inst),
.pc(pc),
.aluop(aluop),
.alu_srcA(alu_srcA),
.add4(add4),
.choose4(choose4),
.reg_dst(reg_dst),
.jmp(jmp),
.branch(branch), 
.memread(memread),
.memwrite(memwrite),
.memtoreg(memtoreg),
.regwrite(regwrite),
.zero(zero),
.shift(shift),
.aluRes(aluRes),
.aluCtr(aluCtr),
.shiftRe(shiftRe),
.memreaddata(memreaddata),
.RsData(RsData),
.RtData(RtData),
.expand(expand),
.expand2(expand2), 
.mux2(mux2),
.mux3(mux3), 
.mux4(mux4), 
.mux5(mux5), 
.mux6(mux6), 
.address(address), 
.jmpaddr(jmpaddr),
.mux1(mux1),
.imm_expand(imm_expand),
.flag(flag)
);

initial begin 
// Initialize Inputs 
clkin = 0; 
reset = 1; 
// Wait 100 ns for global reset to finish 
#100; 
reset = 0; 
end 
parameter PERIOD = 20; 
always begin 
clkin = 1'b0; 
#(PERIOD / 2) clkin = 1'b1; 
#(PERIOD / 2) ; 
end 
endmodule 

接下来依次分析各条指令的仿真执行情况:

运算指令:

指令

代码

op

rs

rt

rd/imm

addiu $1,$0,-8

2401fff8

001001

00000

00001

11111 11111 111000

addi $1,$0,8

20010008

001000

00000

00001

00000 00000 001000

addu $2,$0,$1

00011021

000000

00000

00001

00010 00000 100001

add $2 $2 $1

00411020

000000

00010

00001

00010 00000 100000

sub $3,$2,$1

00411822

000000

00010

00001

00011 00000 100010

ori $4,$0,3

34040003

001101

00000

00100

00000 00000 000011

or $4,$4,$1

00812025

000000

00100

00001

00100 00000 100101

andi $5,$1,10

3025000a

001100

00001

00101

00000 00000 001010

and $5,$5,$1

00a12824

000000

00101

00001

00101 00000 100100

xori $6,$1,2

38260002

001110

00001

00110

00000 00000 000010

xor $6,$6,$1

00c13026

000000

00110

00001

00110 00000 100110

nor $7,$2,$1

00413827

000000

00010

00001

00111 00001 000000

lui $9,12

3c09000c

001111

00000

01001

00000 00000 001100

slt $10,$4,$3

0083502a

000000

00100

00011

01010 00000 101010

slti $11,$6,4

28cb0004

001010

00110

01011

00000 00000 000100

以上运算指令的仿真结果图如下:(以下结果均用16进制表示)

接下来对每条指令进行分析:

addiu:

addiu指令,将寄存器0与-8相加,得到无符号数aluRes=fffffff8;

控制信号aluSrcA=1选择立即数;

regwrite=1选择写回寄存器中;

regDst=0选择写回rt;

mux1为写回寄存器的地址,结果写回寄存器1。

addi:

addi指令,将寄存器0与8相加,得到有符号数aluRes=8,

控制信号aluSrcA=1选择立即数,

regwrite=1选择写回寄存器中,

regDst=0选择写回rt,

mux1为写回寄存器的地址,结果写回寄存器2。

addu:

addu指令,将寄存器0与寄存器1相加,得到无符号数aluRes=8,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器2。

add:

add指令,将寄存器2与寄存器1相加,得到有符号数aluRes=10,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器2。

sub:

sub指令,将寄存器2与寄存器1相减,得到有符号数aluRes=8,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器3。

ori:

ori指令,将寄存器0与3相或,得到aluRes=3,

控制信号aluSrcA=1选择立即数,

regwrite=1选择写回寄存器中,

regDst=0选择写回rt,

mux1为写回寄存器的地址,结果写回寄存器4。

or:

or指令,将寄存器4与寄存器1相或,得到aluRes=b,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器4。

andi:

andi指令,将寄存器1与10相与,得到aluRes=8,

控制信号aluSrcA=1选择立即数,

regwrite=1选择写回寄存器中,

regDst=0选择写回rt,

mux1为写回寄存器的地址,结果写回寄存器5。

and:

and指令,将寄存器5与寄存器1相与,得到aluRes=8,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器5。

xori:

xori指令,将寄存器1与2相与,得到aluRes=a,

控制信号aluSrcA=1选择立即数,

regwrite=1选择写回寄存器中,

regDst=0选择写回rt,

mux1为写回寄存器的地址,结果写回寄存器6。

xor:

xor指令,将寄存器6与寄存器1异或,得到aluRes=2,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器6。

nor:

nor指令,将寄存器7与寄存器1或非,得到aluRes=ffffffe7,

控制信号aluSrcA=0选择寄存器rt,

regwrite=1选择写回寄存器中,

regDst=1选择写回rd,

mux1为写回寄存器的地址,结果写回寄存器7。

lui:

lui指令,将数字c存到寄存器9的高16位中,得到aluRes=000c0000,

控制信号aluSrcA=1选择立即数,

regwrite=1选择写回寄存器中,

regDst=0选择写回rt,

mux1为写回寄存器的地址,结果写回寄存器9。

slt:

slt指令比较寄存器4<寄存器3是否成立,若成立,aluRes=1;

从上面的运算我们可以得到,寄存器4的值为b,寄存器3的值为8,b<8不成立,则aluRes=0;

控制信号aluSecA=0选择寄存器rt,

regDst=1选择写回寄存器rd,

mux1=a,结果写回寄存器10。

slti:

slti指令比较寄存器6<立即数4是否成立,若成立,aluRes=1;

从上面的运算我们可以得到,寄存器6的值为2,2<4成立,则aluRes=1;

控制信号aluSecA=1选择立即数,

regDst=0选择写回寄存器rt,

mux1=b,结果写回寄存器11。

移位指令:

指令

代码

op

rs

rt

rd/imm

sll $8,$8,1

00084040

000000

00000

01000

01000 00001 000000

srl $8,$8,1

00084042

000000

00000

01000

01000 00001 000010

sra $8,$8,1

00084043

000000

00000

01000

01000 00001 000011

sllv $8,$1,$8

01014004

000000

01000

00001

01000 00000 000100

srlv $8,$1,$8

01014006

000000

01000

00001

01000 00000 000110

srav $8,$1,$8

01014007

000000

01000

00001

01000 00000 000111

以上六条移位指令的仿真结果图如下:

在初始化时,我们令8=8、1=2,移位指令分为两大类,分别使用移位模块、ALU模块执行运算,接下来分别看各条指令的结果。(以下结果均用16进制表示)

sll:

第一大类的移位指令通过移位模块计算,shift=1表示启用移位模块,shamt=1,将$8左移一位,结果显示在shiftRe中,结果10正确。

srl:

shift=1表示启用移位模块,shamt=1,将$8逻辑右移一位,结果显示在shiftRe中,结果8正确。

sra:

shift=1表示启用移位模块,shamt=1,将$8右移算数一位,结果显示在shiftRe中,由于8为正数,得到运算结果4,正确。

sllv:

第二大类的移位指令通过ALU模块计算,shift=0表示不启用移位模块,1=2,将8左移2位,结果显示在aluRes中,结果10正确。

srlv:

第二大类的移位指令通过ALU模块计算,shift=0表示不启用移位模块,1=2,将8逻辑右移2位,结果显示在aluRes中,结果4正确。

srav:

第二大类的移位指令通过ALU模块计算,shift=0表示不启用移位模块,1=2,将8算术右移2位,结果显示在aluRes中,结果1正确。

存取指令:

指令

代码

op

rs

rt

rd/imm

sw $4,4($2)

ac440004

101011

00010

00100

00000 00000 000100

lw $5,4($2)

8c450004

100011

00010

00101

00000 00000 000100

存取指令将寄存器4的值先存入内存地址为:寄存器2的值+4的位置,再将数据取出至寄存器5。

根据上面指令的运算结果,4=b,2=10,则内存地址应为14,存取的数据为b。接下来看指令的仿真结果图:

sw:

RsData=10,对应寄存器2的值;

RtData=b,对应寄存器4的值;

aluSrcA=1,选择立即数;

expand=4,立即数的值为4;

memwrite=1,写入内存使能;

regwrite=0,不写入寄存器;

aluRes=14,得到内存地址14;

将寄存器4的值b写入内存地址14中。

lw:

RsData=10,对应寄存器2的值;

aluSrcA=1,选择立即数;

expand=4,立即数的值为4;

memwrite=0,不写入内存;

menread=1,读取内存使能;

regwrite=1,写入寄存器;

mux1=5,写入寄存器地址为5;

aluRes=14,得到内存地址14;

将内存地址14中的值(b)写入寄存器5中。

分支指令:

地址

指令

代码

op

rs

rt

rd/imm

00000000

addi $2,$0,-1

2002ffff

001000

00000

00010

11111 11111 111111

00000004

nop

00000000

000000

00000

00000

00000 00000 000000

00000008

lab:nop

00000000

000000

00000

00000

00000 00000 000000

0000000c

addi $2,$2,1

20420001

001000

00010

00010

00000 00000 000001

00000010

bne $2,$0,lab

1440fffb

000101

00010

00000

11111 11111 111011

00000014

beq $2,$0,lab

1040fffd

000100

00010

00000

11111 11111 111101

由于分支测试比较难以判断,我们另外使用一个测试文件,专门用于测试分支指令。 指令如上图所示,在执行两道加法指令之后,寄存器2的值为0,则在bne指令中不会跳转,在beq指令中跳转。为了便于观察,我们添加了几条空指令nop。

测试结果如下:

接下来对指令依次分析:

bne:

add4=14为PC+4;

expand2 = ffff ffec为相对跳转地址,跳转到-5x4=-20处;

bne=1,表示执行bne指令;

zero=1,表示运算结果为0;

choose4=0,choose4判断最终是否执行跳转,为0不执行跳转;

branch=1,表示分支指令。

beq:

add4=18为PC+4;

expand2 = ffff fff4为相对跳转地址,跳转到-4x4=-10处;

zero=1,表示运算结果为0;

choose4=1,choose4判断最终是否执行跳转,为1执行跳转;

branch=1,表示分支指令。

跳转后的指令:

分支跳转之后,PC地址为18-10=8。

跳转指令:

地址

指令

代码

op

rs

rt

rd/imm

00000000

addi $1,$0,4

20010004

001000

00000

00001

00000 00000 000100

00000004

jr $1

00200008

000000

00001

00000

00000 00000 001000

00000008

nop

00000000

000000

00000

00000

00000 00000 000000

0000000c

lab:nop

00000000

000000

00000

00000

00000 00000 000000

00000010

nop

00000000

000000

00000

00000

00000 00000 000000

00000014

j lab

0c000003

000011

00000

00000

00000 00000 000011

为了验证跳转指令,我们另外使用一个测试文件,专门用于测试跳转指令。 指令如上图所示,在执行加法指令之后,寄存器1的值为4,在jr指令中会跳转到4«2=10的指令地址上;同样,j指令会跳转到c的指令地址上。为了便于观察,我们添加了几条空指令nop。

测试结果如下:

jr:

jmp=1,执行跳转指令;

jr=1,执行jr指令;

RsData=4,为寄存器1的值;

mux5=10,为4«2后得到的指令地址。

jr的下一条指令:

执行jr指令后,PC=10,成功跳转到对应地址。

j:

jmp=1,执行跳转指令;

mux5=c,为我们得到的的指令地址。

j的下一条指令:

执行j指令后,PC=c,成功跳转到对应地址。

4、Basys3板实现

引脚设置:

输入输出的引脚如下:

输入为时钟信号Clk、复位信号Reset,Clk连接系统CPU时钟,复位信号连接按钮开关U18。

输出信号为各个数码管显示所需的高低电平信号,分别为4个位选信号和7个段选信号。具体连接方式如下:

仿真文件:

代码语言:javascript
复制
set_property PACKAGE_PIN U7 [get_ports {seg[6]}]
set_property PACKAGE_PIN V5 [get_ports {seg[5]}]
set_property PACKAGE_PIN U5 [get_ports {seg[4]}]
set_property PACKAGE_PIN V8 [get_ports {seg[3]}]
set_property PACKAGE_PIN U8 [get_ports {seg[2]}]
set_property PACKAGE_PIN W6 [get_ports {seg[1]}]
set_property PACKAGE_PIN W7 [get_ports {seg[0]}]
set_property PACKAGE_PIN U2 [get_ports {sm_wei[0]}]
set_property PACKAGE_PIN U4 [get_ports {sm_wei[1]}]
set_property PACKAGE_PIN V4 [get_ports {sm_wei[2]}]
set_property PACKAGE_PIN W4 [get_ports {sm_wei[3]}]
set_property PACKAGE_PIN W5 [get_ports Clk]
set_property PACKAGE_PIN U18 [get_ports reset]

set_property IOSTANDARD LVCMOS33 [get_ports {seg[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports Clk]
set_property IOSTANDARD LVCMOS33 [get_ports reset]

端口映射:

运行结果:

数码管显示的结果为运算结果/PC地址,这里展示了不同类几条指令的显示结果,结果都与上述仿真结果一致:

add指令:运算结果为8。

sub指令:运算结果为8。

or指令:运算结果为b。

xor指令:运算结果为2。

nor指令:运算结果为ffe7。

lui指令:运算结果为0000。(高4位为000c无法显示)

sll指令:运算结果为10。

srl指令:运算结果为8。

sra指令:运算结果为4。

beq指令:当前地址为0014,分支跳转到0008。

j指令:当前地址为0014,分支跳转到000c。

六. 实验心得

本次实验是在之前实现各个小模块的基础上,将其整合起来,完成一个真正意义上的单周期CPU。这需要我们对CPU的工作原理有一个清晰的认识,而且对各个模块之间的联系、对每条指令控制信号的分析也需要清楚。

在实验里首先遇到的问题是语法问题,对我们来说,verilog语言是一个新的语言,虽然我们在之前的实验里,已经学会了看懂代码、理解其中的意思,但实际写代码的时候总会遇到各种各样的错误,有时候vivado的报错也让人花了很多时间。为了解决这些问题,我参考了网上的资料与以前实验的代码。

另外,对于各个模块的测试尽量先分别进行测试,在测试结果正确后再进行组合。而实验比较难的一部分是对各个指令的控制信号的测试。控制信号联系了各个模块,对控制信号进行分析时需要联系多个模块,并且对指令本身的功能需要完全理解。在根据控制信号进行测试花的时间是最长的,在测试遇到问题的时候,也需要仔细确认是哪一个模块出了问题。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-12-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 实验目的
  • 二. 实验内容
  • 三. 实验原理
  • 四. 实验器材
  • 五. 实验过程与结果
    • 1、组件构成
    • 2、各模块实现
      • (1)ctr.v:
      • (2)aluctr.v:
      • (3)ALU.v:
      • (4)regFile.v:
      • (5)Imem.xci:
      • (6)Dmem.v:
      • (7)shifter.v:
      • (8)signext.v:
      • (9)display.v:
      • (10)top.v:
    • 3、仿真检验
      • 运算指令:
      • 移位指令:
      • 存取指令:
      • 分支指令:
      • 跳转指令:
    • 4、Basys3板实现
      • 引脚设置:
      • 运行结果:
  • 六. 实验心得
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档