写在前面:
本次实验是课程的最后一次实验,要求按照指导书的说明将之前的板块整合成一个完整的CPU,建议大家每连接一个板块都进行一次仿真验证,保证能正常运行且功能正常,如果等到CPU组装好再调试工作量较大并且有些错误难以发现。
关于验收:
当CPU综合设计完成后,课程组的老师会统一安排助教进行验收,时间通常在结课前一两周(距离考试周很近,时间会比较赶),以2022级为例,验收过程首先助教会先询问你是否将程序下载到单片机上(建议所有同学都下板,验收分数会高一个level,而且操作不复杂),完成程序的演示后,助教会挑选CPU中的部分组件来询问你相应的设计原理(所以需要在验收前需要好好复习每个模块的相应内容,保证原理都能理解并且讲清楚),最后就是抽题,可以选择抽难题和简单题,并且难题如果答不上来还有一次换简单题的机会,我感觉难题方差挺大,有比较难的也有难度很小的,难题中考的比较多的是设计类问题比如:如果要在我们的CPU加一个XX功能,那哪些组成部分需要修改呢?(从信号角度考虑,如果需要产生新的信号,那么consignal、insdecode是必须要考虑到的),还有在AU运算模块中加一个新模块(通常是往年有但当年没有的运算)代码逻辑应该怎么写等等,这里附上2021级学长的经验供大家参考:http://t.csdnimg.cn/vMZbY
预祝大家都能在这门课中学有所成,并且取得一个好成绩!
一、设计目的
完整、连贯地运用《电路与电子学》所学到的电路、模电和数电知识,熟练掌握现代 EDA工具基本使用方法,为后续课程学习和今后从事相关工作打下良好的基础或做下一些铺垫。
二、设计内容
① 设计一个由传感器(电桥)、放大器、滤波器、模数转换器构成的模拟通道;
② 按照给定的数据通路、数据格式和指令系统,使用 EDA 工具设计一台用硬连线逻辑控制的简易计算机;
③ 用所设计的计算机控制模拟通道对外界模拟输入信号进行采样、离散和处理,并显示在数码显示管上;
④ 整理出设计报告。
三、详细设计
3.1 模型机的设计思路
1.首先根据自上到下的原则,了解模型机整体的设计思路原理,再根据整体原理去设计下层的结构和功能;
2.利用好分块设计,使用 Verilog 语言对每个模块进行设计,依次分析和检查每个模块的功能和仿真波形是否正确;
3.再根据从下到上的原则,依据整体架构对封装好的各板块进行连接,每连接一个板块进行一次仿真验证,确保无误后再进行下一个组建的连接,直到整个模型机设计完毕。
3.2 模型机的整体架构
3.3 各模块的具体实现
(此部分必须各模块功能、接口设计以及功能实现)
3.2.1 控制信号产生逻辑
接口设计:模块的输入端口是来自指令译码器的 12 个指令信号:mova、movb、movc、movd、movi、 add、sub、jmp、jg、in1、out1、halt,以及控制取址和执行周期的 sm 信号,还有指令码的直接输入ir[7...0],以及 g 状态位的输入,输出端口为各个模块的使能信号或控制信号:sm_en、pc_ld、pc_in、reg_sr[1:0]、 reg_dr[1:0]、reg_we、s[1:0]、au_en、au_ac[3:0]、gf_en、in_en、out_en、mux_s。
功能实现:控制信号产生逻辑接收指令译码器的输出,在 sm、ir[7:0]以及状态位 g 的配合下产生每个模块所需要的控制信号。
Verilog 语言:
bash
module con_signal(mova,movb,movd,movc,add,sub,jmp,jg,g,in1,out1,movi,halt,ir,sm,sm_en,ir_ld,ram_re,ram_wr,pc_ld,pc_in,reg_sr,reg_dr,reg_we,s,au_en,au_ac,gf_en,in_en,out_en,mux_s);
input mova,movb,movc,movd,add,sub,jmp,sm,jg,g,in1,out1,movi,halt;
input [7:0]ir;
output reg sm_en,ram_re,ir_ld,pc_in,ram_wr,pc_ld,reg_we,gf_en,in_en,out_en,mux_s,au_en;
output reg [1:0]reg_sr,reg_dr,s;
output reg [3:0]au_ac;
always @(*)
begin
au_en=mova | movb | add | out1 | sub;
sm_en=~halt;
ir_ld=~sm;
ram_re=(~sm) | movc | movi;
ram_wr=movb;
gf_en=sub;
pc_ld=jmp | (jg & g);
pc_in=movi | (~sm);
reg_we=movi | mova | movc | movd | sub | add | in1;
in_en=in1;
reg_dr=ir[3:2];
out_en=out1;
reg_sr=ir[1:0];
if(movb) s=2'b10;
else if(movc) s=2'b01;
au_ac=ir[7:4];
mux_s=mova | movb |movi | add | sub | in1;
end
endmodule
3.2.2 指令译码器
接口设计:模块输入为译码器使能信号 en 和指令寄存器传来的机器码 ir[3:0],输出为十二条指令信号 mova、movb、movc、movd、add、sub、jmp、jg、in1、out1、movi、halt,将会接到控制信号发生逻辑上。
功能实现:当译码器使能信号en 为 0 时使能禁止,各个输出端口均输出 0;当 en=1 时,根据传入的机器码 ir 进行指令判断,对应的指令端口输出 1,其余为 0。
Verilog 语言:
bash
module ins_decode(en,ir,mova,movb,movc,movd,add,sub,jmp,jg,in1,out1,movi,halt);
input en;
input [3:0] ir;
output mova,movb,movc,movd,add,sub,jmp,jg,in1,out1,movi,halt;
reg [11:0]HappyNewYear;
always @ (en,ir)
begin
if(en==0)
begin HappyNewYear<=12'b000000000000; end
else
begin
if(ir==4'b0100)
begin HappyNewYear<=12'b100000000000; end
else if(ir==4'b0101)
begin HappyNewYear<=12'b010000000000; end
else if(ir==4'b0110)
begin HappyNewYear<=12'b001000000000; end
else if(ir==4'b0111)
begin HappyNewYear<=12'b000100000000; end
else if(ir==4'b1000)
begin HappyNewYear<=12'b000010000000; end
else if(ir==4'b1001)
begin HappyNewYear<=12'b000001000000; end
else if(ir==4'b1010)
begin HappyNewYear<=12'b000000100000; end
else if(ir==4'b1011)
begin HappyNewYear<=12'b000000010000; end
else if(ir==4'b1100)
begin HappyNewYear<=12'b000000001000; end
else if(ir==8'b1101)
begin HappyNewYear<=12'b000000000100; end
else if(ir==8'b1110)
begin HappyNewYear<=12'b000000000010; end
else if(ir==8'b1111)
begin HappyNewYear<=12'b000000000001; end
else
begin HappyNewYear<=12'b000000000000; end
end
end
assign {mova,movb,movc,movd,add,sub,jmp,jg,in1,out1,movi,halt}=HappyNewYear;
endmodule
3.2.3 算术单元 AU
接口设计:函数发生器模块共有 4 个输入端口,包括来自通用寄存器的数据来源 a[7:0]、b[7:0],以及来自控制信号发生逻辑的 a[3:0]指令码和使能信号au_en 信号。输出端口包括连接总线的数据 t 以及 gf标志。
功能实现:要实现:算术运算、直接经过两种功能。对于每种功能都要从 a,b 选择适当的端口获取数据,并且在要考虑加减法运算对 gf 标志的影响,要利用好补码的运算以及大小比较。
Verilog 语言:
bash
module au(au_en,ac,a,b,t,gf);
input au_en;
input [3:0] ac;
input [7:0] a;
input [7:0] b;
output reg [7:0] t;
output reg gf;
always @(au_en,ac,a,b)
begin
gf=1'b0;
t=8'b00000000;
if(au_en==1'b0)
begin
t=8'hZZ;
end
else if(au_en==1'b1)
begin
if(ac==4'b1000)
begin
t=a+b;
end
else if(ac==4'b1001)
begin
t=b+(~a)+8'b00000001;
if(t[7]==0)gf=1;
else if(t[7]==1)gf=0;
end
else if(ac==4'b0100 || ac==4'b0101 || ac==4'b1101)
begin t=a; end
else
begin t=8'hZZ; end
end
end
endmodule
3.2.4 8 重 3-1 多路复用器
接口设计:8 重 3-1 多路复用器有 3 个八位输入 a[7:0]、b[7:0]、c[7:0],1 个输出 y[7:0],称之为 8重,s[1:0]选择将哪个输入传至输出。
功能实现:当控制信号s 为 00 时,输出 y=a,当控制信号 s 为 01 时输出 y=b,当控制信号 s=10 时,输出 y=c,控制信号 s=11 时,输出y=a。
Verilog 语言:
bash
module mux3_1(a,b,c,s,y);
input [7:0]a,b,c;
input [1:0]s;
output reg[7:0]y;
always @(*)
begin
if(s==2'b01) y<=b;
else if(s==2'b10) y<=c;
else y<=a;
end
endmodule
3.2.5 8 重 2-1 多路复用器
接口设计:8 重 2-1 多路复用器有 2 个八位输入 a[7:0]、b[7:0],1 个输出 y[7:0],称之为 8 重,s 选择将哪个输入传至输出。
功能实现:当控制信号s 为 0 时,输出y=a,当控制信号 s 为 1 时输出 y=b。
Verilog 语言:
bash
module mux2_1(a,b,s,y);
input [7:0]a,b;
input s;
output reg[7:0] y;
always @(*)
begin
if(s==1'b0)y<=a;
else if(s==1'b1)y<=b;
end
endmodule
3.2.6 SM
**接口设计:**输入端口包括时钟信号clk 和使能信号 sm_en,输出为 sm 用于指示当前是取指周期还是执行指令周期。
**功能实现:**在时钟的下降沿对 sm 取反。SM 为 0 是取指令周期;SM 为 1 是执行指令周期。
Verilog 语言:
bash
module sm(sm_en,sm,clk);
input sm_en,clk;
output sm;
reg t=1'b0;
always @(negedge clk)
begin
if(sm_en==1'b1) t<=~t;
end
assign sm=t;
endmodule
3.2.7 指令寄存器 IR
接口设计:输入端口有时钟信号 clk,控制读入信号 ld_ir,来自总线的信号a[7:0],输出为 x
功能实现:当控制信号 ir_ld 为 1 时,指令寄存器在时钟信号 CLK 的下降沿将总线传输的指令写入寄存器。
Verilog 语言:
bash
module ir(clk,ld_ir,a,x);
input clk,ld_ir;
input [7:0]a;
output reg [7:0]x=8'b00000000;
always @(negedge clk)
begin
if(ld_ir==1'b1)
x<=a;
end
endmodule
3.2.8 状态寄存器 PSW
接口设计:输入端口包括时钟信号 clk,使能信号 g_en 和移位逻辑传输过来的g 信号。输出为 gf 信号。
功能实现:PSW 用来存放 SUB 指令执行结果的状态标志,结果是否为零。当 g_en 为 1 时,在时钟下降沿将 g 写入 gf。
Verilog 语言:
bash
module psw(g_en,gf,g,clk);
input g,clk,g_en;
output reg gf=1'b0;
always @(negedge clk)
begin
if(g_en==1'b1)
gf<=g;
end
endmodule
3.2.9 指令计数器 PC
接口设计:模块的输入端有来自总线上的数据a[7:0],以及来自控制信号发生逻辑的加载信号 ld_pc,自加信号 in_pc,时钟信号 clk。输出端口为 c[7:0],通向选择器的地址输出ADDR。
功能实现:in_pc 为 1,ld_pc 为 0,执行地址加 1 操作;in_pc 为 0,ld_pc 为 1,执行写入操作,将输入 a[7:0]写入到输出c[7:0]中。
Verilog 语言:
bash
module pc(ld_pc,in_pc,clk,a,c);
input in_pc,clk,ld_pc;
input [7:0]a;
output reg [7:0]c=8'b00000000;
always @(negedge clk)
begin
if(in_pc==1'b1 && ld_pc==1'b0)
c<=c+8'b00000001;
else if(in_pc==1'b0 && ld_pc==1'b1)
c<=a;
end
endmodule
3.2.10 通用寄存器组
接口设计:通用寄存器组模块的输入端口包括来自控制信号发生单元的写使能信号 we、源寄存器地址 sr[1:0],目的寄存器地址 dr[1:0],来自总线上的数据输入 i[7:0] 和系统时钟信号 clk。输出包括s[7:0]和 d[7:0]。
功能实现:该寄存器组有读和写两种功能,对于读操作:根据 sr 和 dr 的值分别为 00、01、10、11 分别选择 r0、r1、r2、r3 的值从对应的 s/d 口输出;对于写操作,当 we 为 1 时,在时钟下降沿根据 dr 的值为 00、01、10、11 分别将 i 的值读入 r0、r1、r2、r3。
Verilog 语言:
bash
module reg_group(we,clk,sr,dr,i,s,d);
input we,clk;
input [1:0]sr,dr;
input [7:0]i;
output reg[7:0]s,d;
reg [7:0]r0=8'b00000001,r1=8'b00000001,r2=8'b00000001,r3=8'b00000001;
always @(*)
begin
if(sr==2'b00)
s=r0;
else if(sr==2'b01)
s=r1;
else if(sr==2'b10)
s=r2;
else
s=r3;
if(dr==2'b00)
d=r0;
else if(dr==2'b01)
d=r1;
else if(dr==2'b10)
d=r2;
else
d=r3;
end
always @(negedge clk)
begin
if(we==1'b1)
begin
case(dr)
2'b00:r0<=i;
2'b01:r1<=i;
2'b10:r2<=i;
2'b11:r3<=i;
endcase
end
end
endmodule
3.2.11 RAM
接口设计:RAM 的端口包括 mux_3 选择器传来的地址 address、时钟信号 inclock、写入寄存器的信号 we、以及读取寄存器中的值的信号 outenab 和端口 dio
功能实现:当 we 和 outenab 均为 0 时,在时钟信号的上升沿输出高阻态 Z,当 we 为 0,outenab 为 1 时,进行写操作,在时钟信号的上升沿将 dio 中的数据写入地址address 对应的储存单元中,当 we 为 1,outenab为 0 时,执行读操作,将地址 address 对应储存单元中的值从 dio 输出。
四、系统测试
4.1 测试环境:
开发软件:Quartus ll version 9.0 Build 184 04/29/2009 S.J web Edition
操作系统:Windows11 家庭版
FPGA 学习板芯片信号:Cyclone II EP2C5T144C8
4.2 测试代码:
(使用模型机实现的指令编写一至两个程序测试模型机的正确性。)
R3 初始值为 00000111,R1 初始值为 00000001
ram.mif 文件:
4.3 测试结果
下板代码功能仿真:
下板代码时序仿真:
下板图示:
仿真代码功能仿真:
仿真代码时序仿真:
4.4 模型机性能分析
分析:此程序占用资源数为 165(4%),时钟周期为 22.414ns,warning13 条,性能较好,占用资源较少,符合实验要求。
五、实验总结、必得体会及建议
5.1 从需要掌握的理论、遇到的困难、解决的办法以及经验教训等方面进行总结。
答:对于本次实验,需要学会如何使用 Quartus 编写具有对应功能的模块与元件、掌握如何组装并设计简易的模型机,同时需要了解如何使用在 FPGA 实验板上验证功能是否正确。