文章目录
一、 单周期CPU的设计原理
1.1 单周期CPU概述
中央处理器,即CPU,作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。在CPU内部,电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。
1.2 CPU工作原理
CPU在处理指令时,一般需要经过以下几个步骤:
(1) 取指令(IF):根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,PC根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入PC,当然得到的“地址”需要做些变换才送入PC。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
CPU的指令处理过程如下:
2.1 指令系统的设计
2.1.1 概述
本文所设计的单周期CPU的指令系统采用类似MIPS的设计风格,包括以下四类指令:
(1) 运算类指令;
(2) 传送类指令;
(3) 存储类指令;
(4) 控制类指令;
其中,所有指令的操作码部分用4位二进制表示,寄存器编号用3位二进制表示。在下述的具体设计表示中,以助记符表示的是汇编指令;以代码表示的则是二进制机器指令。
2.2 整体框架的设计
本文所设计的单周期CPU的整体框架主要包括七部分:程序计数器、指令寄存器、寄存器组、算术逻辑单元、数据存储器、控制单元和顶层模块。具体框架如下:
3.具体实现
程序计数器
代码如下:
module PC(
input clk,
input [15:0] PCin,
input PCWre,
input Reset,
output reg [15:0] PCout
);
initial begin
PCout <= 0;
end
always@(posedge clk) begin
if(Reset == 0) begin
PCout <= 0;
end
else if(PCWre == 0) begin
PCout <= PCout;
end
else begin
PCout <= PCin;
end
end
endmodule
指令存储器
代码如下:
module InsMemory(
input InsMemRW,
input [15:0] address,
output reg [15:0] DataOut
);
reg [7:0] mem [0:127];
initial begin
DataOut = 16'b1111000000000000;
$readmemb("Instructions.txt", mem);
end
always@(*) begin
DataOut[15:8] <= mem[address];
DataOut[7:0] <= mem[address+1];
end
endmodule
寄存器组
代码如下:
module RegFile(
input CLK,
input RST,
input RegWre,
input [2:0] ReadReg1,
input [2:0] ReadReg2,
input [2:0] WriteReg,
input [15:0] WriteData,
output [15:0] ReadData1,
output [15:0] ReadData2
);
reg [15:0] regFile[0:7];
integer i;
assign ReadData1 = regFile[ReadReg1];
assign ReadData2 = regFile[ReadReg2];
always @ (negedge CLK) begin
if (RST == 0) begin
for(i=1;i<8;i=i+1)
regFile[i] <= 0;
end
else if(RegWre == 1 && WriteReg != 0) begin
regFile[WriteReg] <= WriteData;
end
end
endmodule
算数逻辑单元
代码如下:
module ALU(
input [2:0] ALUopcode,
input [15:0] rega,
input [15:0] regb,
output reg [15:0] result,
output zero,
output sign
);
assign zero = (result==0)?1:0;
assign sign = result[15];
always @( ALUopcode or rega or regb ) begin
case (ALUopcode)
3'b000 : result = rega + regb;
3'b001 : result = rega - regb;
3'b010 : result = regb << rega;
3'b011 : result = rega | regb;
3'b100 : result = rega & regb;
3'b101 : result = (rega < regb)?1:0;
3'b110 : begin
if (rega<regb &&(( rega[15] == 0 && regb[15]==0) ||
(rega[15] == 1 && regb[15]==1))) result = 1;
else if (rega[15] == 0 && regb[15]==1) result = 0;
else if ( rega[15] == 1 && regb[15]==0) result = 1;
else result = 0;
end
3'b111 : result = regb;
endcase
end
endmodule
数据存储器
代码如下:
module DataMemory(
input clk,
input [15:0] address,
input RD,
input WR,
input [15:0] DataIn,
output [15:0] DataOut
);
reg [7:0] ram[0:127];
integer i;
initial begin;
for(i=0;i<128;i=i+1)
ram[i]<=0;
end
assign DataOut[7:0] = (RD == 0)? ram[address+1]:8'bz;
assign DataOut[15:8] = (RD == 0)? ram[address]:8'bz;
always@(negedge clk) begin
if(WR == 0) begin
if(address>=0 && address<128) begin
ram[address] <= DataIn[15:8];
ram[address+1] <= DataIn[7:0];
end
end
end
endmodule
控制单元
代码如下:
module ControlUnit(
input [3:0] opcode,
input zero,
input sign,
output reg PCWre,
output reg ALUSrcA,
output reg ALUSrcB,
output reg DBDataSrc,
output reg RegWre,
output reg InsMemRW,
output reg RD,
output reg WR,
output reg RegDst,
output reg ExtSel,
output reg [1:0] PCSrc,
output reg [2:0] ALUOp
);
initial begin
RD = 1;
WR = 1;
RegWre = 0;
PCWre = 0;
InsMemRW = 1;
end
always@ (opcode) begin
case(opcode)
4'b0000:begin // add
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ALUOp = 3'b000;
end
4'b0001:begin //addi
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 1;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 0;
ExtSel = 1;
ALUOp = 3'b000;
end
4'b0010:begin //sub
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ALUOp = 3'b001;
end
4'b0011:begin // or
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ALUOp = 3'b011;
end
4'b0100:begin // ori
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 1;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 0;
ExtSel = 0;
ALUOp = 3'b011;
end
4'b0101:begin //and
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ALUOp = 3'b100;
end
4'b0110:begin //sll
PCWre = 1;
ALUSrcA = 1;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ALUOp = 3'b010;
end
4'b0111:begin //slt
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ALUOp = 3'b110;
end
4'b1000:begin //mov
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 1;
ExtSel = 1;
ALUOp = 3'b111;
end
4'b1001:begin //movi
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 1;
DBDataSrc = 0;
RegWre = 1;
InsMemRW = 1;
RD = 1;
WR = 1;
RegDst = 0;
ExtSel = 1;
ALUOp = 3'b111;
end
4'b1010:begin //sw
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 1;
RegWre = 0;
InsMemRW = 1;
RD = 1;
WR = 0;
ExtSel =1;
ALUOp = 3'b000;
end
4'b1011:begin //lw
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 1;
DBDataSrc = 1;
RegWre = 1;
InsMemRW = 1;
RD = 0;
WR = 1;
RegDst = 0;
ExtSel = 1;
ALUOp = 3'b000;
end
4'b1100:begin //beq
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
RegWre = 0;
InsMemRW = 1;
RD = 1;
WR = 1;
ExtSel = 1;
ALUOp = 3'b001;
end
4'b1101:begin //bgtz
PCWre = 1;
ALUSrcA = 0;
ALUSrcB = 0;
RegWre = 0;
InsMemRW = 1;
RD = 1;
WR = 1;
ExtSel = 1;
ALUOp = 3'b001;
end
4'b1110:begin //j
PCWre = 1;
RegWre = 0;
InsMemRW = 1;
RD = 1;
WR = 1;
ALUOp = 3'b010;
end
4'b1111:begin //halt
PCWre = 0;
RegWre = 0;
InsMemRW = 1;
RD = 1;
WR = 1;
end
default:begin
RD = 1;
WR = 1;
RegWre = 0;
InsMemRW = 0;
end
endcase
end
always@(opcode or zero or sign) begin
if(opcode == 4'b1110) // j
PCSrc = 2'b10;
else if(opcode == 4'b1100) begin
if(zero == 1)
PCSrc = 2'b01;
else
PCSrc = 2'b00;
end
else if(opcode == 4'b1101) begin
if(zero == 0 && sign == 0)
PCSrc = 2'b01;
else
PCSrc = 2'b00;
end
else begin
PCSrc = 2'b00;
end
end
endmodule
顶层模块实现
二、通过在proteus上进行C51仿真LED流水灯实验
1、绘制原理图
1)添加元件
(1)点击绘制原理图按钮。
(2)左键单击元件,然后再点击P按钮,进入元件选择界面。
(3)在 Keywords 处输入 AT89C51 ,然后在中间的窗口内双击AT89C51芯片,即可添加到元件列表中,而后依次添加LED-YELLOW、RES
2)摆放元件
(1)左击元件列表窗内的 AT89C51 芯片,然后再原理图编辑窗口内左击摆放。
(2)左击元件列表内的 LED-YELLOW ,再点击 旋转按钮 ,可以在预览窗内看见元件顺时针旋转了下,再在原理图编辑框内一次摆放LED灯共8个。
(3)再按照(2)的步骤依次摆放8个电阻,然后左键双击“10K”,弹出修改值的对话框,将10K修改为300,以至于让LED更亮。
(4)拉一条主线。(左击一下起点,然后移动鼠标,然后双击一下终点,即可拉一条主线)
(5)连接管脚。(左击一下起点和左击一下终点即可连接)
(6)点击终端接口→选择POWER,放置电源,然后左键双击电源图标,修改为VCC,再点击OK。
(7)使用 LBL 为支线标记编号,连接到主线的支线,需要对支线进行编号才能正常连接,否则后面需要正常实验成功(使用方法:点击 LBL 后,在左键单击支线上,修改值)
2、编写51程序
1)创建一个工程
(1)打开 Keil 软件,点击 Project → New uVision Project …
(2)给工程命名。(这里我取名为 LED )
(3)在搜索框内输入 AT89C51 ,再选中 AT89C51 芯片,然后点击 OK 。
2)编写main.c文件
(1)点击左上角新建文件,再在文本框内复制粘贴51程序代码。`
//51单片机编程常用的头文件
#include <reg51.h>
#include <intrins.h>
//延迟函数
void delay_ms(int a)
{
int i,j;
for(i=0;i<a;i++)
{
for(j=0;j<1000;j++) _nop_();
}
}
void main(void)
{
while(1)
{
P0=0xfe;
delay_ms(50);
P0=0xfd;
delay_ms(50);
P0=0xfb;
delay_ms(50);
P0=0xf7;
delay_ms(50);
P0=0xef;
delay_ms(50);
P0=0xdf;
delay_ms(50);
P0=0xbf;
delay_ms(50);
P0=0x7f;
delay_ms(50);
}
}
(2)点击保存按钮,再命名为main.c文件(一定要加.c后缀,不然就不是C文件了),再点击保存。
(3)右键点击 Source Group 1 ,再点击 Add Existing Files to Group “Source Group 1”…
(4)选中刚刚创建的 main.c 文件,并点击 Add 。
(5)可以看见 main.c 文件已经在 Source Group 1 目录下面了。
3)生成 .hex 文件
(1)点击魔法棒,在弹出的窗口内选择 Output ,再勾选 Create HEX File ,然后点击 OK。
(2)点击编译按钮,进行编译,编译成功并生成了两个头文件(这一步不可忽略,否则无法生成 .hex 文件)
3、开始仿真
(1)回到Proteus软件的原理图内,双击 AT89C51 芯片后,在弹出的窗口的 Program File 一栏从刚才 keil 软件编译后的路径中添加 .hex 文件,再点击 OK 。(2)点击调试按钮,开始仿真。
(3)仿真结果