PIC16C5X系列CPU
PIC16C5X系列的基本介绍
PIC16C5X是美国Microchip推出的世界上第一种8脚的超小型单片机系列,体积虽小但拥有很多功能特点,节省了很多其他单片机应用中必须外接的元器件,所以它是目前最便宜的8位OPT单片机。主要点特如下:
采用RISC,仅33条指令,指令字长为12位。除了涉及PC值改变的指令外,其余指令均为单周期指令。在本设计中,单周期为3个时钟周期,即两级流水线设计。
系统为哈佛结构。数据总线与指令总线各自独立。当一条指令在ALU中执行时,下一条指令已经被取出放到指令寄存器中等待执行了。
内部有7个特殊功能的寄存器,以操作I/O接口,设置看门狗和定时器的参数等。
系统可进入睡眠模式。功耗很低。
12~20根双向可独立的编程I/O口。考虑到兼容所有系列,故本设计有20个I/O接口,A口4位,B口8位,C口8位。
手册给出的工作频率为约20MHz,本设计基于sim13工艺库,工作频率可达50MHz。
PIC16C5X系列的引脚以及功能
引脚
功能描述
RA0~RA3
I/O口,双向可编程
RB0~RB7
I/O口,双向可编程
RC0~RC7
I/O口,双向可编程
MCLR
复位脚,低电平有效
VDD
电源
VSS
地
T0CKI
外部时钟输入
OSC1
振荡输入
OSC2
振荡输出
N/C
未用
注:RTCC设置成内部定时器时(由程序设定),这时应将RTCC端接VSS或VDD,以避免干扰。采用RC振荡时,OSC2端输出一OSC1的4分频信号。
PIC16C5X系列的内部结构
PIC16C5X在一个芯片上集成了一个8位算术逻辑单元ALU和工作寄存器(W),以承担算数逻辑操作任务;384~2K的12位程序存储器ROM,在本设计中ROM并未实现,而是采用简单的办法,将指令信息放在测试文件中,设计中合理的改变PC的值就好了;80个8位的数据寄存器RAM;20个I/O端口;8位计数器及预分频器;时钟、复位以及看门狗计数器等。
PIC16C5X提供二级堆栈,故子程序的条用仅有两层
PIC16C5X系列的指令集
内部功能设计思路及代码
二级堆栈
always @(posedge Clk)
begin
if(POR)
TOS <= 12'h000; // 重新上电,堆栈清0
else if(CE)
TOS <= (CALL ? PC : (RETLW ? NOS : TOS)); //CALL指令,PC置入栈顶;RETLW下,栈顶推出,栈底变栈顶
end
always @(posedge Clk)
begin
if(POR)
NOS <= 12'h000;
else if(CE)
NOS <= (CALL ? TOS : NOS); //子程序调用,栈顶存储新的PC数据,原本栈顶给的数据推到栈底
end
二级堆栈的实现方式如上述代码,大体思路为:
在系统重新上电时,堆栈的数据全部清零,即堆栈中的数据初始为0。
程序正常运行过程中, 当主程序中出现CALL指令时,栈顶储存此时主程序的PC,栈底值仍为0。
当子程序内部存在CALL指令时,栈底存储之前栈顶的值,即主程序的PC;栈顶存储此时第一级子程序的PC,保证子程序嵌套的正常运行。
当子程序返回时,原本栈底的存储的信息顶入栈顶,栈底信息并未清0。实际上,依据上述的代码设计,仅有第二级子程序返回的时候,栈顶的数据会变化。
看门狗
assign WDT_Rst = Rst | WDTClr; //WDT计数复位信号,全局复位或者看门狗清零引起。
always @(posedge Clk)
begin
if(WDT_Rst)
WDT <= 0;
else if (WDTE) //WDTE为WDT使能信号。清0可以使WDT不再计数
WDT <= WDT + 1; //WDT计数
end
always @(posedge Clk)
begin
if(WDT_Rst)
WDT_TC <= 0; //复位下,WDT溢出信号清零
else
WDT_TC <= &WDT; //WDT计时达到最大,输出WDT_TC为1,表示溢出
end
看门狗的实现方式如上述代码,主要分为两种,即WDT正常计数以及WDT溢出,系统重置。实现的大体思路如下:
系统的复位以及看门狗清零信号都会引起WDT的计数清零,同时溢出信号无效(为低)。
WDTE信号置低,以关闭WDT的功能。
正常计数情况下,WDT每个时钟周期自动加1,即使系统进入低功耗,即SLEEP模式也不例外。
可通过位与操作判断WDT是否计满,仅当WDT计数计满时,溢出信号为高,系统重置。
实时时钟/计数器 RTCC(Real Time Clock/Count)
RTCC寄存器为PIC的操作寄存器F1,它用于对外加在RTCC引脚上的脉冲计数,或对内部时钟计数(起定时器作用)。OPTION特殊寄存器以配置RTCC和WDT的各种参数,包括分频参数,计数信号源,计数边沿等。RTCC计数器采用递增方式计数,当计数至FF时,在下一个计数发生后,将自动复零,重新开始计数,以此一直循环下去。
always @(posedge Clk)
begin
if(POR)
TMR0 <= 0;
else if(WE_TMR0)
TMR0 <= DO;
else if(CE_Tmr0)
TMR0 <= TMR0 + 1;
end
RTCC的实现方式如上述代码,具体实现思路如下:
系统复位时,RTCC清零;
RTCC可以外部置数。通过对F1寄存器的写,可以将数据写入这个寄存器中;
当复位和写功能均无效时,根据OPTION寄存器的配置,RTCC可以完成相应的计数功能。CE_Tmr0信号的作用在于选择所配置的信号进行累加。
由于RTCC寄存器依赖于OPTION寄存器的配置,故有必要说明OPTION寄存器的设置方法:
assign T0CS = OPTION[5]; // 选择RTCC时钟源: 1 - 外部触发, 0 - 内部指令时钟
assign T0SE = OPTION[4]; // RTCC的触发源: 1 - 下降沿, 0 - 上升沿
assign PSA = OPTION[3]; // 预分频对象选择: 1 - WDT, 0 - RTCC
assign PS = OPTION[2:0]; // 预设分频参数: RTCC - 2^(PS+1), WDT - 2^PS
assign Tmr0_CS = (T0CS ? T0CKI_Pls : CE); // T0CS 为1选择外部计数脉冲,为0选择内部时钟
assign Rst_PSC = (PSA ? WDTClr : WE_TMR0) | Rst; //WDT清零和操作F1都会产生重置信号
assign CE_PSCntr = (PSA ? WDT_TC : Tmr0_CS);
always @(posedge Clk)
begin
if(Rst_PSC)
PSCntr <= 8'b0;
else if (CE_PSCntr)
PSCntr <= PSCntr + 1;
always @(*)
begin
case (PS)
3'b000 : PSC_Out <= PSCntr[0];
3'b001 : PSC_Out <= PSCntr[1];
3'b010 : PSC_Out <= PSCntr[2];
3'b011 : PSC_Out <= PSCntr[3];
3'b100 : PSC_Out <= PSCntr[4];
3'b101 : PSC_Out <= PSCntr[5];
3'b110 : PSC_Out <= PSCntr[6];
default: PSC_Out <= PSCntr[7];
endcase
end
always @(posedge Clk)
begin
if(POR)
dPSC_Out <= 0;
else
begin
dPSC_Out[0] <= PSC_Out;
dPSC_Out[1] <= PSC_Out & ~dPSC_Out[0]; //前一个信号右推一个时钟周期,做上升沿的统计
end
end
assign PSC_Pls = dPSC_Out[1];
always @(posedge Clk)
begin
if(Rst)
dT0CKI <= 3'b0;
else
begin
dT0CKI[0] <= T0CKI; // 外部时钟和内部时钟的最小公倍数为dT0CKI[0]的周期
dT0CKI[1] <= dT0CKI[0]; // dT0CKI[1]右推一个内部时钟周期
dT0CKI[2] <= (T0SE ? (dT0CKI[1] & ~dT0CKI[0]) // dT0CKI[1]下降沿出现脉冲
:(dT0CKI[0] & ~dT0CKI[1])); // dT0CKI[1]上升沿出现脉冲
end
end
assign T0CKI_Pls = dT0CKI[2];
assign CE_Tmr0 = (PSA ? Tmr0_CS : PSC_Pls);
上述代码即为OPTION寄存器的解码和相应的RTCC以及WDT的配置。
第一段将OPTION寄存器的功能位相应解出,从上到下作用分别为:选择信号源T0CS;选择信号触发源T0SE;定时器和WDT选择位PSA;分频参数PS。详细可见手册。
第二段则用简单的连续赋值语句和条件操作符完成选择信号源、选择RTCC和WDT的功能。注意到:考虑到WDT清零和RTCC的置数功能,有必要添加计数复位的情况,即Rst_PSC信号。
第三段则实现内部时钟的计数功能,以为后面的信号分频做准备。
第四段则为分频的处理,用case语句描述了信号分频的8种情况,最终从PSC_Out中得到对应的分频信号。
第五段则是用以选择分频信号的脉冲,将上一段得到的PSC_OutT做出相位差,即dPSC_Out[0]信号比PSC_Out信号晚一个时钟周期,在通过一定的逻辑操作统计分频信号的上升沿。最终通过PSC_Pls输出对应的内部时钟的脉冲。
第六段则是对外部输入信号的处理,复位情况下,外部的信号的输入寄存清零;正常运行时,通过周期的推迟和一定的逻辑操作,T0SE选择外部信号上升沿或者下降沿做统计,输出相应的外部时钟的脉冲信号T0CKI_Pls。
最后一段则为RTCC的使能信号的选择。当PSA信号为1时,RTCC被选择。F1寄存器上的信息即为内部或者外部信号分频后的统计脉冲。当PSA信号为0时,WDT被选择。F1寄存器上的信息即为内部时钟的分频信息。
再编码加速
本设计针对指令采用了译码-再编码-再译码的加速操作,可以提高运算速度。
第一段,针对指令表的译码操作,其中参数为局部变量定义,此处省略。
assign dNOP = (OP_NOP == IR[11:0]);
assign dMOVWF = (OP_MOVWF == IR[11:5]);
assign dCLRW = (OP_CLRW == IR[11:0]);
assign dCLRF = (OP_CLRF == IR[11:5]);
assign dSUBWF = (OP_SUBWF == IR[11:6]);
assign dDECF = (OP_DECF == IR[11:6]);
assign dIORWF = (OP_IORWF == IR[11:6]);
assign dANDWF = (OP_ANDWF == IR[11:6]);
assign dXORWF = (OP_XORWF == IR[11:6]);
assign dADDWF = (OP_ADDWF == IR[11:6]);
assign dMOVF = (OP_MOVF == IR[11:6]);
assign dCOMF = (OP_COMF == IR[11:6]);
assign dINCF = (OP_INCF == IR[11:6]);
assign dDECFSZ = (OP_DECFSZ == IR[11:6]);
assign dRRF = (OP_RRF == IR[11:6]);
assign dRLF = (OP_RLF == IR[11:6]);
assign dSWAPF = (OP_SWAPF == IR[11:6]);
assign dINCFSZ = (OP_INCFSZ == IR[11:6]);
assign dBCF = (OP_BCF == IR[11:8]);
assign dBSF = (OP_BSF == IR[11:8]);
assign dBTFSC = (OP_BTFSC == IR[11:8]);
assign dBTFSS = (OP_BTFSS == IR[11:8]);
assign dOPTION = (OP_OPTION == IR[11:0]);
assign dSLEEP = (OP_SLEEP == IR[11:0]);
assign dCLRWDT = (OP_CLRWDT == IR[11:0]);
assign dRETLW = (OP_RETLW == IR[11:8]);
assign dCALL = (OP_CALL == IR[11:8]);
assign dGOTO = (OP_GOTO == IR[11:9]);
assign dMOVLW = (OP_MOVLW == IR[11:8]);
assign dIORLW = (OP_IORLW == IR[11:8]);
assign dANDLW = (OP_ANDLW == IR[11:8]);
assign dXORLW = (OP_XORLW == IR[11:8]);
assign dTRISA = (OP_TRISA == IR[11:0]);
assign dTRISB = (OP_TRISB == IR[11:0]);
assign dTRISC = (OP_TRISC == IR[11:0]);
assign dErr = ~|{ dNOP, dMOVWF, dCLRW, dCLRF,
dSUBWF, dDECF, dIORWF, dANDWF,
dXORWF, dADDWF, dMOVF, dCOMF,
dINCF, dDECFSZ, dRRF, dRLF,
dSWAPF, dINCFSZ,
dBCF, dBSF, dBTFSC, dBTFSS,
dOPTION, dSLEEP, dCLRWDT, dRETLW,
dCALL, dGOTO, dMOVLW, dIORLW,
dANDLW, dXORLW, dTRISA, dTRISB,
dTRISC};
第二段,指令归类,便于后续的在编码。大体可有算数指令,逻辑指令,移位指令,位操作,与W寄存器有关的操作,与数据寄存器有关的操作,间址寻址操作,跳操作,写数据寄存器的操作,写W寄存器的操作。
assign dAU_Op = |{dSUBWF, dDECF, dADDWF, dINCF, dMOVF, dDECFSZ, dINCFSZ};
assign dLU_Op = |{dCOMF, dIORWF, dANDWF, dXORWF};
assign dSU_Op = |{dRRF, dRLF, dSWAPF};
assign dBP_Op = |{dBCF, dBSF, dBTFSC, dBTFSS};
assign dLW_Op = |{dCLRW, dRETLW, dMOVLW, dIORLW, dANDLW, dXORLW};
assign dFile_En = |{dMOVWF, dCLRF, dAU_Op, dLU_Op, dSU_Op, dBP_Op};
assign dINDF = dFile_En & (IR[4:0] == pINDF);
assign dTst = |{dDECFSZ, dINCFSZ, dBTFSC, dBTFSS};
assign dWE_F = |{dBP_Op, ((dAU_Op | dLU_Op | dSU_Op) & IR[5]), dMOVWF, dCLRF};
assign dWE_W = |{dLW_Op, ((dAU_Op | dLU_Op | dSU_Op) & ~IR[5])};
第三段,再编码,使后续运算并行,提高处理速度。代码中附注释。
assign dALU_Op[ 0] = (dBP_Op ? IR[5] : |{dSUBWF, dINCF, dINCFSZ,
dIORLW, dXORLW,
dIORWF, dXORWF,
dRLF, dSWAPF});
assign dALU_Op[ 1] = (dBP_Op ? IR[6] : |{dSUBWF, dDECF, dDECFSZ,
dANDWF, dXORWF, dANDLW, dXORLW,
dRRF, dRLF});
assign dALU_Op[ 2] = (dBP_Op ? IR[7] : |{dSUBWF, dADDWF, dMOVWF,
dIORWF, dANDWF, dXORWF,
dIORLW, dANDLW, dXORLW});
assign dALU_Op[ 3] = |{dBSF, dBTFSS, dCALL, dRETLW,
dMOVLW, dIORLW, dANDLW, dXORLW};
assign dALU_Op[ 4] = |{dSUBWF, dADDWF, dRRF, dRLF};
assign dALU_Op[ 5] = |{dCLRW, dCLRF, dSUBWF, dDECF, dADDWF, dINCF, dMOVF,
dIORWF, dANDWF, dXORWF, dIORLW, dANDLW, dXORLW};
assign dALU_Op[ 6] = dBP_Op | dLU_Op | dIORLW | dANDLW | dXORLW;
assign dALU_Op[ 7] = dBP_Op | dSU_Op | dMOVWF | dCLRW | dCLRF;
assign dALU_Op[ 8] = dTst;
assign dALU_Op[ 9] = dINDF;
assign dALU_Op[10] = dWE_W;
assign dALU_Op[11] = dWE_F;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// ALU Implementation - ALU[3:0] are overloaded for the four ALU elements:
// Arithmetic Unit, Logic Unit, Shift Unit, and Bit
// Processor.
//
// ALU Operations - Arithmetic, Logic, and Shift Units
//
// ALU_Op[1:0] = ALU Unit Operation Code
//
// Arithmetic Unit (AU): 00 => Y = A + B;
// 01 => Y = A + B + 1;
// 10 => Y = A + ~B = A - B - 1;
// 11 => Y = A + ~B + 1 = A - B;
//
// Logic Unit (LU): 00 => V = ~A;
// 01 => V = A & B;
// 10 => V = A | B;
// 11 => V = A ^ B;
//
// Shift Unit (SU): 00 => S = W; // MOVWF
// 01 => S = {A[3:0], A[7:4]}; // SWAPF
// 10 => S = {C, A[7:1]}; // RRF
// 11 => S = {A[6:0], C}; // RLF
//
// ALU_Op[3:2] = ALU Operand:
// A B
// 00 => File 0
// 01 => File W
// 10 => Literal 0
// 11 => Literal W;
//
// ALU Operations - Bit Processor (BP)
//
// ALU_Op[2:0] = Bit Select: 000 => Bit 0;
// 001 => Bit 1;
// 010 => Bit 2;
// 011 => Bit 3;
// 100 => Bit 4;
// 101 => Bit 5;
// 110 => Bit 6;
// 111 => Bit 7;
//
// ALU_Op[3] = Set: 0 - Clr Selected Bit;
// 1 - Set Selected Bit;
//
// ALU_Op[5:4] = Status Flag Update Select
//
// 00 => None
// 01 => C
// 10 => Z
// 11 => Z,DC,C
//
// ALU_Op[7:6] = ALU Output Data Multiplexer
//
// 00 => AU
// 01 => LU
// 10 => SU
// 11 => BP
//
// ALU_Op[8] = Tst: 0 - Normal Operation
// 1 - Test: INCFSZ/DECFSZ/BTFSC/BTFSS
//
// ALU_Op[9] = Indirect Register, INDF, Selected
//
// ALU_Op[10] = Write Enable Working Register (W)
//
// ALU_Op[11] = Write Enable File {RAM | Special Function Registers}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
第四段,再编码后的译码操作,后译码需结合各种运算单元。
// 算术指令操作数
assign C_In = ALU_Op[0]; // 加法的进位输入
assign B_Inv = ALU_Op[1]; // B操作数取反,完成减法
assign A_Sel = ALU_Op[3]; // A操作数选择
assign A = A_Sel ? KI : DI; // A_Sel为高,选择立即数,A_Sel为低,数据寄存器中的数据
assign B_Sel = ALU_Op[2]; // B操作数选择
assign B = B_Sel ? W : 0; // B_Sel为高,选择W操作寄存器中的数,B_Sel为低,数据置0
assign Y = B_Inv ? ~B : B; // B是取反相,为减法做准备
// 算术单元
assign {DC_In, X[3:0]} = A[3:0] + Y[3:0] + C_In;
assign {C_Out, X[7:4]} = A[7:4] + Y[7:4] + DC_In;
// 逻辑单元
assign LU_Op = ALU_Op[1:0];
always @(*)
begin
case (LU_Op)
2'b00 : V <= ~A;
2'b01 : V <= A | B;
2'b10 : V <= A & B;
default : V <= A ^ B;
endcase
end
// 移动和交换单元
assign S_Sel = ALU_Op[1:0];
always @(*)
begin
case (S_Sel)
2'b00 : S <= B;
2'b01 : S <= {A[3:0], A[7:4]};
2'b10 : S <= {C, A[7:1]};
default : S <= {A[6:0], C};
endcase
end
// 位操作单元
assign Bit = ALU_Op[2:0]; //位选
assign Set = ALU_Op[3]; //置1还是清0
assign Tst = ALU_Op[8]; //是否测试跳
// 位选
always @(*)
begin
case(Bit)
3'b000 : Msk <= 8'b0000_0001;
3'b001 : Msk <= 8'b0000_0010;
3'b010 : Msk <= 8'b0000_0100;
3'b011 : Msk <= 8'b0000_1000;
3'b100 : Msk <= 8'b0001_0000;
3'b101 : Msk <= 8'b0010_0000;
3'b110 : Msk <= 8'b0100_0000;
default : Msk <= 8'b1000_0000;
endcase
end
assign U = Set ? (DI | Msk) : (DI & ~Msk); // 对DI指定位进行置位
assign T = DI & Msk; //测试DI的指定位,如果为1,且Msk也为1,则T中仅有一个1,反之T全0
assign g = Tst ? (Set ? |T : ~|T) : 1'b0;
//如果是跳指令,则1跳的话,对T进行位或,可得指定位,
//反之0跳,T比全0,需要位或后取反。存在一个Bug,BCFSS与BSFSS并不置位!!!
// 算术单元的输出
assign D_Sel = ALU_Op[7:6];
always @(*)
begin
case (D_Sel)
2'b00 : DO <= X;
2'b01 : DO <= V;
2'b10 : DO <= S;
default : DO <= U;
endcase
end
// 写W寄存器
assign WE_W = CE & ALU_Op[10];
always @(posedge Clk)
begin
if(POR)
W <= 8'b0;
else if(CE)
W <= (WE_W ? DO : W); // 写使能信号有效下,将ALU输出或者地址上的数据写入W
end
// 状态位Z的操作
assign Z_Sel = ALU_Op[5];
assign Z_Tst = ~|DO; //DO全零下,Z状态应该为高。
always @(posedge Clk)
begin
if(POR)
Z <= 1'b0;
else if(CE)
Z <= (Z_Sel ? Z_Tst : (WE_PSW ? DO[2] : Z)); //Z的置位分两种情况,数据输出全0或者写PSW
end
// 状态位DC操作
assign DC_Sel = ALU_Op[5] & ALU_Op[4];
always @(posedge Clk)
begin
if(POR)
DC <= 1'b0;
else if(CE)
DC <= (DC_Sel ? DC_In : (WE_PSW ? DO[1] : DC));
end
// 状态位C的操作
assign C_Sel = ALU_Op[4]; //算数加减引起C位变化
assign S_Dir = ALU_Op[1] & ALU_Op[0]; //移位引起的状态C的变化,11左移,不是11右移
assign C_Drv = (~ALU_Op[7] & ~ALU_Op[6]) ? C_Out : (S_Dir ? A[7] : A[0]);
always @(posedge Clk)
begin
if(POR)
C <= 1'b0;
else if(CE)
C <= (C_Sel ? C_Drv : (WE_PSW ? DO[0] : C));
end
// 跳操作
always @(*)
begin
Skip <= WE_SLEEP | WE_PCL
| (Tst ? ((&ALU_Op[7:6]) ? g : Z_Tst)
: ((GOTO | CALL | RETLW) ? 1'b1 : 1'b0 ));
// Sleep和写F2都会使PC跳过下一条指令
// g为BCFSS和BSFSS引起的跳,Z_Tst为INCFSZ和DECFSZ引起的跳
// GOTO CALL和RETLW引起跳过下条指令
end
// 间址寻址
assign INDF = ALU_Op[9];
assign FA = (INDF ? FSR : (KI[4] ? {FSR[6:5], KI[4:0]} : {2'b0, KI[4:0]}));
设计理解结语
这个PIC16C5X系列CPU的设计是基于Github上Michael A. Morris的源代码,理解并稍作修改而成。作为eda课程的作业以及初入数字IC设计的第一个完整代码,在各种设计思想上都有很大的益处。不同于雷思磊在【自己动手写CPU】中较为明确的模块拆分和复杂情况,PIC CPU在本设计仅有一个模块,可读性上也许更好,因人而异吧。而考虑到独特的再编码加速设计,也对于Verilog的设计有一定的启发作用,不同于本科接触的仅考虑实现前仿的功能,数字IC的涉及出发点还应该是基于DC综合的电路方法,以优化IC面积和时序的方式的角度去设计。
本设计代码基本实现了PIC16C5X系列手册的要求,33条指令,特殊的操作寄存器,看门狗,定时器,二级堆栈等等。具体到细节内部,有些地方仍缺乏理解,会在之后详细的考虑之后做出修改和更新。
第一版 2019.1.1