处理器体系结构
Y86指令集结构
对于Y86来说,它的程序猿可见状态就是这几种: 寄存器、存储器、条件码、PC、程序状态。
在Y86当中,寄存器依旧是8个,每一个寄存器可以存储一个字,也就是一个32位二进制。条件码是一个一位二进制的寄存器,保存着最近的算术或逻辑运算所造成的影响的信息。PC则是程序计数器,记录当前正在执行的指令的地址。
存储器则是一个很大的字节数组,Y86的程序可以使用虚拟地址(类似于数组的下标)来访问存储器,硬件和操作系统会将虚拟地址翻译为实际的地址。最后一个程序状态(stat),它则代表着程序的运行情况。
以上便是程序猿可见的状态,或者说机器级程序可访问的CPU状态,我们在设计和实现一个处理器的时候,就是设计一系列指令去操作这些状态。
Y86指令集
接下来我们就看看Y86的指令集,这里LZ就直接上图了,这些指令其实都是从X86的指令集YY而来。
上面的指令相信大家都不会太陌生,LZ这里就不仔细的解释了,下面我们只简单的把每个指令的作用过一遍。
halt:这个指令将会终止指令的执行。
nop:这是一个占位指令,它不做任何事情,后续为了实现流水线,它有一定的作用。
xxmovl:这是一系列的数据传送指令,其中r代表寄存器,m代表存储器,i代表立即数。比如rrmovl指令,则代表将一个寄存器的值,赋给另外一个寄存器。
opl:操作指令,比如加法,减法等等。
jxx:条件跳转指令,根据后面的条件进行跳转。
cmovxx:条件传送指令,后面的xx代表的是条件。特别的是,条件传送只发生在两个寄存器之间,不会将数据传送到存储器。
call与ret:方法的调用和返回指令。一个将返回地址入栈,并跳到目标地址。一个将返回地址入PC,并跳到返回地址。
push与pop:入栈和出栈操作。
指令编码
在图的右边,是指令所占的字节数或者说编码。一般两个寄存器占用一个字节,存储器则占用四个字节,指令的编码和功能占用一个字节。因此可以看到,比如rrmovl指令,它的字节长度是2,其中第一个字节代表了指令rrmovl,第二个字节代表了两个寄存器。
对于opl、jxx、cmovxx指令来说,都有一个fn标识,占用4个二进制位(半个字节)。这个便是指令的功能部分,这个是由于它们的指令编码一样,但功能有所不同所造成的。比如对于opl,就有加、减、与、异或等操作,那么它们的指令编码第一个字节就分别为十六进制的60、61、62、63。
对于寄存器的表示,是使用4个二进制位表示的,这是一个ID标识。所有的寄存器可以看做是一个寄存器文件,其中的ID标识就类似于它们的地址。对于一些只需要一个寄存器的指令来说,另一个寄存器标识位使用0xF表示。
还有的指令需要一个字的常数,比如irmovl指令,call指令等等。这种指令,将把常数放在最后的四个字节当中,顺序按照大端法或小端法表示(与机器和OS有关)。对于call指令来说,这四个字节就是一个地址,这个地址就是绝对地址,指向了存储器当中的某一个位置,这个位置存储着代码。采用绝对地址是为了描述简单,真实当中,是采取的基于PC的相对地址。
Y86异常
对于Y86来说,程序猿可见的状态中就有stat状态码,它标识了程序执行的状态。Y86需要有能力根据stat去做一些处理。不过为了简单起见,这里除了正常执行之外,都将停止指令的执行。真实当中,会有专门的异常处理程序。
Y86有四种不同的状态码,AOK(正常)、HLT(执行halt指令)、ADR(非法地址)和INS(非法指令)。
Y86程序
书中给出了一个示例程序,来说明X86和Y86的区别,其实两者是非常相似的,区别就在于,有的时候Y86需要两条指令来达到X86一条指令就可以达成的目的。
比如对于X86指令中的 addl $4,%ecx 这样的指令,由于Y86当中的addl指令中不包含立即数,所以Y86需要先将立即数存入寄存器,即使用irmovl指令,然后再使用addl来处理加法运算。
总的来说,Y86就是一个X86的缩减版,它的目的是以简单的结构实现一个处理器,帮助我们了解处理器的设计和实现。有兴趣的猿友可以去观摩一下Y86程序生成的汇编代码,并进行逐一的分析,实际上,这与X86是十分类似的。
逻辑设计和硬件控制语言HCL
电子电路中,用1.0V左右的高电压表示逻辑1,用0.0V左右的低电压表示逻辑0.
一、逻辑门
1.逻辑门产生的输出,等于它们输入位值的某个布尔函数。
2.
AND &&
OR ||
NOT !
3.逻辑门只对单个位的数进行操作,而不是整个字。
4.逻辑门总是活动的,输入变化输出很快就跟着变化。
二、组合电路和HCL布尔表达式
1.构建计算块(组合电路)时的限制
- 两个或多个逻辑门的输出不能连接在一起
- 必须无环
2.组合逻辑电路和c语言中逻辑表达式的区别
- 组合电路的输出会持续响应输入变化,c语言表达式只有在执行过程中被遇到才求值
- 逻辑门只对0和1操作,c语言表达式中参数可以是任意整数,0是FALSE,不是0的都是TRUE
- c的逻辑表达式可能被部分求值
三、字级的组合电路和HCL整数表达式
- 所有字级的信号都声明为int,不指定字的大小
-
多路复用函数用情况表达式来描述,具体格式如下:
[ select_1 : expr_1 select_2 : expr_2 …… ]
从逻辑上讲,这些选择表达式是顺序求值的。
- 选择表达式为1时,表示如果前面没有情况被选中,就选择这种情况
-
不同 选择表达式之间允许不互斥
四、集合关系
判断集合关系的通用格式是:
iexpr in {iexpr1,iexpr2,...,iexprk}
iexpr等都是整数表达式。
SEQ的时序
组合逻辑不需要任何时序或控制——只要输入变化了,值就通过逻辑门网络传播。
我们也将读随机访问存储器(寄存器文件、指令存储器和数据存储器)看成和组合逻辑一样的操作。(写随机访问存储器需要等待高电平)
由于指令存储器只用来读指令,因此我们可以将这个单元看成是组合逻辑。(内存向指令存储器中写指令是CPU外部的事件 不属于CPU内的时序)
每个时钟周期,程序计数器都会装载新的指令地址。
只有在执行整数运算指令时,才会装载条件码寄存器。
只有在执行mov、push、call指令时,才会写数据存储器。
要控制处理器中活动的时序,只需要寄存器和存储器的时钟控制。
因为指令运行计算的结果,写入寄存器或存储器中。
我们可以把取指、译码、执行等过程看做是组合逻辑的处理过程(因为它们不涉及写入寄存器)。把写回看做是另一个过程。
则整个过程可简化为下图所示:
【举例详解】
有如下指令:
0x000 : irmovl $0x100, %ebx
0x006 : irmovl $0x200, %edx
0x00c : addl %edx, %ebx
0x00e : je dest
0x013 : rmmovl %ebx, 0(%edx)
0x019 : dest: halt
在我们的SEQ处理器中,一个时钟周期(即两次高电平时间的时间间隔)执行一条指令。
时钟周期3开始时(点1处),一个高电平打入,地址0x00c载入程序计数器PC中。这样,与PC相连的MCU(主存控制单元)就在内存中把地址0x00c处的addl指令提取出来,加载到指令存储器中。(从内存中读取数据很慢,这个过程会很久,所以我们的时钟周期要很长,才能做到一个时钟周期执行一条指令)同时,PC的值加上addl指令的长度,得出新PC值,新PC值通过总线传播,等待下次高电平时写入PC。
组合逻辑指令存储器中的输入一变化,值(addl指令)就通过逻辑门网络传播。故,瞬间读出了寄存器文件中%edx、%ebx的值(因为读寄存器文件不需要高电平触发)
读出的%edx、%ebx的值瞬间流动到组合逻辑ALU中,ALU根据之前传播的addl指令,知道此为加法指令,瞬间计算出这两个值的结果valE。valE通过总线传播瞬间到达寄存器文件,但是此时还不能向寄存器文件写入,必须等待下次的高电平。
故此时,寄存器文件和存储器中保存的还都是上条指令的结果值。(点1、2处)
时钟周期4开始时(点3处),一个高电平打入,上周期产生的新PC值写入程序计数器,上周期计算得到的addl的结果valE值写入寄存器文件中的%ebx中。
因为地址0x00e载入了程序计数器中,故会取出并执行跳转指令je。因为条件码ZF为0,所以不会选择分支。在这个周期末尾(点4),程序计数器已经产生了新值0x013。但是直到下个周期开始之前,寄存器和存储器中的状态还是保持着addl指令设置的值。
【如此例所示,用时钟来控制状态元素的更新,以及值通过组合逻辑来传播,足够控制我们SEQ实现中每条指令执行的计算了。每次时钟由低变高时,处理器开始执行一条新指令。】
【读操作沿着这些单元传播,就好像它们是组合逻辑,而写操作是由时钟控制的。】
参考资料:http://blog.csdn.net/yang_yulei/article/details/22529437
http://www.tuicool.com/articles/Zv6v6n
http://www.cnblogs.com/20135202yjx/p/4888820.html
实验:
代码如下