如果将自己限制为仅16条指令,那么应该选择哪一条,以及如何在不掉队的情况下进行管理?
在我从头开始构建4位HRRG(Heath Robinson,Rube Goldberg)计算机的项目的一篇专栏中,我们介绍了CPU寄存器和指令集。您可能还记得,由于我们只有4位数据总线(以及12位地址总线),因此我们选择了只有2 ^ 4 = 16条指令以及2 ^ 4 = 16个CPU寄存器。
HRRG的CPU寄存器和指令。(来源:马克斯·麦克菲尔德(Max Maxfield)
为了确保我们都敲打同一鼓音,让我们提醒自己,六个通用寄存器R0至R5用于存储数据值并“累加”任何算术或逻辑运算的结果。状态寄存器S0和S1主要用于存储任何算术或逻辑运算的状态结果,例如,相减的结果是否为零。
程序计数器(PC)用于跟踪CPU在程序中的当前位置。堆栈指针(SP)用于跟踪堆栈的顶部。索引寄存器(IX)主要用于保存计数值或用于访问内存的偏移量。中断向量(IV)用于保存称为中断服务路由(ISR)的特殊子程序类型的内存地址。
引入堆栈指针
我们将考虑所有这些小寄存器流氓如何在以后的专栏中详细介绍它们的魔力,但是如果万一这对您来说是新手,则简要描述一下SP的操作可能是一个好主意。
我们大多数人都去过自助餐厅,在该餐厅中,一堆餐盘堆叠在基于弹簧的机构上。假设您是负责将印版装入机械装置的人。我们还假设板编号为(1、2、3…),并且是一名强迫症工程师,这是将前三个板装入机械装置的顺序,如下所示:
基于Spring的自助餐厅板块存储机制(来源:Max Maxfield)
现在,假设有一个顾客进来,伸手去拿盘子。当然,它们将检索您添加到堆栈顶部的最后一块盘子(在我们的示例中为3)。在计算方面,这种形式的存储和检索将被归类为后进先出(LIFO)过程。
好吧,我们的SP的工作方式与此类似。在程序开始时,我们将使用内存中某个区域的某个位置的地址加载SP,而该内存将不会用于其他任何用途。随后,每次执行PUSH操作时,CPU会将指定的数据写入SP当前指向的存储位置(“堆栈顶部”),然后递增SP使其指向下一个空闲位置。相比之下,每次执行POP操作时,CPU都会先将SP递减以指向堆栈顶部的数据,然后从堆栈中读取该数据并将其存储在我们告诉它的任何位置。
引入6502
出于以下讨论的目的,我们将使用MOS技术6502提供比较的基础。6502于1975年推出,具有8位数据总线和16位地址总线,其寄存器包括一个8位累加器寄存器(A),两个8位索引寄存器(X和Y),一个7位寄存器。位处理器状态标志寄存器(P),8位堆栈指针(S)和16位程序计数器(PC)。
与HRRG不同,在HRRG中,我们可以用所需的任何值加载12位SP,而6502的8位SP在加电时会自动加载$ 00(请记住,我们使用“ $”字符表示十六进制值),堆栈的起始地址固定为$ 0100。这意味着6502的堆栈地址空间被限制为跨越256个地址,从$ 0100到$ 01FF。
尽管与今天的微处理器产品相比,6502看起来很简单,但是在推出之初它就被认为是非常了不起的,尤其是它的价格合理(1975年为25美元)。许多人继续基于此处理器创建令人惊叹的项目,例如此基于6502的虚拟现实(VR)系统。并且6502的新形式不断出现在现场,例如MOnSter 6502 CPU。
此外,与HRRG不同,在HRRG中,我们可以向12位中断向量(IV)加载所需的任何值,而6502则硬接线以在内存地址$ FFFE和$ FFFF中查找以检索其16位中断向量,其中这个2字节的值将由用户加载到内存中(当我们说“由用户”时,我们的意思是“由用户程序”)。
在2 ^ 8 = 256种可能的操作码(指令)中,原始6502使用151将其组织为56条指令(取决于指令),一种或多种寻址模式。根据指令和寻址方式的不同,6502操作码可能需要零个,一个或两个字节作为操作数。因此6502机器指令的长度从1到3个字节不等。
MOV(加载和存储)
6502允许用户将值从存储器加载到其累加器(A)和其索引寄存器(X和Y)中。同样,它允许用户将这些寄存器中的值存储到内存中。所有这些都需要六个指令,如下所示:
LDA(加载累加器)
LDX(加载X寄存器)
LDY(加载Y寄存器)
STA(存储累加器)
STX(存储X寄存器)
STY(存储Y寄存器)
相比之下,HRRG具有单个MOV指令,根据其操作数,该指令可用于从寄存器到寄存器,寄存器到内存,内存到寄存器以及内存到内存中移动(复制)数据。此外,这些说明适用于HRRG的所有寄存器(即使这样做没有任何意义,请参见下文)。
INC(递增)和DEC(递减)
6502允许用户在指定的存储位置或其索引寄存器(X和Y)中对值进行递增(加1)和递减(从中减去1)。为此,它需要执行以下六个指令:
INC(增加存储单元的内容)
INX(增加X寄存器的内容)
INY(增加Y寄存器的内容)
DEC(减少存储单元的内容)
DEX(减少X寄存器的内容)
DEY(递减Y寄存器的内容)
“如何增加或减少累加器的内容?” 我听到你哭了。好吧,为了用6502做到这一点,您将必须执行常规的加法或减法运算,如本专栏的稍后部分所述。
相比之下,HRRG的INC和DEC指令可用于增加内存位置以及CPU的4位和12位寄存器中任何一个的内容。
“什么?任何寄存器-甚至程序计数器?” 我听到你紧张地尖叫。是的,即使似乎没有必要,您也可以在任何寄存器上使用这些指令。例如,增加程序计数器(PC)通常被认为是一件坏事,但是HRRG允许在机器代码和底层硬件中这样做。
我们可能会在汇编器中标记某些“傻瓜”(我们将在以后的专栏中讨论),但是如果用户决定忽略并绕过汇编器发出的任何警告和/或错误消息,那么就这样吧,因为(a)在没有大量异常和特殊情况的情况下,更易于设计可工作的硬件,(b)用户可能会想到我们没有想到的狡猾的使用模型,并且(c)我们不是“明智的警察”(除了还有其他事情,我没有合适的裤子)。
ADDC和SUBB(加减法)
在简单计算机上考虑加法时,通常会考虑将两个数字加起来,例如3 + 2 =5。问题是我们可以表示的数字大小为受我们的数据总线和数据字段的宽度限制。例如,在HRRG的情况下,可以使用单个4位半字节表示0到15范围内的无符号数或-8到+7范围内的有符号数。
这显然是一个限制。幸运的是,我们可以使用多个半字节来表示我们的值。例如,在HRRG的情况下,可以使用一对4位半字节来表示0到255范围内的无符号数或-128到+127范围内的有符号数。
假设我们想将两个2点值加在一起。在这种情况下,我们将从添加两个最低有效的半字节(LSN)开始。根据它们的值,这将导致将0或1值存储在进位(C)状态标志中。当我们添加下一个对点时,我们还需要包括(添加)进位标志的内容。
一些早期的8位处理器提供了两条加法指令,例如ADD(“无进位加法”)和ADDC(“有进位加法”)。其他用户(例如6502)仅提供“带进位加法”版本,并且要由用户来实现“无带进位加法”,方法是先将0的进位标志装入然后执行加法。
同样的事情也适用于减法。在这种情况下,某些早期的8位处理器提供了两条减法指令,例如SUB(“无借位减法”)和SUBB(“无借位减法”)。诸如6502之类的其他软件仅提供“带借位减法”版本,并且要由用户来实现“无带借物减法”,方法是先将进位标志装入1,然后执行减法。
“等等,我们没有借用状态标志,”我听到你在抱怨。没错,但是在减法的情况下,进位(C)标志承担借位(B)标志的作用。基于唯一的物理标志是进位标志,一些设计人员倾向于说“减去/不携带进位”,并使用诸如SUBC助记符之类的东西,但是,在我看来,这最终导致了更多的混乱,而不是值得的。
最重要的是6502提供了以下两个说明:
ADC(带进位加)
SBC(带进位减)
此外,这些指令仅允许您将指定存储位置的内容添加/累加到累加器的内容中,结果存储在累加器中。
同样,HRRH提供以下两个说明:
ADDC(带进位加)
SUBB(带借位减)
但是,这些指令允许执行寄存器到寄存器,寄存器到内存,内存到寄存器以及内存到内存的加法和减法。(在我的下一篇专栏中,我们将考虑使用ADDC和SUBB指令来实现其ADD和SUB对应项的各种方式。)
ROLC和RORC(旋转和移位)
可能有八个基本的旋转和移位操作,我们可以为其分配助记符,如下所示:
ROL(向左旋转)
ROR(向右旋转)
ROLC(通过进位标志向左旋转)
RORC(通过进位标志向右旋转)
LSHL(逻辑左移)
ASHL(算术左移)
LSHR(逻辑右移)
ASHR(算术右移)正确的)
请记住,不同的CPUS的设计人员对这种事情使用各种不同的助记符。我上面显示的那些对我来说最有意义。现在,如果我们决定(但没有决定)在我们的4位HRRG中实现所有这8条指令,则其动作的图形表示如下所示:
各种可能的移位和旋转操作的动作(来源:Max Maxfield)
对于ROL(向左旋转),所有位都向左移动一位;同样,从概念上讲“掉落到末端”的最高有效位(MSB)被复制到最低有效位(LSB)和进位标志。相比之下,在ROR(向左旋转)的情况下,所有位都向右移一位;同样,从概念上讲“从末端掉下来”的LSB也被复制到MSB和进位标志中。
除了将进位标志的原始内容复制到LSB之外,ROLC(通过进位向左旋转)与ROL非常相似。同样,除了进位标志的原始内容被复制到MSB中外,RORC(从进位向右旋转)与ROR非常相似。
LSHL(逻辑左移)操作与ROL(左旋转)和ROLC(左移通过猫)操作非常相似,不同之处在于将0复制到LSB中。同样,LSHR(逻辑右移)操作与ROR(右移)和RORC(右移进位)操作非常相似,不同之处在于将0复制到了MSB中。
ASHL(算术左移)操作在功能上与LSHL(逻辑左移)相同-两者均导致将0复制到LSB中-因此,没有设计者会费心将它们作为单独的指令在CPU中实现。另一方面,在编写程序时,我们可能更喜欢使用两种不同的助记符作为注释形式,以提醒自己(和其他读者)我们在捕获代码时的想法。
最后,ASHR(算术右移)与LSHR(逻辑右移)类似,不同之处在于MSB(符号位)被自身复制回去(另请参见“ C / C ++>移位运算符的工作方式”)。
对于HRRG(仅限16条指令),我们决定只实施八个基本旋转和移位中的两个:
ROLC(通过进位标志向左旋转)
RORC(通过进位标志向右旋转)
我们选择这两项的原因是,很容易将它们用作实现其他指令功能的基础。(在下一篇专栏中,我们将考虑使用ROLC和RORC指令来实现其ROL,ROR,LSHL,LSHR,ASHR和ASHR对应项的各种方式。)
AND,OR,XOR和CMP(逻辑运算)
这些指令的工作方式与该星球上几乎所有其他处理器上的指令工作方式相似,因此我们在这里不会花太多时间。只需说6502的AND(逻辑与),EOR(异或)和ORA(异或)仅允许您对累加器的内容执行操作,并在内存中保存另一个值,并将结果存储在蓄能器。相比之下,HRRG的AND,OR和XOR等效项支持寄存器到寄存器,寄存器到内存以及内存到寄存器操作。
对于HRRG的CMP(比较指令),它也支持寄存器到寄存器,寄存器到内存,内存到寄存器和内存到内存操作,将比较的两个值视为是无符号的二进制值。
CLR和SET(位操作)
一些处理器提供了一组指令,可用于清除或设置状态寄存器中的各个位。例如,6502支持七种这样的指令:
CLC(清除进位标志)
CLD(清除十进制模式标志)
CLI(清除中断禁止标志)
CLV(清除溢出标志)
SEC(设置进位标志)
SED(设置十进制模式标志)
SET(设置中断禁止标志)
HRRG没有提供任何这些说明,但是如果提供了这些说明,它们的经济学原理将如下所示(正如我们所看到的,这是我疯狂的原因):
CLRN(清除负标志)
CLRZ(清除零标志)
CLRC(清除进位标志)
CLRO(清除溢出标志)
CLRI(清除中断屏蔽标志)
SETN(设置负标志)
SETZ(设置零标志)
SETC(设置进位标志)
SETO(设置溢出标志)
SETI(设置中断屏蔽标志)
SETH(设置停止标志)
观察到没有CLRH(清除暂停标志)。这是因为一旦暂停标志设置为1,重置它的唯一方法就是触发一个中断(假设中断屏蔽标志设置为1)或重置机器。
关键是我们可以使用AND和OR逻辑运算来实现所有这些指令。假设我们想将进位标志(状态寄存器S0中的位2)清除为0,我们可以通过将S1的内容与%1011进行“与”操作(请记住,我们使用'%'字符来表示二进制值)来实现。同样,如果要将进位标志设置为1,可以通过将状态寄存器S0的内容与%0100进行逻辑或运算来实现。
综上所述,在编写汇编代码时最好有位操作指令对我们可用,因此我们将在下一节中讨论如何使用汇编器将它们添加到库中。
推入和弹出(或拉出)
这些指令用于将值压入堆栈并再次弹出(或拉出)它们。在6502的情况下,有6条与堆栈相关的指令(请记住,正如我们前面所讨论的),6502的8位堆栈指针本身在上电时会自动加载$ 00。
TSX(将堆栈指针的值传输到索引寄存器X)
TXS(将索引寄存器X的内容传输到堆栈指针)
PHA(将累加器的内容推送到堆栈)
PHP(将处理器状态寄存器的内容推送到栈上)
PLA(将栈顶上的值拉到累加器中)
PLP(将栈顶上的值拉到处理器状态寄存器中)
对于HRRG,我们只有两个说明:
PUSH(将所选寄存器或存储器位置的内容推入堆栈)
POP(将堆栈顶部的值弹出到所选寄存器或存储器位置)
HRRG的指令可用于任何CPU的寄存器或存储器位置。此外,HRRG的MOV指令提供(并超过了)6502的TSX和TXS指令的功能。
JMP,JSR和相关指令
JMP(无条件跳转)指令允许CPU跳转到程序的另一部分。JSR指令告诉CPU跳转到子例程。JSR通常的工作方式是用户将所有相关信息压入堆栈,然后调用JSR。反过来,CPU将程序计数器(PC)中的返回地址压入堆栈,然后跳转到子例程。
仍在谈论这通常的工作方式,在子例程的末尾,使用RTS(从子例程返回)指令将返回地址从堆栈顶部弹出到程序计数器(PC)中,然后将我们返回主程序。程序。
还值得注意的是,中断服务程序(ISR)的作用有点类似于子程序,因为该中断将导致CPU在服务该中断之前将返回地址推入堆栈的顶部。在ISR的末尾,使用RTI(中断返回)指令将返回地址弹出堆栈顶部,然后将我们返回主程序。
6502拥有以下所有四个说明:
JMP(无条件跳转)
JSR(跳转到子程序)
RTS(从子程序返回)
RTI(从中断返回)
处理器还将支持一堆指令,这些指令将根据状态标志的状态触发跳转(或分支)。例如6502提供了八种这样的指令,如下所示:
BCC(科若进位标志清除)
BCS(科若进位标志组)
BEQ(如果科零标志集)
BMI(分公司如果负数标记组)
BNE(分公司如果零标志清除)
BPL(分公司如果负数标记清除)
BVC(
BVS(如果设置了溢出标志则分支)(如果设置了溢出标志则分支)
与6502的JMP和JSR指令允许CPU在其16位地址空间内跳转到任何地方不同,这些分支指令使用带符号的8位相对地址将控制权转移到位于前127个字节(后)和128个字节内的目标。分支指令后(之前)的字节数。程序往往会进行很多跳转,例如循环循环,因此在时钟有限的日子里,使用1字节的分支地址而不是2字节的跳转地址可能会节省大量的时间和空间。速度,处理器周期和内存位置。
对于HRRG,我们只有两个与跳转有关的指令:
JMP(无条件跳转)
JSR(跳转到子例程)
我们没有RTS或RTI指令-通过简单地从栈顶检索返回地址并将其使用POP指令加载到程序计数器(PC)中,可以达到相同的效果。
事实是,实现JMP指令的方式意味着我们可以使用它来实现与具有以下指令集相同的效果:
JMP(无条件跳转)
JMPN(无条件跳转,或“从不跳转”)*
JPN(如果为负,则跳转;如果N标志为1)
JPNN(如果为非负,则跳转;如果N标志为0),
JPZ(如果为零,则跳转;如果Z标志为1)
JPNZ(如果不为零则跳跃;如果Z标志为0)
JPC(如果进位则跳跃;如果C标志为1)
JPNC(如果不进位则跳跃;如果C标志为0)
JPO(如果溢出则跳跃;如果O标志为1)
JPNO(如果不溢出则跳转;如果O标志为0)
JPI(如果中断屏蔽则跳转;如果I标志为1)**
JPNI(如果没有中断屏蔽则跳转;如果I标志为0)**
JPH(如果暂停则跳转;如果H标志为1)***
JPNH(如果不停止则跳转;如果H标志为0)**
注意* JMPN(“永不跳转”)可用于调试目的。
注意**基于I标志为0或1或H标志为0的状态进行的跳转不是特别有用,因为程序员已经知道这些标志包含的内容(与N,Z,C和O不同)标志,其值取决于算术和逻辑运算的结果)。但是,它们是通过执行HRRG的JMP指令的方式来实现的。
注意*** JPH(如果H标志为1,则跳转)是完全没有意义的,这是因为一旦程序将此标志设置为1,CPU就会停止操作,并且只能通过触发中断来重置该标志(假定中断屏蔽标志设置为1)或通过重置机器,因此此处仅出于完整性考虑而包含此指令。
对于大多数处理器,JSR(跳转到子例程)指令的行为与JMP(无条件跳转)指令的行为类似;也就是说,没有与JPN,JPNN等等效的JSR。但是,由于HRRG的指令体系结构,我们可以使用JSR来实现与以下指令相同的效果:
JSR(无条件JSR)
JSRN(无条件JSR)*
JSN(JSR如果为负;如果N标志为1)
JSNN(JSR如果不是负;如果N标志为0)
JSZ(JSR如果为零;如果Z标志为1)
JSNZ(如果不是零,则为JSR;如果Z标志为0 ;则为JSR );如果是
JSC,如果是进位;如果C标志为1;则为
JSNC;如果不是,则为JSR;如果C标志为0,则为
JSO;如果是溢出,则为JSR;如果O标志为1,
则为JSNO。(如果没有溢出,则为JSR;如果O标志为0,则为JSR )
JSI(如果是中断屏蔽,则为JSR;如果I标志为1))**
JSNI(如果不是中断屏蔽,则为JSR;如果I标志为0)**
JSH(如果暂停,则为JSR;如果H标志是1)***
JSNH (如果不停止,则为JSR;如果H标志为0)**
注意*,**和***; 对于上述各种跳转指令,适用相同的警告。
编辑:hfy
全部0条评论
快来发表一下你的评论吧 !