上一篇博客我们简单介绍了Y86指令集体系,而这篇博客我们将介绍指令集体系的逻辑设计和硬件控制语言HCL,为后面去实现Y86打下基础。
在硬件设计中,用电子电路来计算对位进行运算的函数,以及在各种存储器元素中存储位。大多数现代电路技术都是用信号上的高电压或者低电压来表示不同的位值。在当前的技术中心,逻辑 1 是用 1.0 伏特的高电压表示,而逻辑 0 是用 0.0 伏特的低电压表示。要实现一个数字系统需要三个组成部分:
①、计算对位进行逻辑操作的函数的组合逻辑
②、存储位的存储器元素
③、控制存储器元素更新的时钟信号
本篇博客我们就介绍这些不同的组成部分,以及用来描述不同处理器设计的控制逻辑——HCL(Hardware Control Language,硬件控制语言)
1、基本逻辑设计——逻辑门
逻辑门是数字电路的基本计算元素,它们产生的输出,等于它们输入位值的某个布尔函数。如下图:
这三个逻辑门和我们前面讲C语言运算符是对应的。但是要注意区别:
①、逻辑门只对单个位进行操作,而不是整个字。这很好理解,因为硬件识别的是高低电平。
②、逻辑门总是活动的,一旦一个门的输入变化了,在很短的时间内,输出就会相应的变化。
2、高级逻辑设计——组合电路
上面说的都是单个逻辑门,只能执行很简单的计算,如果遇到稍微复杂点的,就不能计算了,这时候组合电路就出现了。
将很多逻辑门组合成一个网所构建的计算块称为组合电路。构建这个网有两个限制:
①、两个或多个逻辑门的输出不能连在一起。否则它们可能会导致线上的信号矛盾,可能会导致一个不合法的电压或电路故障。
②、这个网必须是无环的,也就是在网中不能有路径经过一系列的门而形成一个回路,这样的回路会导致该网络计算的函数有歧义。
我们给出几个非常有用的简单组合电路例子:
1、== 表达式(单个位)
上图我们用HCL来写就是:eq = (a && b) || (!a && !b)。这个表达式的结果分析,只有a 和 b 都为 0 或者都为 1 的时候,输出才为1。而这个对应于我们高级编程语言中的 a == b 表达式。
2、HCL表达式:(a && s) || (b && !s)
对于上面电路的结果,我们分析如果s为1,则结果为a,否则结果为b。
3、HCL表达式 和 C 语言逻辑表达式的区别
区别有以下三点:
①、逻辑门是持续输出的,如果电路的输入变化了,在一定的延迟之后,输出也会相应的变化,而C表达式是在执行到的时候才会求值。
②、两者操作的值不同,逻辑门只对值 0 和值 1 进行操作,而C 逻辑表达式允许参数是任意整数,0表示false,其它任意值都表示true。
③、C 逻辑表达式存在短路,比如对于a && b这个符号来说,C语言中的规定是如果前者为假,则后者不会再计算。而HCL当中没有这种说法。
4、字级组合电路和 HCL 整数表达式
前面我们说的高级逻辑设计,也依然是针对1位进行操作的。那么如何才能真正操作多个位呢,比如平时常用的32位或者64位。那么我们就必须将多个位一起操作,比如下面的:
这个图当中每个位相等的判断都是上面所提到的两个not门,两个and门和一个or门组成的组合电路(第一个讲的组合电路(a && b) || (!a && !b))。总共32个,它们并列的一起进行,最终再通过一个and门,就完成了判断两个32位的数字是否相等的操作。也就是表达式A == B,值得注意的是,这里的A和B都是32位的。C语言中可以假设为 int 型的。
还有一种字级多路复用器用HCL来描述就是:
它代表的意思就是如果s为1,则输出A,否则输出B,同样,这里的A和B都是32位的。那么使用电路如何表达呢?其实就是第二个组合电路的32位版本。如下图:
可以看到,s的not值是被复用的,否则的话,这里需要32个not门。值得一提的是,HCL中条件选择表达式中的条件是不需要互斥的,只是按照优先顺序依次选取,这与C语言中的switch是不同的。
最后一种HCL表达式,选择表达式可以是任意的布尔表达式,可以有任意多的情况。
用逻辑门表示如下:
上述的表达式可以简化,比如第二个表达式为!s1,而不用写的完整!s1&&s0,因为另一种情况s1=0已经出现在第一个表达式中了。
5、存储器和时钟
上面我们介绍的都是组合电路,它们的作用是根据输入来产生一个值。但是刚才也说过,组合电路是一直持续输出的,因此它无法保持一个状态不变。也就是说组合电路从本质上来讲,不存储任何信息。他们只是简单的响应输入信号,产生等于输入的某个函数的输出。但我们的计算机是需要存储数据的,因此就需要能保存状态的存储设备。存储设备是由一个时钟控制,时钟是一个周期型号,它控制着存储设备什么时候更新设备里的值。
常用的存储设备一般有两种:
①、时钟寄存器:简称寄存器,存储单个位或字。时钟信号控制寄存器加载输入值。
②、随机访问存储器:简称存储器,存储多个字,用地址来选择该读或者该写哪个字。
时钟寄存器的典型应用是程序计数器PC、条件码寄存器以及程序状态。它们都有明确的输入,这意味着它们的值其实是某几个值的一个函数,比如条件码寄存器的输入主要就是逻辑计算单元的值,因此条件码寄存器的值就可以看做是逻辑计算单元的函数。
下图表示寄存器的工作状态。大多数时候,寄存器都保持在稳定状态(用x表示),产生的输出等于它的当前状态,信号沿着寄存器前面的组合逻辑传播,这时产生了一个新的寄存器输入(用y表示),但是只要时钟信号是低电位的,寄存器的输出就保持不变。当时钟变为高电位时,输入信号就加载到寄存器中,称为下一个状态y,直到下一个时钟上升沿,这个状态就一直是寄存器的新输出。
随即访问存储器最典型的例子就是我们的寄存器文件(也就是8个程序寄存器)和随即访问存储器(也就是我们常说的内存)。它们没有明确的输入值,因此不存在函数关系。不论是寄存器文件还是随即访问存储器,都有读和写两种操作,而对于时钟寄存器来说,是无所谓读和写的,因为它只会根据输入的变化改变输出的值,是可以直接连接到电路上去的。
如下图:寄存器文件一般有两个读端口(A和B)和一个写端口(W)。每个端口都附带一个地址来标识操作的是哪个寄存器,而对于写端口,还有一个输入数据,对于读端口,则还有一个输出数据。具体的图示如下。
上面的寄存器文件,电路可以读取两个程序寄存器的值,同时更新第三个寄存器的状态。每个端口都有一个地址输入,表明该选择是哪个程序寄存器,另外还有一个数据输出或对应该程序寄存器的输入值。可以看到在寄存器文件的写端口处,有一个时钟(clock)控制着写的操作。当时钟变化时,输入数据的值将会更新到对应的寄存器当中。而对于读数据,则类似于组合电路,根据输入的地址值(src),寄存器文件会输出相应的数据。
对于 随即访问存储器 来说,与寄存器文件非常相似。不同的是,随即访问存储器有一个地址输入,一个写的数据输入以及一个读的数据输出。
读操作:如果我们在输入address上提供一个地址,并将write控制信号设置为0,那么经过一段延迟之后,存储在那个地址上的值就会出现在输出data上。如果地址超出了范围,error信号会设置为1,否则就设置为0。
写存储器是由时钟控制的:我们将address设置为期望的地址,将data in(数据输入) 设置为期望的值,而 write设置为1,然后当我们控制时钟时,只要地址是合法的,就会更新存储器中指定的位置。对于读操作来说,如果地址是不合法的,error信号会被设置为1.这个信号由组合逻辑产生,因为所需的边界检查纯粹就是地址输入的函数,不涉及任何保存状态。