前言
在很久之前,我一直有一个非常哲学的思考:软件程序的最底层是汇编或机器码,硬件代码的最底层是电路结构,那么这个汇编和HDL到底哪个在更底层呢?
当然后来我知道了这两个并不是一个维度的事情,HDL是用来描述电路的结构,做出来一个支持特定功能或通用功能的芯片,而汇编是使用这个电路来完成各种指定的工作。
但是呢,我还是不能理解软硬件到底是怎么联系起来的,到底软件是怎么来使用硬件的呢?最近在看《计算机组成与设计》,看到第二章的时候忽然感觉自己行了,有点融会贯通的感觉,因此我想把目前的认识思考记录一下,看看之后会不会有更深的思考。
硬件结构与指令集
软件程序是一定要跑在一定的硬件结构上的,那么我这次试用的硬件结构就是之前写的那个RISC处理器:
关于这个处理器的具体设计与实现,除了可以参考这几篇博客外,最好还是看书《Verilog HDL高级数字设计(第二版)》“7.3 RSIC存储程序机的设计与综合”,里面有详细的说明。
该处理器的支持的指令如下;
短指令的格式为:
操作标志 | 源src | 目的dst | |||||
0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
长指令的格式为:
操作标志 | 源src | 目的dst | |||||
0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
地址 | |||||||
0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 |
指令 | 操作码 | 操作 | 示意 |
NOP | 0000 | 不进行任何操作 | 无 |
ADD | 0001 | 源和目的寄存器中的值相加,存入目的寄存器 | dst = src + dst |
AND | 0011 | 源和目的按位取与,存入目的寄存器 | dst = src & dst |
NOT | 0100 | 源寄存器内容取反,存入目的寄存器 | dst = ~src |
SUB | 0010 | 目的减源寄存器后存入目的寄存器 | dst = dst - src |
RD | 0101 | 从长指令第二字节指定的存储单元中取出操作数,写入到目的寄存器 | dst = mem[addr] |
WR | 0110 | 将源寄存器值写入到指定存储单元 | mem[addr] = src |
BR | 0111 | 将第二字节指定地址中的内容写入PC,实现指令跳转 | PC = mem[addr] |
BRZ | 1000 | 当zero_flag有效时,将第二字节指定地址中的内容写入PC,实现指令跳转 | PC = mem[addr] |
HALT | 1111 | 停止执行,直到复位 | 无 |
处理器有四个通用寄存器用例数据处理,为了防止一会忘了,还是把结构图再放这吧:
之有了这个处理器,我就已经成功的把底层电路和指令进行了关联,也就是说,只要你能上层软件的程序给我拆解为该处理器支持的指令,写入到这个处理器的mem中,那么处理器就可以执行你的程序,给出你需要的结果。
因此截止此处,硬件电路——指令集之间的联系我是弄明白了,接下来就是指令集——汇编之间的联系。
指令集与汇编
一般而言,一条汇编语句是可以对应一条指令的,当然也可能是对应多条指令。例如下面这句汇编代码:
add R0 R1 # R1 = R1 + R0
其对应的指令实际就是短指令:
0001_00_01
这个东西是不是也可以叫机器码了,反正已经是处理机认识的二进制码流了。因此可以看出来,汇编实际上就是在面向内存编程,常用的操作实际就是寄存器与mem之间的读写,以及寄存器里数据的运算,通过简单的操作达到复杂的程序目的。
无论上层是多么复杂的代码,只要能编译到汇编语言,实际上就已经是机器可以识别的语言,换句话说,汇编已经可以认为是通过指令集直接操控底层电路结构。那么实际上软硬件交互的问题就推进到底层电路——指令集——汇编这里。
汇编与高级语言
高级软件语言写好的程序在编译完成后开始执行之前会被分配内存空间,这个内存空间里放这这个程序的机器码或者说指令以及各种变量的内存地址,例如一个数组A[30]可能就先申请了30个地址,假设这个数组的基地址为mem[0],同时程序里的一个变量h被分配在地址mem[64]位置(这段是我摘抄的书里示例)。
那么对于一句软件代码:
A[20] = A[10] + h
就会被编译为如下的汇编语言:
load mem[10] R0 #把A[100]的值取到通用寄存器R0
load mem[64] R1 #把变量h的值取到通用寄存器R1
add R0 R1 #R1 = R1 + R0
store R1 mem[20] #把R1的值写入A[300]
然后再转成机器码:
0101_00_00_00001010 # read R0 = mem[10],长指令
0101_01_00_01000000 # read R1 = mem[64],长指令
0001_00_01 # add R1 = R0 + R1,短指令
0110_01_00_00010100 # write mem[20] = R1,长指令
OK,处理器执行了这四个指令后,变量A[20]的值就已经改变了,后面软件程序继续使用A[20]就可以了~
因此,指令——软件程序之间的连续也建立了起来。
结束
再从正向理一下,就是软件程序——汇编程序——指令——二进制机器码——处理机处理,而处理器是使用verilogHDL语言根据所支持的指令集而设计完成的,那么从上到下就算是联系起来了。
我觉得我理解的还是比较粗浅,毕竟我刚看到第二章,后面如果有更深的理解再来更新。