本实验使用的是Verilog,离谱的是CSDN居然找不到Verilog的代码块,只能使用c语言的代码块了。
通过设计并实现支持 10 条指令的CPU,进一步理解和掌握CPU 设计的基本原理和过程。
设计和实现一个支持如下十条指令的单周期 CPU。
非访存指令
清除累加器指令 CLA
累加器取反指令 COM
算术右移一位指令 SHR:将累加器ACC中的数右移一位,结果放回 ACC
循环左移一位指令 CSL:对累加器中的数据进行操作
停机指令 STP
访存指令
加法指令 ADD X:[X]+[ACC]–〉ACC,X为存储器地址,直接寻址
存数指令 STA X,采用直接寻址方式
取数指令 LDA X,采用直接寻址
转移类指令
无条件转移指令 JMP imm:signExt(imm)->PC
有条件转移(负则转)指令 BAN X: ACC 最高位为 1 则(PC)+ X -> PC,否则 PC 不变
一条指令的执行过程包括:取指令→分析指令→取操作数→执行指令→保存结果。单周期 CPU 是指所有指令均在一个时钟周期内完成的CPU。CPU由数据通路及其控制部件两部分构成,因而要完成一个支持若干条指令 CPU 的设计,需要依次完成以下两件事:
1)根据指令功能和格式设计 CPU 的数据通路;
2)根据指令功能和数据通路设计控制部件。
设目标CPU的机器字长为16位,存储字长和指令字长均为21位,存储单元个数假设为64,按字寻址,并取PC位数为16。约定指令格式高5位为指令的操作码字段,低16位为指令的地址码或者立即寻址的操作数。
取指令:根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,PC根据指令字长度自动递增产生下一条指令所需要的指令地址。
指令译码:对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
指令执行:根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
存储器访问:所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
结果写回:指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
指令存储器将操作码送到控制单元,将地址送到数据存储器,将偏移地址送到PC;控制单元控制PC的运行和停止,控制累加器的读写,控制运算单元的运行,控制数据存储器的数据存储;累加器、PC和数据存储器都要受到时钟信号的控制;ALU通过累加器和数据存储器中的数据进行运算,将运算结果输送给数据存储器和累加器,将条件转移信号输给PC。
在第三部分通过对该CPU实现细节的分析、设计,并得到该CPU的原理图后,就可以依次实现各个模块,并进行仿真验证了。
4.1 CPU各模块Verilog实现
(1)累加器
输入 时钟信号clk、读写控制线wr、输入数据indate
输出 输出数据outdate
功能 ACC数据从outdate输出,如果wr有效,在clk下降沿将会把indate写入ACC中。
Verilog关键代码:
module ACC(
input wire clk,wr,
input wire [15:0] indate,
output wire [15:0] outdate
);
reg [15:0] ace;
assign outdate = ace;
initial begin
ace = 0;
end
always@(negedge clk) begin
if(wr==1)
ace = indate;
end
endmodule
(2)指令存储器
输入 16位指令地址addr
输出 21位指令ins
功能 存放待执行的指令,并根据地址输出指令
Verilog关键代码:
module InsMem(
input wire[15:0] addr,
output reg[20:0] inst
);
reg[20:0] insmem [63:0];
initial begin
insmem[0] = 21'b011110000000000000000;//lda
insmem[1] = 21'b000110000000000000000;//com
insmem[2] = 21'b001010000000000000000;//shr
insmem[3] = 21'b001110000000000000000;//csl
insmem[4] = 21'b000010000000000000000;//cla
insmem[5] = 21'b010110000000000000001;//add
insmem[6] = 21'b011010000000000000010;//sta
insmem[7] = 21'b100010000000000001000;//jmp
insmem[8] = 21'b100110000000000000001;//ban
insmem[9] = 21'b010010000000000000000;//stp
end
always@* begin
inst = insmem[addr];
end
endmodule
(3)PC
输入 时钟信号clk、重置信号rst、停机信号stop、无条件转移信号wr、
条件转移信号judge、pc修改量change(16位)
输出 指令地址pc(16位)
功能 每个时钟上升沿pc的值自动加1,并输出,如果wr为1,修改pc为
change-1,如果judge为1,修改pc为pc+change,如果rst为1,修改pc为0
Verilog关键代码:
module PC(
input wire clk,rst,wr,stop,judge,
input wire[15:0] change,
output reg[15:0] pc
);
reg flag;
initial begin
pc = 0;
flag = 1;
end
always@* begin
if(stop==1)
flag = 0;
if(rst==1)
pc = 0;
end
always@(posedge clk) begin
if(flag==1)
pc = pc+1;
end
always@(negedge clk) begin
if(wr==1)
pc = change-1;
if(judge==1)
pc = pc+change-1;
end
endmodule
(4)控制单元
输入 指令(操作码)(5位)
输出 停机信号stop、PC读写控制线pc_wr、ACC读写控制线 acc_wr、数
据存储器读写控制线date、ALU的操作选择信号op
功能 根据当前指令功能对stop、pc_wr、acc_wr、date和op赋值
Verilog关键代码:
module ctrlUnit(
input wire [4:0] opcode,
output reg stop,pc_wr,acc_wr,date,
output reg [4:0] op
);
initial begin
stop = 0;
pc_wr = 0;
acc_wr = 0;
date = 0;
op = 5'b01001;
end
always@(opcode) begin
case(opcode)
5'b00001:{stop,pc_wr,acc_wr,date,op}= 9'b001000001;//cla
5'b00011:{stop,pc_wr,acc_wr,date,op}= 9'b001000011;//com
5'b00101:{stop,pc_wr,acc_wr,date,op}= 9'b001000101;//shr
5'b00111:{stop,pc_wr,acc_wr,date,op}= 9'b001000111;//csl
5'b01001:{stop,pc_wr,acc_wr,date,op}= 9'b100001001;//stp
5'b01011:{stop,pc_wr,acc_wr,date,op}= 9'b001001011;//add
5'b01101:{stop,pc_wr,acc_wr,date,op}= 9'b000101101;//sta
5'b01111:{stop,pc_wr,acc_wr,date,op}= 9'b001001111;//lda
5'b10001:{stop,pc_wr,acc_wr,date,op}= 9'b010010001;//jmp
5'b10011:{stop,pc_wr,acc_wr,date,op}= 9'b010010011;//ban
endcase
end
endmodule
(5)数据存储器
输入 时钟信号clk、读写控制线wr、16位指令地址 addr、输入数据indata
输出 16 位输出数据 outdata
功能 存放数据,可以读出,或在clk下降沿写入。
Verilog关键代码:
module regFile (
input wire[15:0] addr,
input wire[15:0] indata,
input wire wr,clk,
output wire[15:0] outdate
);
reg[15:0] regs[63:0];
assign outdate = regs[addr];
initial begin
regs[0] = 16'b0000000000000001;
regs[1] = 16'b1000000000000000;
end
always@(negedge clk)begin
if(wr==1)
regs[addr] = indata;
end
endmodule
(6)ALU
输入 操作数in1和in2、操作选择信号alu_op
输出 ALU运算结果Z
功能 根据操作选择信号计算in1和in2的运算结果Z。
Verilog关键代码:
module ALU(
input wire [15:0] in1, in2,
input wire [4:0] alu_op,
output reg flag,
output reg [15:0] z
);
initial begin
flag = 0;
z = 0;
end
always@* begin
case(alu_op)
5'b00001: z=0;//cla
5'b00011: z=~in1;//com
5'b00101: z={1'b1,in1[15:1]};//shr
5'b00111: z={in1[14:0],in1[15]};//csl
5'b01001: ;//stp
5'b01011: z=in1+in2;//add
5'b01101: z=in1;//sta
5'b01111: z=in2;//lda
//jmp
5'b10011: if(in1[15]==1)//ban
flag = 1;
endcase
end
endmodule
4.2 CPU 顶层文件封装实现
该 CPU 的输入为系统时钟信号clk和重置信号rst。在这个文件中,声明了
PC,InsMem,regFile,ACC,ctrlUnit,ALU等模块。并声明了几个wire类型的变量用于连接各个部件。
Verilog 关键代码:
module CPU(
input wire clk,rst
);
wire stop,pc_wr,acc_wr,date,judge;
wire [15:0] addr;
wire [4:0] op;
wire [15:0] in1,in2,z;
wire [20:0] ins;
PC pc(.clk(clk), .rst(rst), .stop(stop), .wr(pc_wr), .judge(judge), .change(ins[15:0]), .pc(addr));
InsMem insmem(.addr(addr), .inst(ins));
ACC acc(.clk(clk), .wr(acc_wr), .indate(z), .outdate(in1));
regFile ref(.wr(date), .clk(clk), .addr(ins[15:0]), .indata(z), .outdate(in2));
ctrlUnit ctrlu(.opcode(ins[20:16]), .stop(stop), .pc_wr(pc_wr), .acc_wr(acc_wr), .date(date), .op(op));
ALU alu(.alu_op(op), .in1(in1), .in2(in2), .flag(judge), .z(z));
endmodule
4.3 CPU 模拟仿真
Verilog 关键代码:
module CPU_tb;
reg clk, rst;
CPU cpu(.clk(clk), .rst(rst));
initial begin
clk = 1;
rst = 1;
#1 rst=0;
#20 $stop;
end
always
#1 clk = ~clk;
always
#20 $stop;
endmodule
仿真及分析:
设计出的CPU基本能实现相应的功能,完成10条指令,能明显地看出数值
变化,仿真过后的结果表明10条指令的执行情况较好,在一个时钟周期内所设计的CPU能够完成每一条指令的执行,指令执行结果与预期的结果是一致的。通过仿真可以看到最终顺利实现了每个模块的功能,整个CPU按照设计好的指令运行。
主要设计和实现步骤:
通过前几个实验的实验原理,分析各种指令的处理过程,将CPU内部各个部分划分成多个模块,使每一个模块实现一定的功能,按照对每个模块功能预设,选择对应的控制信号,将各个模块连接起来,完成CPU的设计。
在实现的过程中,通过verilog语言,设计合适的变量和相应的信号类型,通过合适的信号将CPU中的各个模块联系起来,实现CPU的模拟。
主要注意事项:
(1)verilog里面的wire和reg两种变量类型,会影响最后的仿真结果。
(2)每一个模块都有相对应的功能,要将模块之间分清楚,一个功能只能由一个模块实现。
(3)弄清楚哪些变量需要存储,哪些变量需要随时钟信号的变化而变化,确保变量定义的准确性。
心得体会:
本次单周期CPU设计实验,将课堂上所讲的内容与实际联系起来,自己实现了单周期CPU的设计,加深了CPU处理指令过程理解,之前对于课堂上学的内容理解不够透彻,本次实验加深了印象。
在这次实验中,我也更加了解每条指令的处理过程,以及单周期CPU是如何工作的,同时在本次实验也加深了对于verilog语言的了解,之前只是简单的复现实验,对于很多细节不太了解,通过这次实验,对verilog语言,有了系统性的认知。
在这个实验中,最重要的是学会模块化,将一项工作分成多个模块进行完成,先简化成小部分,然后再将其组合起来。每一个模块实现一个功能,所有的模块组合起来,就能完成一项工作,也相当于一项小工程。
本次实验使我受益匪浅,让我对于计算机组成原理的知识有了更深的理解,同时,让我深入学习了Verilog语言。