编码的奥秘:两种典型的微处理器

转自:《编码的奥秘》    第十九章

            微处理器—集成计算机中央处理器( C P U)的所有组件在一个硅芯片上—诞生于1 9 7 1年。它的产生有一个很好的开端:第一个微处理器是Intel 4004,其中有2 3 0 0个晶体管。今天,差不多3 0年过去了,为家用计算机所制造的微处理器中将近有10 000 000个晶体管。

            微处理器实际的作用基本上保持不变。现在的芯片上附加的上百万个晶体管可以做许多有趣的事情,但在微处理器最初的探索过程中,这些事情更多的是分散我们的注意力而不是给我们以启迪。为了对微处理器的工作情况获得更清晰的认识,让我们先看一下最初的微处理器。

           这些微处理器出现在1 9 7 4年。在该年度, I n t e l公司在4月推出了8 0 8 0,M o t o r o l a(摩托罗拉)—从2 0世纪5 0年代开始生产半导体和晶体管产品的公司—在8月份推出了6 8 0 0。它们并非是那年仅有的微处理器。同样是在1 9 7 4年,德克萨斯仪器公司推出了4位的TMS 1000,用在许多计算器、玩具和设备上; National Semiconductor(国家半导体公司)推出了PA C E,它
是第一个1 6位的微处理器。然而,回想起来, 8 0 8 0和6 8 0 0是两个最具有重大历史意义的芯片。

            I n t e l设定8 0 8 0最初价格为$ 360,这是对IBM System/360的一个讽刺。IBM System/360是一个大型机系统,由许多大公司使用,要花费几百万美元。(今天,你只花$ 1 . 9 5就可以买到一个8 0 8 0芯片。)这并不是说8 0 8 0可以与S y s t e m / 3 6 0相提并论,但不用几年, I B M公司也将会正视这些很小的计算机。

           8 0 8 0是一个8位的微处理器,有6 0 0 0个晶体管,时钟频率为2 M H z,可寻址6 4 K B的存储空间。6 8 0 0(今天也只卖$ 1 . 9 5)有大约4 0 0 0个晶体管,也可寻址6 4 K B的存储空间。第1代6 8 0 0的时钟频率为1 MHz,但到1 9 7 7年M o t o r o l a公司发布新款的6 8 0 0时,其时钟频率已为1 . 5 M H z和2 MHz。

           这些芯片称作“单芯片微处理器”,不太准确的名称为“一个芯片上的计算机”。处理器只是整个计算机的一部分。此外,计算机至少还需要一些随机访问存储器( R A M)、供人们输入信息到计算机的方法(输入设备),供人们从计算机获取信息的方法(输出设备),以及其他可把所有这些东西连接在一起的芯片。这些组件将在第2 1章详细介绍。

           从现在起,我们只考察微处理器。描述微处理器时,通常是用框图来画微处理器的内部组件及它们是如何连接的。但在第1 7章已有够多的图了,现在,我们将通过观察处理器与外界的相互作用来了解它的内部。换句话说,为了弄清楚它的作用,可以把微处理器看成是一个黑盒子,它的内部操作不需要做详细研究。我们可以通过测试芯片的输入和输出信号,特别是芯片的指令集来掌握微处理器的功能。 

             8 0 8 0和6 8 0 0都是4 0管脚的集成电路。这些芯片最普通的I C封装大约是2英寸长,半英寸宽,1 / 8英寸厚:

             当然,你看到的只是外包装。位于其内部的硅晶片非常小,就拿早期的8位微处理器来说,其硅晶片小于1 / 4平方英寸。外包装保护硅晶片并通过管脚提供对芯片的输入和输出点的访问。下图显示了8 0 8 0的4 0个管脚的功能:

扫描二维码关注公众号,回复: 4420562 查看本文章

             本书的所有电气或电子设备都需要某种电源供电。8 0 8 0的一个特别之处在于它需要三种电源电压:管脚2 0必须连到5伏电源上,管脚11连到- 5伏电源上,管脚2 8连到1 2伏电源上;管脚2接地(1 9 7 6年,I n t e l发布了8 0 8 5芯片,简化了这些电源需求)

             其余管脚都画有箭头。从芯片中出来的箭头表示输出信号,这是由微处理器控制的信号,计算机中其余芯片对此作出响应。指向芯片的箭头表示输入信号,这是来自于其他芯片的信号,8 0 8 0对它们做出响应。有些管脚既是输入又是输出。

             第1 7章的处理器需要振荡器使它工作。8 0 8 0需要两个不同的2 MHz同步时钟输入,在2 2和1 5管脚上分别标记为。这些信号可以很方便地由I n t e l公司生产的8 2 2 4时钟信号发生器提供。给这个芯片连上一个18 MHz的石英晶体,剩下的工作它基本上可以完成。

             一个微处理器通常有多个输出信号来寻址存储空间,这种信号的数目与微处理器可寻址的存储器空间的大小直接相关。8 0 8 0有1 6个地址信号,标为A 0~A 1 5,具有寻址即65 536字节的存储空间的能力。

            8 0 8 0是一个8位微处理器,一次可从存储器中读出、写入8位数据。它包括8个数据信号D0~ D7,这些信号是在此芯片中仅有的几个既作为输入又作为输出的信号。当微处理器从存储器读数据时,这些管脚作为输入;当微处理器向存储器写数据时,这些管脚作为输出。

           微处理器的另外1 0个管脚是控制信号。例如, R E S E T输入用来复位微处理器。输出信号-W R表示微处理器要向R A M中写数据。(-WR信号对应于R A M阵列的写入输入。)另外,当芯片读取指令时,其他控制信号会在某个时候出现在D0~ D7管脚。由8 0 8 0构成的计算机系统通常使用8 2 2 8系统控制芯片来锁存这些附加的控制信号。后面将会讲述一些控制信号。由于8 0 8 0的控制信号非常复杂,因此,除非你想基于8 0 8 0芯片来实际设计计算机,否则最好不要用这些控制信号来折磨自己。

           假定8 0 8 0微处理器连接了6 4 K B的存储器,这样可以不通过微处理器来读写数据。

           8 0 8 0芯片复位后,它从存储器的地址0 0 0 0 h处读取该字节,送到微处理器中。这可以通过在地址信号端A0~A1 5输出1 6个0来实现。它读取的字节必须是8 0 8 0指令,这种读取字节的过程叫作取指令。

           在第1 7章构造的计算机里,所有指令(除了停止指令H LT)都是3个字节长,包括一个操作码和两个字节的地址。在8 0 8 0中,指令长度可以是1个字节、2个字节或3个字节。有些指令可使8 0 8 0从存储器的某一位置处读出一个字节送到微处理器中;有些指令可使8 0 8 0从微处理器中把数据写入存储器的某一位置处;其他指令可使8 0 8 0不使用R A M而在内部执行。第一条
