PIC16C5X CPU项目总结

2019-04-15 15:17发布

PIC16C5X系列CPU

PIC16C5X系列的基本介绍

PIC16C5X是美国Microchip推出的世界上第一种8脚的超小型单片机系列,体积虽小但拥有很多功能特点,节省了很多其他单片机应用中必须外接的元器件,所以它是目前最便宜的8位OPT单片机。主要点特如下:
  1. 采用RISC,仅33条指令,指令字长为12位。除了涉及PC值改变的指令外,其余指令均为单周期指令。在本设计中,单周期为3个时钟周期,即两级流水线设计。
  2. 系统为哈佛结构。数据总线与指令总线各自独立。当一条指令在ALU中执行时,下一条指令已经被取出放到指令寄存器中等待执行了。
  3. 内部有7个特殊功能的寄存器,以操作I/O接口,设置看门狗和定时器的参数等。
  4. 系统可进入睡眠模式。功耗很低。
  5. 12~20根双向可独立的编程I/O口。考虑到兼容所有系列,故本设计有20个I/O接口,A口4位,B口8位,C口8位。
  6. 手册给出的工作频率为约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)1

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
  1. OPTION配置第六段存在仍未完全理解的问题 ↩︎