指令集是处理器体系架构的重要组成部分。指令集有两个发展方面,包括以X86为代表的CISC(复杂指令集)和以ARM、MIPS为代表的RISC(精简指令集)。CISC的目标是尽可能将常用的功能用最少甚至一条指令来实现,因此该指令对应的执行电路往往是复杂的,其侧重的是硬件功能的实现;RISC则相反,其是将复杂的执行电路进行分解,即用尽可能简单的多指令去描述该功能,以软件来降低硬件的复杂度,因此RISC对编译器的要求比较高。CISC的指令长度是可变的,执行周期也不固定,而RISC则是定长的、往往都是单周期执行。寄存器多也是RISC的特点。 本文的重点不在于详细比较RISC和CISC,而是介绍CPU指令的编码和译码。我们都知道CPU的流水线执行过程有取指、译码、执行、访存和回写(参看博文:CPU指令的流水线执行),但很多人对于这些步骤的理解都仅仅在概念层面上的,有必要对其进行详细阐述,理解CPU指令的设计和实现。 我们都知道C语言是对现实问题的解决方法和过程的高度抽象,其语法主要包括数值、逻辑运算和分支控制转移。数值运算就是加减乘除(比较也是减法),逻辑运算就是与、或、非、异或等,分支控制转移包括if/else、for、while等语法。汇编语言是在机器层面上对C语言的理解和建构,其指令同样也包括数值、逻辑运算、分支控制转移,但是一行C语言可能需要多条汇编指令才能实现。同时由于指令在内存上,而CPU访问寄存器要比访问内存要快得多,所以CPU的运算一般都在寄存器中进行,因此汇编语言一般都需要增加内存和寄存器直接的数据加载/存储指令。CPU只认识二进制输入,所以可以把汇编语言当次硬件层面上的伪代码,其便于开发人员熟记,同样是低级语言。 指令的编码就是实现汇编语言到二进制机器码的过程,其是汇编器实现的(编译器是将C语言转为汇编语言)。 现在假设某种简单的CPU只支持4种功能:包括 1)加法 ADD Rd,Rs,Rn , 结果是Rd=Rs+Rn 2)减法 SUB Rd,Rs,Rn , 结果是Rd=Rs-Rn 3)数据传送 MOV Rd,Rs,结果是Rd=Rs 4)数据加载 LDR Rd,[Rs] ,结构是将内存中以Rs寄存器的值为地址取值赋给Rd 如何进行编码呢? 1)首先将指令分为操作码+操作数 两个部分,操作码即代表指令功能,如ADD、SUB等,其在CPU中就代表某种具体的电路,如ADD就代表加法电路,SUB代表减法电路;操作数即是代表功能的输入和输出,对应电路的输入和输出。 2)现在共有4种功能,那至少需要2个比特来进行编码,如00代表ADD,01代表SUB,10代表MOV,11代表LDR; 3)操作数的编码,假设寄存器共有8个,Rd,Rs,Rn都是其中的一个,即d,s,n的范围是0到7,那至少需要3个比特来编码,如000代表R0,001代表R1,以此类推,111代表R7. 那要实现以上四种功能指令,总共需要2+3+3+3=11个比特进行编码。如SUB R6,R1,R2,即R1减去R2的值赋给R7,那其编码就是01 110 001 010. 理解完指令的编码,那指令的译码应该是比较好理解的。取指就是根据当前程序计数寄存器PC(假设硬件电路规定R7就是寄存器PC)的值从内存中取出机器码指令,该机器码的值是01 110 001 010。接下来的过程就是译码,即根据最先的两个比特01送入译码器,选择为减法电路;110即译码选择为R6,001译码选择R1,010译码选择R2. 再接着的就是指令的执行了,执行就是减法单元电路对两个输入(R1,R2)进行运算,将结果赋给R6. 取指和译码都是CPU的控制单元(CU)完成的,执行是ALU(逻辑运算单元)完成,可以将ALU看出是很多种电路的集合。CPU指令的设计和指令编码息息相关。 ARM和MIPS CPU都是32位字长,因此指令编码是32比特,能够支持和表达更多的功能(操作码)和寄存器(操作数)。如ARM体系是r0到r15,因此需要4个比特来表示,当然ARM的寄存器还有组的概念,即CPU在不同的工作模式时看到的寄存器可能是不同的,如r13,r14等。这些译码时不仅需要指令操作数作为输入,还需要当前状态寄存器的值作为输入。 16位指令集是因为什么产生的呢?是因为同样的一段C语言代码,用16位指令进行编码比32位编码能够节省30%的代码量,代码量越少,那占用的内存就越少,自然成本越低。在MCU领域,一般都是成本敏感的,所以16为指令在MCU领域有非常广泛的使用。 ARM和MIPS的指令设计是以32位为基础进行设计,其执行也是32作为输入的,那如何在32位指令集中实现16位呢?我们都知道二八原理,即20%的指令的使用率会达到80%,所以我们对这20%的指令(其是32位指令的一个子集)进行编码,自然可以用较少的比特数来进行编码,而在16位指令使用时,我们可以强制要求其只使用一部分寄存器,那自然可以以更少的比特数来进行寄存器的编码。可以将16位指令集看成是32位指令集的一个子集,CPU在译码阶段先将16为指令转化为对应的32位指令,再进行译码、执行。 ARM的16位指令集为thumb指令集,MIPS的16为指令集为MIPS16指令集。至于32位指令集和16位指令集之间的无缝切换,请参看另一篇博文:32位和16位指令集模式自动切换机制。
|