指令执行完后, 8 0 8 0访问存储器中的第二条指令,依此类推。这些指令组合在一起构成一个计算机程序,用来完成一些自己感兴趣的事情。

          当8 0 8 0运行在最高速度即2 MHz时,每个时钟周期为5 0 0纳秒( 1除以2 000 000周等于0 . 0 0 0 0 0 0 5 0 0秒)。第1 7章中的每条指令都需要4个时钟周期, 8 0 8 0的每条指令则需要4~1 8个时钟周期,这意味着每条指令的执行时间为2~9微秒(即百万分之一秒)。

         了解微处理器功能的最好方法可能是在系统方式下测试其完整的指令集。

         第1 7章最后出现的计算机仅有1 2条指令。一个8位微处理器很容易就有2 5 6条指令,每个操作码对应于某个8位值。(如果一些指令有2个字节的操作码,则实际会有更多的指令)。8 0 8 0虽没有那么多,但它也有2 4 4条操作码。这看起来似乎很多,但总的来说,却又不比第1 7章中的计算机功能多多少。例如,如果想用8 0 8 0做乘法或除法,仍然需要写一段小程序来实现。

         第1 7章中讲过,处理器指令集的每个操作码都和某个助记符相联系,有些助记符之后可能还有操作数。但这些助记符仅用来方便地表示操作码。处理器只读取字节,它并不懂组成这些助记符的字符的含义。

         第1 7章中的计算机有两条很重要的指令,称作装载( L o a d)和保存(S t o r e)指令。这些指令都占用三个字节的存储空间。装载指令的第一个字节是操作码,操作码后的两个字节表示1 6位地址。处理器把在此地址中的字节送到累加器。同样,保存指令把累加器中的内容存储到指令指定的地址处。

         下面,我们用助记符来简写这两个操作:

           在此,A表示累加器(装载指令的目的操作数,保存指令的源操作数),a a a a表示一个1 6位的存储器地址,通常用4位十六进制数来表示。 

          8 0 8 0中的8位累加器称作A,就像第1 7章中的累加器。正如第1 7章中的计算机一样, 8 0 8 0也有两条与装载和保存指令功能一样的指令。8 0 8 0中这两条指令的操作码为3 2 h和3 A h,每个操作码后有一个1 6位地址。8 0 8 0的助记符为S TA(代表存储累加器的内容)和L D A(代表装载到累加器):

             除了累加器,8 0 8 0微处理器内部还包括6个寄存器( r e g i s t e r),每个寄存器可以保存8位的值。这些寄存器和累加器非常相似,事实上,累加器被看作是一种特殊的寄存器。和累加器一样,这6个寄存器也是锁存器。处理器可以把数据从存储器传送到寄存器,也可以把数据从寄存器送回到存储器。然而,这些寄存器没有累加器的功能强大。例如,当两数相加时,其
结果通常送往累加器而非其中一个寄存器。

            在8 0 8 0中,除累加器外的6个寄存器的名字分别为B,C,D,E,H和L。人们通常问的第一个问题是“用F和G干什么?”,第二个问题是“用I,J和K又要做什么?”,答案是使用寄存器H和L具有某种特殊的含义。H代表高(H i g h),L代表低( L o w )。通常把H和L的8位合起来记作H L来表示一个1 6位寄存器对,H作为高位字节,L作为低位字节。这个1 6位值通常用来寻
