一、设计目标
- 掌握微程序控制器的基本原理
- 设计可以实现实现基本的指令运算指令、数据传输指令、输入输出指令、转移指令;并且具有中断和原码一位乘法功能
- 使用Verilog HDL 在Max Plus2上实现CPU模型的仿真
注:我是在MaxPlus2上实现的,由于MaxPlus2太古老了,推荐大家使用Quartus。
二、指令设计
1、指令格式
单字节指令:
操作码 OP 4位 | 目的寄存器 Rd 2位 | 源寄存器 Rs 2位 |
双字节指令:
操作码 OP 4位 | 目的寄存器 Rd 2位 | 源寄存器 Rs 2位 |
立即数字段 8位 |
2、指令集
本CPU模型有13条指令,其中0-9为单字节指令,10-13为双字节指令。
使用Verilog实现的时候,会在内存之中预先设置好执行的指令,然后模拟仿真,观察结果。
序号 | 指令助记符 | 功能 | 操作码 | 举例 | 机器码 |
0 | IN Rd | 输入 IN←Rd | 0000 | IN R2 | 0000 10 00 |
1 | OUT Rs | 输出 OUT←Rs | 0001 | OUT R1 | 0001 00 01 |
2 | MOV Rd, Rs | 寄存器传输 Rd←Rs | 0010 | MOV R1,R2 | 0010 10 01 |
3 | ADD Rd, Rs | 加运算 Rd←Rs+Rd 并设置Cy,Zero标志 | 0011 | ADD R3,R0 | 0011 11 00 |
4 | AND Rd, Rs | 与运算 Rd←Rs & Rd 并设置Zero标志 | 0100 | AND R1,R0 | 0100 01 00 |
5 | MUL | 原码一位乘法运算 {HIGH,LOW}←RD*RS | 0101 | MUL RD RS | 0101 01 10 |
6 | STI | 开中断 | 0110 | STI | 0110xxxx |
7 | CLI | 关中断 | 0111 | CLI | 0111xxxx |
8 | IRET | 中断返回 | 1000 | IRET | 1000xxxx |
9 | HLT | 停机 | 1001 | HLT | 1001xxxx |
10 | LDI | Rd←立即数 | 1010 | LDI R1 59 | 101001xx 01011001 |
11 | LAD | 读内存Rd←MEM | 1011 | LAD R1 | 10110100 |
12 | STA | 写内存MEM←Rs | 1100 | STA R2 | 11001000 |
13 | JMP target | 无条件转移 PC←地址 | 1101 | JMP 59 | 110101xx 01011001 |
14 | JC target | 条件转移,有进位时转移 如果 FC= =1’b1, PC←立即数,程序实现转移。 否则 不修改PC,程序顺序执行。 | 1110 | JC 50 | 11100000 01010000 |
三、CPU结构
结构图
说明
ALU运算器:
当ALU_B为1时,ALU输出,否则处于高阻态
S1、S0控制ALU的运算种类
FC进位标志寄存器:
当做加法指令时,进位保存在FC中
用于条件跳转指令的判断条件。
MAR地址寄存器、A、B运算暂存器:
输出没有三态控制,即只要输入到寄存器,输出就有值了。
程序计数器PC:
当LDPC为1时,在时钟上升沿,接收数据。
当INC_PC为1时,在时钟上升沿,实现PC+1。
当PC_B为1时,输出数据。否则高阻态。
内存:
/CE=1 /WE=x,不操作。
/CE=0 /WE=0 写内存;/CE=0 /WE=1 读内存。
读内存,由内存到MDR,再由MDR到总线。
寄存器IR:
寄存器R3~R0:
以R0为例:当R_B为1时,R输出(根据指令判断),
否则处于高阻态。
当LDR0为1时,在时钟上升沿,接收数据。
四、 时序
分为两个节拍
T1:在T1上升沿,微程序控制器工作,设置微指令各字段的值。根据各字段的值,设置微控制信号;各微控制信号,控制各寄存器传输到总线BUS。
T2:在T2的上升沿,当LDXXX的信号有效时,将数据从总线输入到寄存器中
五、微程序控制器
1、微指令格式
运算器 2位 | 向总线输出 3位 | 从总线输入 3位 | 下地址 6位 |
S1 S0 | XXX_B | LDXXX | uMA |
2、字段说明
XXX_B为1时,XXX部件输出到总线上。
LDXXX为1时,当T2上升沿到来时,将总线上的数据输入到XXX部件。
LDXXX字段
|
|
|
|
0 | 0 | 0 | NOP |
0 | 0 | 1 | LDA |
0 | 1 | 0 | LDB |
0 | 1 | 1 | LDR |
1 | 0 | 0 | LDOUT |
1 | 0 | 1 | LDMAR |
1 | 1 | 0 | LDIR |
1 | 1 | 1 | LDPC |
XXX_B字段
|
|
|
|
0 | 0 | 0 | NOP |
0 | 0 | 1 | ALU_B |
0 | 1 | 0 | R_B |
0 | 1 | 1 | PC_B |
1 | 0 | 0 | STI |
1 | 0 | 1 | CLI |
1 | 1 | 0 | MEM_B |
1 | 1 | 1 | IN_B |
C字段
|
|
|
|
0 | 0 | 0 | NOP |
0 | 0 | 1 | P<1> |
0 | 1 | 0 | P<2> |
0 | 1 | 1 | P<3> |
1 | 0 | 0 | P<4> |
1 | 0 | 1 | P<5> |
1 | 1 | 0 | 保留 |
1 | 1 | 1 | 保留 |
3、微程序流程图
六、Verilog HDL实现源码
module CPU(clk,reset,interrupt,T1,T2,PC,MAR,IR,uMA,A,B,ALU,R0,R1,R2,R3,LDR,LDIR,BUS,FC);
input clk,reset,interrupt;
output T1,T2,PC,MAR,IR,uMA,A,B,ALU,R0,R1,R2,R3,LDR,LDIR,BUS,FC;
reg[7:0] MEM0,MEM1,MEM2,MEM3,MEM4; //内存中的普通程序
reg[7:0] R0,R1,R2,R3,ALU,A,B,PC,BUS,MAR,IR;
reg[1:0] S; // 2位ALU控制字段
reg[2:0] LDXXX,XXX_B; // 三个控制字段
reg[5:0] uMA; // 6位微地址字段
//T1时刻直接XXX_B、设置LDXXX控制信号, T2时刻根据LDXXX信号 从BUS传数据
reg LDA,LDB,LDR,LDPC,LDOUT,LDMAR,LDIR,INC_PC,FC; // 微控制信号
reg T1;
wire T2;
//产生时序T1 T2;初始内存中的机器指令
always @(posedge clk)
begin
if(reset)
begin
T1 <= 1'b0;
//内存初始赋值(输入机器指令)MEM
MEM0 <= 8'b10100000; //立即数传值->R0
MEM1 <= 8'b10000000;
MEM2 <= 8'b10100100; //立即数传值->R1
MEM3 <= 8'b10000000;
MEM4 <= 8'b00110001; //R0+R1 ->R0
end
else
//设置时序
T1 <= ~T1;
end
//设置时序
assign T2=~T1;
//T1 设置微代码各字段
always @(posedge T1)
begin
if(reset)
uMA <= 6'b000000;
else
begin
case(uMA)
6'h00:
begin
S <= 2'b00;
XXX_B <= 3'b101;
LDXXX <= 3'b000;
INC_PC <= 1'b0;
uMA <= 6'h01;
end
6'h01:
begin
S <= 2'b00;
XXX_B <= 3'b000;
LDXXX <= 3'b000;
INC_PC <= 1'b0;
uMA <= 6'h02;
end
6'h02:
begin
S <= 2'b00;
XXX_B <= 3'b011;
LDXXX <= 3'b101;
INC_PC <= 1'b1;
uMA <= 6'h03;
end
6'h03:
begin
S <= 2'b00;
XXX_B <= 3'b110;
LDXXX <= 3'b110;
INC_PC <= 1'b0;
uMA <= 6'h04;
end
6'h04:
begin
S <= 2'b00;
XXX_B <= 3'b000;
LDXXX <= 3'b000;
INC_PC <= 1'b0;
case({IR[7],IR[6],IR[5],IR[4]})
4'b0011:
uMA <= 6'h08; //ADD
4'b1010:
uMA <= 6'h16; //LDI
endcase
end
6'h08: //RD->A
begin
S <= 2'b00;
XXX_B <= 3'b010;
LDXXX <= 3'b001;
INC_PC <= 1'b0;
uMA <= 6'h09;
end
6'h09://RS->B
begin
S <= 2'b00;
XXX_B <= 3'b010;
LDXXX <= 3'b010;
INC_PC <= 1'b0;
uMA <= 6'h0A;
end
6'h0A: //A+B->RD
begin
S <= 2'b01;
XXX_B <= 3'b001;
LDXXX <= 3'b011;
INC_PC <= 1'b0;
uMA <= 6'h01;
end
6'h16: //LDI
begin
S <= 2'b00;
XXX_B <= 3'b011;
LDXXX <= 3'b101;
INC_PC <= 1'b1;
uMA <= 6'h17;
end
6'h17:
begin
S <= 2'b00;
XXX_B <= 3'b110;
LDXXX <= 3'b011;
INC_PC <= 1'b0;
uMA <= 6'h01;
end
endcase
end
end
//设置每字段的控制信号
always @(S or LDXXX or XXX_B)
begin
//ALU运算控制
case(S)
2'b00:
begin
ALU <= ALU;
end
2'b01:
begin
{FC,ALU} <= A + B;
end
endcase
// A字段控制 LDXX
case(LDXXX)
3'b000:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0000000;
end
3'b001:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b1000000;
end
3'b010:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0100000;
end
3'b011:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0010000;
end
3'b100:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0001000;
end
3'b101:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0000100;
end
3'b110:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0000010;
end
3'b111:
begin
{LDA,LDB,LDR,LDOUT,LDMAR,LDIR,LDPC} <= 7'b0000001;
end
endcase
// B字段控制 XX_B
case(XXX_B)
3'b000:
begin
BUS <= BUS;
end
3'b001:
begin
BUS <= ALU;
end
3'b010:
begin
case({IR[1],IR[0]})
2'b00:
BUS <= R0;
2'b01:
BUS <= R1;
2'b10:
BUS <= R2;
2'b11:
BUS <= R3;
endcase
end
3'b011:
begin
BUS <= PC;
end
3'b100:
begin
STI <= 1'b1;
CLI <= 1'b0;
end
3'b101:
begin
STI <= 1'b0;
CLI <= 1'b1;
end
3'b110:
begin
case(MAR)
8'h00:
BUS <= MEM0;
8'h01:
BUS <= MEM1;
8'h02:
BUS <= MEM2;
8'h03:
BUS <= MEM3;
8'h04:
BUS <= MEM4;
endcase
end
3'b111:
BUS <= IN;
endcase
end
endmodul
七、仿真
MEM0 <= 8'b10100000; //立即数传值->R0
MEM1 <= 8'b10000000;
MEM2 <= 8'b10100100; //立即数传值->R1
MEM3 <= 8'b10000000;
MEM4 <= 8'b00110001; //R0+R1 ->R0
八、结语
本文只实现了简单的功能加法、立即数传值指令,介绍如何使用Verilog 模拟微程序控制器,进而实现一个简单CPU的功能。实现微程序流程图中的所有指令的源代码,在我的GitHub 之中,欢迎大家参考。