简单微处理器的设计与实现
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
简单微处理器的设计与实现
刘言(湖北师范学院)
摘要:
本次设计完全用verilog硬件描述语言编写微处理器的各个部件,在顶层文件里将各个部件连接起来形成一个简单的微处理器,加上一些外围模块来实现一些功能。
一、设计任务和技术指标
运用在“数字电路与逻辑设计”课程中学过的基本理论知识,设计并用可编程逻辑器件实现一个简单的八位操作数的微处理器。
完成微处理器硬件系统设计和指令系统设计两方面的任务,使微处理器能够实现两个不带符号位的八位二进制数原码相乘等功能。
二、简单介绍
本次设计完全用verilog硬件描述语言编写微处理器的各个部件,在顶层文件里将各个部件连接起来形成一个简单的微处理器,加上一些外围模块来实现一些功能。
本次设计在合理性与实用性上没有考虑,只是为了达到技术指标,从原理上完成了一个简单的RISC_CPU设计。
1.微处理器硬件系统及原理
A、内部组成
微处理器硬件由一下八个基本部件组成:
运算器、控制器、指令寄存分离器、时钟发生器、程序计数器、累加器、地址选择器、数据选择器。
时钟发生器CLK_GEN:利用输入时钟信号生成三个时钟信号送往对应的部件。
程序计数器PC:存放将要执行指令的地址。
指令寄存器IR:存放被执行指令的操作码和操作数的地址,直接供运算器与控制器。
控制器CON:根据操作码产生一系列时序逻辑信号,控制微处理器各个部件协调一致地完成每条指令相应的操作。
运算器ALU:当时钟上升沿来时对累加器与数据总线上的数进行加减乘除或逻辑运算。
累加器ACC:暂时存放运算结果。
地址选择器:选择输出到地址总线上的地址,是PC还是操作数地址。
数据选择器:选通ALU的运算结果输出到数据总线上去。
下面是内部连接图:(见最后一页)
B、外围模块
程序存储器ROM:存放程序指令。
数据存储器RAM:读取存放运算中间变量等临时数据。
输入输出特殊寄存器:数据的输入输出。
外部部分连接图:
外围设备通过总线连接CPU
2.处理器指令系统及功能
处理器的基本指令字长为W位,指令的每一位从高到低用DW、DW-1、…D1、D0表示,有些微处理器的一条指令包括多个指令字长,即每条指令的长度不一样,例如Intel的80386等。
本实验为了简化设计,规定所有的指令都是单指令字的指令,且所有指令的长度都是8,即一个字节。
因此可以有8种操作,5根地址线寻址空间为32B。
指令的前3位为操作码,后5位为操作数的地址,寻址方式只设计了直接寻址。
详细如下表:指令助记符操作说明
000_D DIV除A=A/(D)
001_D JMP无条件跳转PC=D
010_D ADD加A=A+(D)
011_D SUB减A=A-(D)
100_D MUL乘TA=A*(D)
101_D LDA读A=(D)
110_D STO写D=A
111_D JNA条件跳转如果A=0,PC=D.
三、各模块详细介绍
1、时钟发生器:
时钟发生器clkgen的波形见下图:
2、程序计数器:
程序计数器用于提供指令地址,以便读取指令,指令按地址顺序存放在存储器中。
有两种途径可形成指令地址:其一是顺序执行的情况,其二是遇到要改变顺序执行程序的情况,例如执行JMP 指令后,需要形成新的指令地址。
复位后,指令指针为零,即每次CPU重新启动将从ROM的零地址开始读取指令并执行。
每条指令执行完需1个时钟,这时pc_addr已被增1,指向下一条指令。
如果正执行的指令是跳转语句,这时CPU状态控制器将会输出load_pc信号,通过load口进入程序计数器。
程序计数器(pc_addr)将装入目标地址(ir_addr),而不是增1。
Verilog代码:
1.module counter(pc_addr,ir_addr,load,clock,rst);
2.output[4:0]pc_addr;
3.input[4:0]ir_addr;
4.input load,clock,rst;
5.reg[4:0]pc_addr;
6.
7.always@(posedge clock or posedge rst)
8.begin
9.if(rst)
10.pc_addr<=0;
11.else
12.if(load)
13.pc_addr<=ir_addr;
14.else
15.pc_addr<=pc_addr+1;
16.end
17.endmodule
3、指令寄存器
指令寄存器的触发时钟是clk1,在clk1的正沿触发下,寄存器将数据总线送来的指令存入opcode(高3位)或ir_addr(低5位)中。
但并不是每个clk1的上升沿都寄存数据总线的数据,因为数据总线上有时传输指令,有时传输数据。
什么时候寄存,什么时候不寄存由CPU状态控制器的load_ir信号控制。
load_ir信号通过ena口输入到指令寄存器。
复位后,指令寄存器被清为零。
Verilog代码:
1.module register(opcode,ir_addr,data,ena,clk,rst);
2.output[2:0]opcode;
3.output[4:0]ir_addr;
4.input[7:0]data;
5.input ena,clk,rst;
6.reg[2:0]opcode;
7.reg[4:0]ir_addr;
8.
9.always@(posedge clk)
10.begin
11.if(rst)
12.begin
13.opcode<=3'b000;
14.ir_addr<=5'b00000;
15.end
16.else
17.begin
18.if(ena)
19.begin
20.opcode<=data[7:5];
21.ir_addr<=data[4:0];
22.end
23.end
24.end
25.endmodule
4、运算器
根据输入的8种不同操作码对两个数据(data、accum)分别实现相应的运算,结果由alu_out 输出。
Verilog代码:
1.module alu(alu_out,zero,bz,data,accum,alu_clk,opcode);
2.output[7:0]alu_out;
3.output zero,bz;
4.input[7:0]data,accum;
5.input[2:0]opcode;
6.input alu_clk;
7.reg[7:0]alu_out,tmp;
8.reg bzt;
9.initial bzt=0;
10.
11.parameter DIV=3'b000,
12.JMP=3'b001,
13.ADD=3'b010,
14.SUB=3'b011,
15.MUL=3'b100,
16.LDA=3'b101,
17.STO=3'b110,
18.JA=3'b111;
19.
20.assign zero=!accum;
21.always@(posedge alu_clk)
22.begin
23.casex(opcode)
24.DIV:alu_out<=accum/data;
25.JMP:alu_out<=accum;
26.ADD:alu_out<=data+accum;
27.SUB:alu_out<=accum-data;
28.MUL:begin
29.if(!bzt)
30.begin
31.{tmp,alu_out}<=data*accum;
32.bzt<=1;
33.end
34.else
35.begin
36.alu_out<=tmp;
37.bzt<=0;
38.end
39.end
40.LDA:alu_out<=data;
41.STO:alu_out<=accum;
42.JA:alu_out<=accum;
43.default:alu_out<=8'bxxxx_xxxx;
44.endcase
45.end
46.assign bz=bzt;
47.endmodule
5、累加器
累加器用于存放当前的结果,它也是alu其中一个数据来源。
复位后,累加器的值是零。
当累加器通过ena口收到来自CPU状态控制器load_acc信号时,在clk1时钟正跳沿时就收到来自于数据总线的数据。
Verilog代码:
1.module accum(accum,data,ena,clk,rst);
2.output[7:0]accum;
3.input[7:0]data;
4.input ena,clk,rst;
5.reg[7:0]accum;
6.
7.always@(posedge clk)
8.begin
9.if(rst)
10.accum<=8'b0000_0000;//Reset
11.else
12.if(ena)//当CPU状态控制器发出load_acc信号
13.accum<=data;//接收来自ALU的数据
14.end
15.
16.endmodule
6、地址选择器
地址选择器用于选择输出的地址是PC(程序计数)地址还是数据/端口地址。
每个指令周期的前4个时钟周期用于从ROM中读取指令,输出的应是PC地址。
后4个时钟周期用于对RAM或端口的读写,该地址由指令中给出。
地址的选择输出信号由fetch提供。
Verilog代码:
1.module addrchoose(addr,fetch,ir_addr,pc_addr);
2.output[4:0]addr;
3.input[4:0]ir_addr,pc_addr;
4.input fetch;
5.
6.assign addr=(fetch)?pc_addr:ir_addr;
7.
8.endmodule
7、数据选择器:
累加器的数据只有在需要往RAM区或输出口写时才允许输出,否则应呈现高阻态,以允许其它部件使用数据总线。
所以任何部件往总线上输出数据时,都需要一控制信号。
而此控制信号的启、停,则由CPU状态控制器输出的各信号控制决定。
数据控制器何时输出累加器的数据则由状态控制器输出的控制信号datactl_ena决定。
Verilog代码:
1.module datactl(data,in,ena);
2.output[7:0]data;
3.input[7:0]in;
4.input ena;
5.
6.assign data=(ena)?in:8'bzzzz_zzzz;
7.endmodule
8、控制器
控制器是最重要的模块,它CPU的控制核心,用于产生一系列的控制信号,启动或停止某些部件。
CPU何时进行读指令读写I/O端口,RAM区等操作,都是由状态机来控制的。
状态机的当前状态,由变量state记录,state的值就是当前这个指令周期中已经过的时钟数。
第1个时钟:读指令,将rd与load_ir置高电平,其余为低电平。
此时地址输出的是PC,所以会将
PC所指的ROM里的数据取到数据总线,并分开存入指令寄存器中。
第2个时钟:同上(不做任何操作)。
第3个时钟:空,输出全低(不做任何操作)。
第4个时钟:PC加1,将inc_pc置高电平,其余为低电平。
第5个时钟:这个时钟fetch翻转,由高变低,开始分析操作码并输出相应的信号。
第6个时钟:这个时钟alu_clk翻转,由低变高,alu分析操作码并进行相应运算,继续分析操作码并输出相应的信号。
第7个时钟:空(不做任何操作)
第8个时钟:空,输出全低(不做任何操作)。
Verilog代码:
1.module machine(inc_pc,load_acc,load_pc,rd,wr,load_ir,datactl_ena,clk,zero,rst,opcode,bz);
2.output inc_pc,load_acc,load_pc,rd,wr,load_ir,datactl_ena;
3.input clk,zero,rst,bz;
4.input[2:0]opcode;
5.reg inc_pc,load_acc,load_pc,rd,wr,load_ir,datactl_ena;
6.reg[7:0]state;
7.
8.parameter DIV=3'b000,
9.JMP=3'b001,
10.ADD=3'b010,
11.SUB=3'b011,
12.MUL=3'b100,
13.LDA=3'b101,
14.STO=3'b110,
15.JA=3'b111;
16.parameter SS0=8'b00000000,
17.SS1=8'b00000001,
18.SS2=8'b00000010,
19.SS3=8'b00000100,
20.SS4=8'b00001000,
21.SS5=8'b00010000,
22.SS6=8'b00100000,
23.SS7=8'b01000000,
24.SS8=8'b10000000;
25.
26.always@(posedge clk)
27.begin
28.if(rst)//接收到复位信号RST,进行复位操作
29.begin
30.state<=SS0;
31.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
32.{wr,load_ir,datactl_ena}<=3'b000;
33.end
34.else
35.begin
36.case(state)
37.SS0:
38.begin
39.state<=SS1;
40.end
41.SS1://读指令
42.begin
43.{inc_pc,load_acc,load_pc,rd}<=4'b0001;
44.{wr,load_ir,datactl_ena}<=3'b010;
45.state<=SS2;
46.end
47.SS2:
48.begin
49.{inc_pc,load_acc,load_pc,rd}<=4'b0001;
50.{wr,load_ir,datactl_ena}<=3'b010;
51.state<=SS3;
52.end
53.SS3:
54.begin
55.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
56.{wr,load_ir,datactl_ena}<=3'b000;
57.state<=SS4;
58.end
59.
60.SS4://pc+1
61.begin
62.{inc_pc,load_acc,load_pc,rd}<=4'b1000;
63.{wr,load_ir,datactl_ena}<=3'b000;
64.state<=SS5;
65.end
66.SS5://fetch翻转
67.begin
68.if(opcode==JA&&zero==1)
69.begin
70.{inc_pc,load_acc,load_pc,rd}<=4'b0010;
71.{wr,load_ir,datactl_ena}<=3'b000;
72.end
73.else
74.if(opcode==JMP)
75.begin
76.{inc_pc,load_acc,load_pc,rd}<=4'b0010;
77.{wr,load_ir,datactl_ena}<=3'b000;
78.end
79.else
80.if(opcode==ADD||opcode==SUB||opcode==MUL||opcode==LDA||
opcode==DIV)
81.begin
82.if(!bz)
83.begin
84.{inc_pc,load_acc,load_pc,rd}<=4'b0001;
85.{wr,load_ir,datactl_ena}<=3'b000;
86.end
87.else
88.begin
89.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
90.{wr,load_ir,datactl_ena}<=3'b001;
91.end
92.end
93.else
94.if(opcode==STO)
95.begin
96.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
97.{wr,load_ir,datactl_ena}<=3'b001;
98.end
99.else
100.begin
101.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
102.{wr,load_ir,datactl_ena}<=3'b000;
103.end
104.state<=SS6;
105.end
106.SS6://alu_clock上升
107.begin
108.if(opcode==ADD||opcode==SUB||opcode==MUL||opcode==LDA||opcode==DIV) 109.begin
110.if(!bz)
111.begin
112.{inc_pc,load_acc,load_pc,rd}<=4'b0101;
113.{wr,load_ir,datactl_ena}<=3'b000;
114.end
115.else
116.begin
117.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
118.{wr,load_ir,datactl_ena}<=3'b101;
119.end
120.end
121.else
122.if(opcode==JMP)
123.begin
124.{inc_pc,load_acc,load_pc,rd}<=4'b1010;
125.{wr,load_ir,datactl_ena}<=3'b000;
126.end
127.else
128.if(opcode==JA&&zero==1)
129.begin
130.{inc_pc,load_acc,load_pc,rd}<=4'b1010;
131.{wr,load_ir,datactl_ena}<=3'b000;
132.end
133.else
134.if(opcode==STO)
135.begin
136.//过一个时钟后把wr变1就可写到RAM中
137.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
138.{wr,load_ir,datactl_ena}<=3'b101;
139.end
140.else
141.begin
142.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
143.{wr,load_ir,datactl_ena}<=3'b000;
144.end
145.state<=SS7;
146.end
147.SS7:
148.begin
149.if(opcode==STO)
150.begin
151.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
152.{wr,load_ir,datactl_ena}<=3'b001;
153.end
154.else
155.if(opcode==ADD||opcode==SUB||opcode==MUL||opcode==LDA|| opcode==DIV)
156.begin
157.{inc_pc,load_acc,load_pc,rd}<=4'b0001;
158.{wr,load_ir,datactl_ena}<=3'b000;
159.end
160.else
161.begin
162.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
163.{wr,load_ir,datactl_ena}<=3'b001;
164.end
165.state<=SS8;
166.end
167.
168.SS8:
169.begin
170.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
171.{wr,load_ir,datactl_ena}<=3'b000;
172.state<=SS1;
173.end
174.default:
175.begin
176.{inc_pc,load_acc,load_pc,rd}<=4'b0000;
177.{wr,load_ir,datactl_ena}<=3'b000;
178.state<=8'bxxxxxxxx;
179.end
180.endcase
181.end
182.
183.end
184.endmodule
9、ROM
程序存储器。
因为只需要验证下功能,这里的ROM是一种用verilog语言模拟的ROM,代码如下:
1.module rom(data,addr,rd,ena);
2.output[7:0]data;
3.input[4:0]addr;
4.input rd,ena;
5.reg[7:0]memory[31:0];
6.wire[7:0]data;
7.
8.initial
9.begin
10.memory[0]=8'b101_00000;//(00H)→A
11.memory[1]=8'b100_00001;//A*(01H)→TA
12.memory[2]=8'b100_11100;//T→1CH
13.memory[3]=8'b110_11101;//A→1DH
14.memory[4]=8'b001_00000;//00H→PC
15.
16.//1CH:输出口0地址,1DH:输出口1地址。
(由硬件连接决定)
17.end
18.
19.assign data=(rd&&ena)?memory[addr]:8'bzzzzzzzz;
20.endmodule
10、RAM
1.module ram(data,addr,ena,rd,wr);
2.inout[7:0]data;
3.input[4:0]addr;
4.input ena;
5.input rd,wr;
6.reg[7:0]ram[27:0];
7.initial
8.begin
9.ram[0]=250;
10.ram[1]=100;
11.end
12.
13.assign data=(rd&&(!ena))?ram[addr]:8'hzz;
14.
15.always@(posedge wr)
16.begin
17.ram[addr]<=data;
18.end
19.endmodule
四、CPU的测试:
这里增加两个数码管译码输出口,将其挂在总线上,连接如下:
其功能是:将输入的8位数据译码,供数码管显示(16进制)。
几个测试用程序:
程序1:
乘法:将存在00H与01H的数相乘,结果送到输出口。
ROM:
//1CH:输出口0的地址,1DH:输出口1的地址
memory[0]=8'b101_00000;//(00H)→A
memory[1]=8'b100_00001;//A*(01H)→TA
memory[2]=8'b100_11101;//T→1DH
memory[3]=8'b110_11100;//A→1CH
memory[4]=8'b001_00000;//00H→PC
RAM:
ram[0]=250;
ram[1]=100;
程序2:
除法:将(00H)/(01H)的结果送到输出口。
memory[0]=8'b101_00000;//(00H)→A
memory[1]=8'b000_00001;//A/(01H)→A
memory[2]=8'b110_11100;//A→1CH
memory[3]=8'b001_00000;//00H→PC
程序3:
减1:让输出口的数循环减一。
ROM:
memory[0]=8'b101_00000;//(00H)→A
memory[1]=8'b011_00001;//A-(01H)→A
memory[2]=8'b110_11100;//A→1CH
memory[3]=8'b111_00000;//如果A=0:00H→PC。
否者PC+1→PC
memory[4]=8'b001_00001;//01H→PC
RAM:
ram(0)=16;
ram(1)=1;
程序4:
加、减1:输出先由F减到0再加到F,如此循环。
//RAM:00H=16,01H= 1.
memory[0]=8'b101_00000;//(00H)→A
memory[1]=8'b011_00001;//A-(01H)→A
memory[2]=8'b110_11100;//A→1CH
memory[3]=8'b111_00101;//如果A=0:05H→PC。
否者PC+1→PC
memory[4]=8'b001_00001;//01H→PC
memory[5]=8'b010_00001;//A+(01H)→A
memory[6]=8'b110_00010;//A→02H
memory[7]=8'b101_00000;//(OOH)→A
memory[8]=8'b011_00010;//A-(02H)→A
memory[9]=8'b111_00000;//如果A=0:00H→PC。
否者PC+1→PC
memory[10]=8'b101_00010;//(02H)→A
memory[11]=8'b110_11100;//A→1CH
memory[12]=8'b001_00101;//05H→PC
乘法程序测试结果:
这个乘法程序将RAM里00H里的数乘以01H里的数,然后输出到数码管译码输出口。
十进制:250*100=25000。
十六进制:FA*64=61A8。
61A8在数码管上显示的段码分别为:
BE101111106
60011000001
EE11101110A
FE111111108
仿真波形图:
段码:BE60EE FE
显示:61A8
最终输出端口上输出了想要的值,CPU设计成功。
五、总结
本次设计不仅完成了设计任务与技术指标而且还有一定的发挥,在目前的基础上进行改进可以很容易的增加指令种类,增加寻址方式,增加寻址空间,再增加适当的外设,几乎可以无限的扩展该CPU的功能。
六、参考文献
1、夏宇闻《verilog数字系统设计教程(第2版)》
2、相关技术网站。