址存储器。后面我们将看到它的简单用法。

             所有这些寄存器都是必需的吗?为什么不在第1 7章中的计算机中用到它们呢?从理论上说,它们并非必需,但是使用它们会很方便。许多计算机程序在同一时刻要用到几个数据,如果所有这些数据都存储在微处理器的寄存器中而非存储器中,执行程序将会更快,因为程序访问存储器的次数越少,那么它的运行速度也就越快。

             8 0 8 8指令中,有一个至少6 3个指令码供一条8 0 8 0指令使用的指令,它就是M O V指令,即M o v e的简写。该条指令只有一个字节,用于把一个寄存器中的内容传送到另一个寄存器中(或同一个寄存器中)。使用大量M O V指令是设计带有7个寄存器(包括累加器)的微处理器的正常结果。

             下面是前3 2条M O V指令。记住目的操作数在左边,源操作数在右边:

            这些都是很方便的指令。当一个寄存器中有值时,可以把它传送到其他寄存器中。注意,上述指令中有四条指令用到H L寄存器对,如:

           

            前面列出的L D A指令把一个字节从存储器中传送到累加器中,这个字节的1 6位地址直接跟在L D A操作码的后面。这里的M O V指令把一个字节从存储器中传送到寄存器B中,但被装载到寄存器中的字节的地址是保存在寄存器对H L中。H L寄存器是怎样得到1 6位存储器地址的呢?它可以通过多种方法来实现,或许是通过某种方法计算出来的。

            总之,这两条指令

           都把一个字节从存储器中装载到微处理器中,但它们用两种不同的方法来寻址存储器地址。第一种方法叫作直接寻址方式,第二种方法叫作间接寻址方式。

            第二批3 2条M O V指令表明用H L寻址的存储器地址也可以作为目的操作数:

            其中一些指令如:

           做的是无用的事,而像:

           这样的指令是不存在的。和这条指令相对应的操作码实际上是停止指令。

           观察这些M O V操作码更明显的方法是考察它的位模式, M O V操作码由8位组成:

            其中字母ddd 代表指代目的操作数的3位代码,s s s代表指代源操作数的3位代码。这3位代码是:

           例如,指令:

           相应的操作码表示为0 11 0 1 0 11,或6 B h。可以通过检查前面的表来验证。 

           因此可能在8 0 8 0内部某个地方,标有s s s的3位标识用在8 - 1数据选择器中,标有d d d的3位标识用于控制3 - 8译码器,此译码器用来决定哪一个寄存器锁存了一个值。

           也可能使用寄存器B和C来构成一个1 6位寄存器对B C,用寄存器D和E来构成一个1 6位寄存器对D E。如果每一个寄存器对都包含用于装载或保存一个字节的存储器地址,则可以使用下述指令:

            另一种类型的传送指令叫做传送立即数,指定的助记符为M V I。传送立即数指令占两个字节,第一个是操作码,第二个是1个字节的数据。此字节从存储器中传送到一个寄存器中或由H L寻址的存储单元中。

            例如,当指令:

             执行后,寄存器E中包含有字节3 7 h。这就是第三种寻址方式,即立即数寻址方式。

             3 2个操作码的集合完成四种基本算术运算,那是在第1 7章开发处理器时我们就已熟悉的运算,即加法( A D D)、进位加法( A D C)、减法( S U B)和借位减法( S B B)。所有情况中,累加器是两个操作数之一,也是结果的目的地址。

            假设A中是3 5 h ,寄存器B中是2 2 h ,当指令:

            执行后,累加器中的结果为1 3 h。

            若A中的值为3 5 h ,寄存器H中的值为1 0 h,L中的值为7 C h,存储器地址1 0 7 C h中的值为4 Ah,则指令:

            把累加器中的内容( 3 5 h)和通过寄存器对H L寻址得到的值( 4 A h)相加,并把结果(7 F h)保存到累加器中。

            A D C和S B B指令允许8 0 8 0加/减1 6位、2 4位、3 2位和更多位的数。例如,假设寄存器对B C和D E都包含1 6位数,你想把它们相加,并把结果存到B C中。下面是具体做法:

           其中有两条加法指令, A D D指令用于低位字节相加, A D C指令用于高位字节相加。第一条加法指令的进位位包含在第二条加法指令中。因为只能利用累加器进行加法运算,所以在这么短的代码中也需要至少4条M O V指令。许多M O V指令常常出现在8 0 8 0代码中。

           该是谈论8 0 8 0标志位的时候了。在第1 7章的处理器中,已有进位标志位C F和零标志位Z F。8 0 8 0还有3个标志位,即符号标志位S F、奇偶标志位P F和辅助进位标志位A F。所有标志位都保存在另一个叫作程序状态字( P S W:program status word)的8位寄存器中。像L D A、S TA和M O V这样的指令不影响标志位,而A D D、S U B、A D C和S B B指令却要影响标志位,影响的方式如下:

           • 当运算结果最高位为1时,符号标志位S F为1,表示结果为负。
           • 当结果为0时,零标志位Z F为1。
           • 当运算结果中“ 1”的个数为偶数时,奇偶标志位P F = 1;当运算结果中“ 1”的个数为奇数时,奇偶标志位P F = 0。P F有时               用来粗略地检测错误,此标志位在8 0 8 0程序中不常用。
           • 当A D D或A D C运算产生进位或S U B与S B B运算不发生借位时,进位标志位C F = 1。(这点不同于第1 7章中的计算                 机进位标志的实现。)
           • 当操作结果的低4位向高4位有进位时,辅助进位标志位A F = 1。此标志位只用在D A A(十进制调整累加器)指令中。

           有两条指令直接影响进位标志位C F:

           第1 7章中的计算机可执行A D D、A D C、S U B和S B B指令(尽管没什么灵活性),但8 0 8 0还可以进行逻辑运算A N D(与)、O R(或)和X O R(异或)。算术运算和逻辑运算都可通过处理器的算术逻辑单元( A L U)来执行:

             A N D、X O R和O R指令按位运算,即逻辑操作只是单独地在对应位之间进行。例如:

            累加器中的结果将为0 5 h。如果第三条指令为O R运算,则结果为5 F h;如果第三条指令为X O R运算,则结果为5 A h。

            C M P(比较)指令与S U B指令基本上一样,除了结果不保存在累加器中。换句话说,C M P执行减法操作再把结果扔掉。这是为什么?是因为标志位。根据标志位的状态可知道所比较的两数之间的关系。例如,当如下指令: 

             执行完时,A中的内容没有改变。然而,若A中的值为2 5 h,则Z F标志置位;若A中的值小于2 5 h,则CF = 1。 

              这8个算术逻辑运算也可以对立即数进行操作:              例如,上面列出的两条指令也可以用下面的指令来替换:

               下面是其他两条8 0 8 0指令:

              C M A即complement accumulator,它对累加器中的值进行取反操作。每个0变为1,每个1变为0。如果累加器中的值为0 11 0 0 1 0 1,C M A指令使它变为1 0 0 11 0 1 0。也可以用下述指令来使累加器按位取反: 

                

              D A A即Decimal Adjust Accumulator,如前所述,它可能是8 0 8 0中最复杂的一条指令。微处理器中有一个完整的小部件专门用于执行这条指令。

             D A A指令帮助程序员用B C D码表示的数来进行十进制算术运算。在B C D码中,每一小块数据的范围在0 0 0 0~1 0 0 1之间,对应于十进制的0~9。利用B C D码格式,每8位字节可存储两个十进制数字。

              假设累加器中的值为B C D码的2 7 h。由于是B C D码,则它实际上指的是十进制的2 7。(十六进制的2 7 h等于十进制的3 9。)再假定寄存器B中的值为B C D码的9 4 h。如果执行指令:

              

               累加器中的值将为BB h,当然,它不是B C D码,因为B C D码中的每一块不能超过9。但是,现在执行指令:

               则累加器中的值为2 1 h,且CF = 1,这是因为2 7和9 4的十进制和为1 2 1。如果想进行B C D码的算术运算,这样做是相当方便的。

                经常需要对一个数进行加1或减1操作。在第1 7章的乘法程序中,我们需要对一个数减1,使用的方法是加上FFh ,它是- 1的2的补码。8 0 8 0中包含特殊的用于寄存器或存储单元的加1指令(称作增量)和减1指令(称作减量):

               单字节指令I N R和D C R可影响除C F外的所有标志位。

               8 0 8 0也包含4个循环移位指令,这些指令可使累加器中的内容左移或右移1位:

              这些指令只影响C F。

             假定累加器中的值为A 7 h,即二进制的1 0 1 0 0 111。R L C指令使A中的内容向左移位,最高位(移出顶端)成为最低位(移进底端),同时决定进位标志位C F的状态。其结果为0 1 0 0 1111且CF = 1。R R C指令用同样的方法向右移位。开始为1 0 1 0 0 111,执行R R C指令后,其结果为11 0 1 0 0 11且CF = 1。 

             R A L和R A R指令有些不同。当向左移位时, R A L指令把C F移入累加器的最低位,而把最高位移入C F中。例如,如果累加器的内容为1 0 1 0 0 111,CF = 0,R A L指令执行的结果是累加器的内容变为0 1 0 0 111 0,且CF = 1。同样,在相同的初始条件下, R A R指令使累加器的内容变为0 1 0 1 0 0 11,CF = 1。

             对于乘2(左移1位)和除2(右移一位)操作,移位指令非常方便。

             把微处理器寻址的存储器叫作随机访问存储器( R A M)是有原因的:微处理器可以简单地根据提供的地址访问某一存储位置。R A M就像一本书一样,我们可以打开它的任何一页。它并不像做在微缩胶片上的一个星期的报纸,要找到周六版,需扫过大半周。同样,它也不同于磁带,要播放磁带上的最后一首歌需快进整个一面。微缩胶片和磁带的存储不是随机访问的,而是顺序访问的。

             R A M确实效果不错,对于微处理器来说更是如此。但在使用存储器时有所差别是有好处的,下面就是一种既非随机又非顺序访问的存储方式:假定你在一个办公室里,人们到你桌前给你分配工作,每个工作都需要某种文件夹。通常你会发现你在继续某项工作之前,必须使用另外一个文件夹先做一些相关的工作。因此你把第一个文件夹放在桌子上,又拿出第二个文件夹放在它上面进行工作。现在又有一个人来让你做一个优先权高于前面工作的工作,你拿来一个新文件夹放在那两个上面继续工作。而此项工作又需要另外一个文件夹,这样在你的桌子上很快就摆了一堆文件夹了。

             注意,这个堆非常明确地、有序地保存了你正在做的工作的轨迹。最上面的文件夹总是最高优先权的工作,去掉这个以后,下一个肯定是你就要做的,如此类推。当你最终去掉了桌子上的最后一个文件夹后(你开始的第1项工作),你就可以回家了。

             以这种方式工作的存储器技术叫做作堆栈( s t a c k)。从底向上压入堆栈,从顶向下弹出堆栈,因此这也叫后进先出存储器,或L I F O。最后放入堆栈中的数据最先被取出,最先放入堆栈中的数据最后被取出。 

             计算机中也可以使用堆栈,不是用来保存工作而是用来存储数据,且已被证明使用起来非常方便。向堆栈中放入数据叫作p u s h(压入),从堆栈中取走数据叫作p o p(弹出)。

             假定你正在用汇编语言设计程序,程序中使用了寄存器A、B和C。但在编程过程中,你发现此程序需要去做另一件事—一个小的计算,其中也要使用寄存器A,、B、C。而你最终要回到先前的程序,并使用A、B、C中原有的值。

             当然,你能做的工作只是简单地把寄存器A、B、C中的值保存到存储器中的不同位置,以后再把这些位置的值装载到寄存器中,但这样做需要保存值被保存的位置。一个显然的方法是把寄存器压入堆栈:

             一会儿再解释这些指令的作用。现在,我们只需要知道它们以某种方式把寄存器的内容保存在一个后进先出的存储器中。一旦这些语句执行了,你的程序就可以毫无顾虑地利用这些寄存器来做其他工作。为了得到原来的值,只需简单地按与压入堆栈相反的顺序把它们从堆栈中弹出即可,如下所示:

              记住是后进先出。如果用错了P O P语句的顺序,就会引起错误。

              堆栈机制的一个好处在于一个程序的不同部分都可以使用堆栈而不会出现问题。例如,在把A、B、C压入堆栈中后,程序的其他部分还可能需要把寄存器C、D、E的内容压入堆栈:

             接着,这一部分程序所要做的就是在第一部分弹出C、B和A之前,用下述方法恢复寄存器的值:

            堆栈是怎样实现的呢?首先,堆栈只是不被别的东西使用的正常的R A M的一部分。8 0 8 0微处理器包含一个特殊的1 6位寄存器来对这一部分存储器进行寻址,这个1 6位寄存器叫作堆栈指针。 

            这里举的压入和弹出寄存器的例子对于8 0 8 0来说不太准确。8 0 8 0的P U S H指令实际上是存储1 6位的值到堆栈, P O P指令用来恢复它们。因此8 0 8 0不用像PUSH C和POP C这样的指令,它有下述8条指令:

             PUSH BC指令把寄存器B和C的内容保存到堆栈中, POP BC指令恢复它们。最后一行的缩写P S W指的是程序状态字,前面讲过,它是包含有标志位的8位寄存器。最后一行的两条指令实际上是把累加器和P S W都压入和弹出堆栈。如果你想保存所有寄存器和标志位的内容,可以使用: 

            当以后想恢复这些寄存器的内容时,按相反的顺序使用P O P指令:

            堆栈是怎样工作的呢?假设堆栈指针为8 0 0 0 h,PUSH BC指令将引起下面这些情况发生:

            • 堆栈指针减1至7 F F F H
            • 寄存器B的内容保存在堆栈指针所指的地址处,即7 F F F h处

            • 堆栈指针减1至7 F F E H
            • 寄存器C的内容保存在堆栈指针所指的地址处,即7 F F E h处。当堆栈指针仍然为7 F F E h时,POP BC指令执行,用来                反向执行每一步:
            • 从堆栈指针所指的地址(即7 F F E h)处装载数据到寄存器C中
            • 堆栈指针增1至7 F F F h
            • 从堆栈指针所指的地址(即7 F F F h)处装载数据到寄存器B中
            • 堆栈指针增1至8 0 0 0 h 

            对每个P U S H指令,堆栈都会增加2个字节,这可能导致程序出现小毛病—堆栈可能会变得很大以致会覆盖掉程序所需的一些代码和数据。这就是堆栈上溢问题。同样,过多的P O P指令会过早用光堆栈内容,这就是堆栈下溢问题。

           如果8 0 8 0同一个6 4 K B的存储器连接,你可能想把初始堆栈指针置为0 0 0 0 h。第一条P U S H指令使地址减1变为F F F F h,这时堆栈占用了存储器的最高地址。如果你的程序放在从0 0 0 0 h处开始的存储器区域,则它和堆栈离的就太远了。

           对堆栈寄存器进行赋值的指令是L X I,即load extended immediate(装载扩展的立即数)。下面这些操作码后的指令也是把两个字节装载到1 6位寄存器:

      

                  L X I指令保存一个字节。另外,上表中最后一条L X I指令用来对堆栈指针赋值。微处理器复位后,这条指令并不常用来作为首先执行的指令之一:

                 

                 也可以对寄存器对和堆栈指针执行加1和减1操作,就好像它们是1 6位寄存器一样:

                 即然是在讨论1 6位指令,可以看看更多一些这样的指令。下面的指令是把1 6位寄存器对的内容加到寄存器对H L中:

               上面这些指令可节约几个字节。例如,第一条指令正常需要6个字节:

             D A D指令通常用于计算存储器地址,这条指令只影响C F。 

             下一步让我们看以下各种指令。下面的两个操作码后都紧跟着一个2字节地址,分别保存和装载寄存器对H L的内容:

             寄存器L的内容保存在地址a a a a处,寄存器H的内容保存在地址a a a a + 1处。 

             下面两条指令用来从寄存器对H L中装载程序计数器P C或堆栈指针S P:

             P C H L指令实际上是一种转移指令, 8 0 8 0执行的下一条指令保存在H L寄存器对中的地址所对应的存储单元中。S P H L是另外一个设置S P的方法。

             下面两条指令中,第一条指令使H L的内容与堆栈中最上面的两个字节进行交换,第二条指令使H L的内容与寄存器对D E的内容进行交换:

              除了P C H L外,还没有讲过8 0 8 0的转移指令。前面第1 7章中讲过,处理器中有一个叫作程序计数器P C的寄存器, P C中包含处理器取回并执行的指令的存储器地址。通常P C使处理器顺序执行存储器中的指令,但有些指令—通常命名为J u m p(转移)、B r a n c h(分支)或g o t o(跳转)—能使处理器偏离这个固定的过程。这些指令使得P C装载另外的值,处理器所取的下一条指令将在存储器的其他位置。

              尽管简单、普通的转移指令很有用,但条件转移指令更有用。这些指令可使处理器根据某些标志,如C F或Z F,来转移到另外的地址处。条件转移指令的存在使得第1 7章中的自动加法机成为一般意义上的数字计算机。

             8 0 8 0有5个标志位,其中4个对条件转移指令有用处。8 0 8 0支持9种不同的转移指令,包括无条件转移指令和基于Z F、C F、P F、S F是1还是0的条件转移指令。

            在介绍这些指令之前,先介绍一下与此相关的另外两种指令。第一个是C a l l(调用)指令。C a l l指令与J u m p指令的不同之处在于:前者把一个新值装入到程序计数器P C中,处理器保存P C中原来的地址,保存在哪里?当然,在堆栈中。

            这种策略意味着C a l l指令可有效地保存“程序从哪里跳转”的标记。处理器最终可利用此地址返回到原来的位置。这个返回指令叫R e t u r n。R e t u r n指令从堆栈中弹出两个字节,并把该值装载到P C中。

            C a l l和R e t u r n指令是任何处理器中都很重要的功能。它们允许程序员编写子程序,子程序是程序中经常用到的代码段。(“经常”一般意味着“不止一次”。)子程序是汇编语言中的基本组成部分。

            让我们看一个例子。假设你正在编写一个汇编语言程序,并且需要使两个数相乘,因此你可以写出一段代码来做这项工作,然后继续往下编写程序,现在又需要使两个数相乘。因为你已知道如何进行两数相乘,因此你只需简单地重复使用同样的指令来完成它。但只是简单地两次把这些指令输入到存储器吗?希望不是,这是对时间和存储空间的浪费,更好的方法是转送到原来的代码处。由于无法返回到程序的当前位置,所以一般的J u m p指令不能用。但使用C a l l和R e t u r n指令可以让你完成所需的功能。

           进行两数相乘的一组指令可以作为一个子程序。下面就是这样的子程序。在第1 7章中,被乘数(和结果)存放在存储器的某一地址中;而在8 0 8 0子程序中,寄存器B的值和寄存器C中的值相乘,然后把1 6位乘积装入寄存器H L中:

           注意,上述子程序的第1行开始有一个标号M u l t i p l y。当然,这个标号对应于子程序所在的存储器地址。子程序开始用了两个P U S H指令,通常在子程序开始处应先保存(以后恢复)它需要使用的寄存器。 

          然后该子程序把H和L寄存器置为0。虽然可以使用M V I指令而不用S U B指令,但那需要使用4个字节的指令而不是2个字节的指令。子程序执行完后,寄存器对H L中保存有相乘的结果。

         下一步该子程序把寄存器B的内容(乘数)移入A中,并且检查它是否为0。如果它为0,乘法子程序到此结束,因为结果为0。由于寄存器H和L已经为0,因而子程序可以只使用J Z指令跳转到末端的两个P O P指令处。

         否则,子程序把寄存器B置为0。现在,寄存器对B C中包含一个1 6位的被乘数,A中为乘数。D A D指令把B C(被乘数)加到H L(结果)中。A中的乘数减1,且只要它不为0,J N Z指令就又使B C加到H L中。此小循环继续下去,直到B C加到H L中的次数等于乘数。(可以用8 0 8 0的移位指令编写一个更有效的乘法子程序。)

          利用这个子程序完成2 5 h与1 2 h相乘的程序用下面的代码:

         C a l l指令把P C的值保存在堆栈中,该值是C a l l指令的下一条指令的地址。然后, C a l l指令使程序转移到标号M u l t i p l y所标识的指令,即子程序的开始。当子程序计算完结果后,执行R E T(返回)指令,即从堆栈中弹出程序计数器的值,程序继续执行C a l l指令后面的语句。

          8 0 8 0指令集中包括条件C A L L指令和条件R e t u r n指令,但它们远不如条件转移指令用得多。下表中完整地列出了这些指令:

           你可能知道,存储器并不是唯一连接在微处理器上的设备。一个计算机系统通常需要输入输出设备以便于实现人机通信。输入输出设备通常包括键盘和显示器。 

          微处理器是怎样与外围设备(对于连接到微处理器而不是存储器的东西的称呼)进行通信的呢?外围设备具有与存储器相似的接口,微处理器可通过对应于外设的具体地址来对外设进行读写。在有些微处理器中,外围设备实际上占用了通常用来寻址存储器的地址,这种配置叫作内存映像I / O。然而在8 0 8 0中,在65 536个正常地址外还有2 5 6个附加地址专门为输入输出设备预留,这些就是I / O端口(I/O Port)。I / O地址信号为A0~A7,但I / O访问与存储器访问不同,由8 2 2 8系统控制芯片锁存的信号来区分。

          O U T指令用于把累加器中的数据写到紧跟该指令的字节所寻址的I / O端口中。I N指令把端口的数据读入到累加器中。

          外围设备有时需要引起微处理器的注意。例如,当你在键盘上按键时,如果微处理器能马上知道这件事通常是有帮助的。这由称作中断(i n t e r r u p t)的机制来完成,这是连接至8 0 8 0 I N T输入端的,由外设产生的信号。

          然而,当8 0 8 0复位时,它不能对中断产生响应。程序必须通过执行E I(Enable interrupts)指令来允许中断,通过执行     D I(Disable Interrupts)指令来禁止中断。

         

           8 0 8 0的 I N T E输出端信号表明允许中断。当外设需要中断微处理器当前工作时,它把 8 0 8 0的 I N T输入端设置为 1 。 8 0 8 0通过从存储器中取出指令对它作出响应,但控制信号表明有中断发生。外设通常通过提供下述指令之一来响应 8 0 8 0:

           以上称作 R e s t a r t指令,它们与 C A L L指令相似,也需要把当前程序计数器的值压入堆栈。但R e s t a r t指令随后转移到一个特定的位置: RST 0 转移到地址 0000h 处, RST 1 转移到地址0 0 0 8 h处等等,直到 RST 7转移到地址 0 0 3 8 h处。位于这些地址中的代码段来处理中断。例如,来自键盘的中断引起 RST 4 指令执行,地址 0 0 2 0 h处的一些代码从键盘读取数据(这将在第 2 1章做全面介绍)。 

           到此为止,已讲述了 2 4 3 个操作码。下述 1 2个字节与任何操作码无关: 0 8 h、 1 0 h、 1 8 h、2 0 h、 2 8 h、 3 0 h、 3 8 h、 C B h、 D 9 h、 D D h、 E D h和F D h。这样总共有 2 5 5个操作码。下面还要提到一个操作码:

           N O P代表 no op,即 no operation (无操作) 。 N O P指令使微处理器什么都不做。这有什么作用吗?用于填空。 8 0 8 0通常可以执行一批 N O P指令而不会有任何坏情况发生。

           以下不打算再详细讨论 Motorola 6800 ,因为它的设计与功能与 8 0 8 0非常相似。下面是6 8 0 0的4 0个管脚:

             VS S代表接地, VC C是5 V电源。与 8 0 8 0相似, 6 8 0 0有1 6个地址输出信号和既可作为输入又可作为输出的 8个数据信号。它有 R E S E T信号和 R /-W信号。 -I R Q信号代表中断请求 。 6 8 0 0的时钟信号比 8 0 8 0的更加简单。 6 8 0 0没有 I / O端口的概念,所有输入输出设备都必须是 6 8 0 0存储器地址空间的一部分。

             6 8 0 0有一个 1 6位程序计数器 P C、一个 1 6位堆栈指针 S P、一个 8位状态寄存器(作为标志)以及两个 8位累加器 A和B。它们都被看成是累加器( B不是只作为一个寄存器)是因为没有能用 A来做而不能用 B来做的事。 6 8 0 0没有附加的 8位寄存器。

           6 8 0 0中有一个 1 6位索引寄存器( index register),可用来保存一个 1 6位地址,很像 8 0 8 0中的寄存器对 H L。对于许多指令来说,它们的地址都可以由索引寄存器和紧跟在操作码后的地址之和得到。

           虽然 6 8 0 0和 8 0 8 0所实现的操作相同— 装载、保存、加法、减法、移位、转移、调用,但很明显的区别是:它们的操作码和助记符完全不同。例如,下面是 6 8 0 0的分支转移指令:

            6 8 0 0没有像 8 0 8 0中那样的奇偶标志位 P F,但它有一个 8 0 8 0中没有的标志位—溢出标志位( overflow flag)。上述转移指令中有些依赖于标志位的组合。

            当然 8 0 8 0和 6 8 0 0指令集是不同的,这两个芯片是同一时间由不同的两个公司的两组不同的工程师设计的。这种不兼容性意味着每一种芯片不能执行对方的机器代码,为一种芯片开发的汇编语言程序也不能翻译成可在另一种芯片上执行的操作码。编写可在多于一种处理器上执行的计算机程序是第 2 4章的主题。

             8 0 8 0和 6 8 0 0还有一个有趣的不同点:在两种微处理器中, L D A指令都是从一个特定的地址处装载到累加器。例如,在 8 0 8 0中,下列字节序列:
           将把存储在地址 3 4 7 B h处的字节装载到累加器。现在把上述指令与 6 8 0 0的L D A指令相比较,后者采用称作 6 8 0 0的扩展地址模式:

           该字节序列把存储在地址 7B34h 处的字节装载到累加器 A中。

           这种不同点是很微妙的。当然,你也可能认为它们的操作码不同:对 8 0 8 0来说是 3 A h,对6 8 0 0来说是 B 6 h。但主要是这两种微处理器处理紧随操作码后的地址是不同的, 8 0 8 0认为低位在前,高位在后; 6 8 0 0则认为高位在前,低位在后。

           这种 I n t e l和 M o t o r o l a微处理器保存多字节数时的根本不同从没有得到解决。直到现在,I n t e l微处理器在保存多字节数时,仍是最低有效字节在前(即在最低存储地址处);而M o t o r o l a微处理器在保存多字节数时,仍是最高有效字节在前。

            这两种方法分别叫作 l i t t l e - e n d i a n ( I n t e l方式)和big-endian (Motorola方式)。辩论这两种方式的优劣可能是很有趣的,不过在此之前,要知道术语 b i g - e n d i a n来自于 Jonathan Swift的《 G u l l i v e r’s Tr a v e l s》,指的是 Lilliput 和B l e f u s c u在每次吃鸡蛋前都要互相碰一下。这种辩论可能是无目的的。先不说哪种方法在本质上说是不是正确的,但这种差别的确当在基于 l i t t l e endian 和b i g - e n d i a n机器的系统之间共享信息时会带来附加的兼容性问题。

            这两种微处理器后来怎样了呢? 8 0 8 0用在一些人所谓的第一台个人计算机上,不过可能更准确的说法是第一台家用计算机上。下图是 Altair 8800 ,出现在 1 9 7 5年1 月份的《 P o p u l a rE l e c t r o n i c s》杂志的封面上。

           当你看到 Altair 8800 时,前面面板上的灯泡和开关看起来似乎很熟悉。这和第 1 6章为64KB RAM阵列建议的初始“控制面板”的界面是同一类型的。

            8 0 8 0之后出现了 Intel 8085 ,更具意义的是出现了 Z i l o g制造的 Z - 8 0芯片。 Z i l o g是Intel 公司的竞争对手,是由 I n t e l公司的前雇员,也曾在 4 0 0 4芯片上做出重要贡献的 Federico Faggin建立的。 Z - 8 0与8 0 8 0完全兼容,且增加了许多很有用的指令。 1 9 7 7年, Z - 8 0用于 Radio ShackTRS-80 Model1 上。

            也是在1 9 7 7年,由Steven Jobs 和Stephen Wo z n i a k建立的苹果计算机公司推出了 APPLE II。APPLE II 既不用8080也不用 6 8 0 0,而是使用了采用 M O S技术的更便宜的 6 5 0 2芯片,它是对6 8 0 0的增强。

            1 9 7 8年6月, I n t e l公司推出了 8 0 8 6,一个 1 6位微处理器,它可访问的存储空间达到 1 M B。8 0 8 6的操作码与        8 0 8 0不兼容,但它包含乘法和除法指令。一年后, I n t e l公司又推出了 8 0 8 8,其内部结构与 8 0 8 6相同,但其外部按字节访问存储器,因此该微处理器可使用较流行的为8 0 8 0设计的 8位支持芯片。 I B M在其5 1 5 0个人计算机—通常叫作 IBM PC—上使用了 8 0 8 8芯片,这种个人计算机在 1 9 8 1 年秋季推出。

            I B M进军P C市场产生了巨大影响,许多公司都发布了与 P C兼容的机器(兼容的含义在随后各章里将要详细讨论)。多年来, “ IBM PC兼容机”也暗指“ Intel inside”,特指所谓 x 8 6家族的 I n t e l微处理器。 Intel x86家族继续发展, 1 9 8 5年出现了 3 2位的 3 8 6芯片, 1 9 8 9年出现了 4 8 6芯片。 1 9 9 3年初,出现了 Intel Pentium微处理器,普遍地用在 P C兼容机上。虽然这些 I n t e l微处理器都不断增加了指令的指令集,但它们仍然支持从 8 0 8 6开始的所有以前处理器的操作码。

            苹果公司的 Macintosh 首次发布于 1 9 8 4年,它使用了 Motorola 68000—一个1 6位的微处理器,也即 6 8 0 0的下一代处理器。 6 8 0 0 0和它的后代(常称为 6 8 K系列)是制造出的最受欢迎的一类微处理器。

            从1 9 9 4年开始, M a c i n t o s h计算机开始使用 Power PC, 一种由 M o t o r o l a、 I B M和A p p l e公司联合开发的微处理器。 P o w e r P C是由一种称作 R I S C(精简指令集计算机)的微处理器体系结构来设计的,它试图通过简化某些方面以提高处理器的速度。在 R I S C计算机中,每条指令通常长度相同, (在 P o w e r P C中为 3 2位) ,存储器访问只限于装载和保存指令,且指令做简单操作而不是复杂操作。 RISC 处理器通常有大量的寄存器以避免频繁访问存储器。

            因为 P o w e r P C具有完全不同的指令集,所以它不能执行 6 8 K的代码。但现在用于 A P P L EM a c i n t o s h的 PowerPC 微处理器可仿真 6 8 K。运行于 PowerPC 上的仿真程序逐个检验 6 8 K程序的每一个操作码,并执行适当的操作。仿真程序不如 P o w e r P C自身代码那样快,但可以工作。

            按照摩尔定律,微处理器中的晶体管数量应该每 1 8个月翻一番。这些多增加的晶体管有什么用处呢?

            有些晶体管用于增加处理器的数据宽度,从 4位到8位到1 6位再到3 2位;另外一些增加的晶体管用于处理新的指令。现在大多数微处理器都有用于浮点算术运算的指令(这将在第 2 3 章解释);还有一些新增加的指令用来进行一些重复计算,以便在计算机屏幕上显示图片和电影。

            现代处理器使用了一些技术用来提高速度,其中之一是流水线技术,处理器在执行一条指令的同时读取下一条指令。由于转移指令会改变执行流程,实际上这样达不到预期效果。现在的处理器还包含一个 Cache( 高速缓冲存储器 ),它是做在处理器内部的快速 R A M阵列,用于保存最近执行的指令。因为计算机程序经常执行一些小的指令循环,因而 C a c h e可以避免这些指令重复装载。所有这些速度提升措施都需要在处理器中有更多的逻辑器件和晶体管。

            如前所述,微处理器只是完整的计算机系统的一部分(尽管是最重要的部分)。我们将在第2 1 章构造这样一个系统,但首先必须学习怎样编码存在存储器中的除了操作码和数字外的其他东西。我们必须返回到小学一年级,再学习一下怎样读写文本。

猜你喜欢

转载自blog.csdn.net/qq_32523587/article/details/84575641