简化的 CPU 执行程序的过程如下
程序经编译器编译后生成二进制指令码,烧写程序即烧写程序对应的二进制指令码,烧写程序到硬件系统中后,这些二进制指令码就保存在系统的硬盘中。
当你打开一个程序的时候,就把对应的指令码+数据加载到了内存里,其中指令部分被随后加载到了CPU的缓存里面,然后CPU根据[程序计数器],即程序指针 PC指向的缓存地址,把存储的指令从缓存取到指令寄存器里面保存,这里是取指。
你取过来的指令,是由操作码和地址码组成的,分别表示执行什么操作和对谁操作,但是这些指令需要控制单元里面的一个叫做译码器的东西来分析,分析这些取来的指令码到底是什么意思,然后根据分析出来的内容指定下一步的行动计划:去哪里找什么部件执行什么操作,这是译码。
随后数据寄存器把数据从内存里面加载到算术逻辑单元,进行运算,并把结果传回数据寄存器,这就是执行。
以上就是三级流水线CPU的执行过程。
程序: 嵌入式代码,如C代码;
编译器: gnu工具链;
硬盘: 可以是上述FPGA板卡的板载flash,烧写代码时,可以将程序烧写固化在flash中,这样板卡掉电后,程序还会存在于flash中,在下次上电后,可从flash中读取;
内存: 工程中对应rom.v外设,工程中提供的代码烧写方式是直接烧录在rom中的,板卡掉电后rom中的数据会丢失,需要重新烧写代码;
缓存: 此工程比较简洁,没有使用缓存结构,将代码烧写进内存rom中后,cpu内核直接与rom进行通信,从rom中取出指令码,省去了缓存这一层级;
程序指针PC : 本质上是一个32bit的寄存器,用于存放指令的地址,在工程的pc_reg.v文件中定义为reg
指令寄存器: 存放根据程序指针PC取出的32bit指令码,在工程中定义为inst_i;
a = 1 + 2 执行具体过程
程序编译过程中,编译器通过分析代码,发现 1 和 2 是数据,于是程序运⾏时,内存会有个专⻔的区域来存放这些数据,这个区域就是「数据段」。如下图,数据 1 和 2 的区域位置:
- 数据 1 被存放到 0x100 位置;
- 数据 2 被存放到 0x104 位置;
编译器会把 a = 1 + 2 翻译成 4 条指令,存放到正⽂段中。如图,这 4 条指令被存放到了 0x200 ~ 0x20c的区域中:
0x200 的内容是 load 指令将 0x100 地址中的数据 1 装⼊到寄存器 R0 ;
0x204 的内容是 load 指令将 0x104 地址中的数据 2 装⼊到寄存器 R1 ;
0x208 的内容是 add 指令将寄存器 R0 和 R1 的数据相加,并把结果存放到寄存器 R2 ;
0x20c 的内容是 store 指令将寄存器 R2 中的数据存回数据段中的 0x108 地址中,这个地址也就 是变量 a 内存中的地址;
编译完成后,执⾏程序的时候,程序计数器会被设置为 0x200 地址,然后依次执⾏这 4 条指令。
如何让程序跑的更快?
对于 CPU 时钟周期数我们可以进⼀步拆解成:「指令数 x 每条指令的平均时钟周期数(Cycles PerInstruction,简称 CPI )」,于是程序的 CPU 执⾏时间的公式可变成如下:
-
指令数,表示执⾏程序所需要多少条指令,以及哪些指令。这个层⾯是基本靠编译器来优化,毕竟同样的代码,在不同的编译器,编译出来的计算机指令会有各种不同的表示⽅式。
-
每条指令的平均时钟周期数 CPI,表示⼀条指令需要多少个时钟周期数,现代⼤多数 CPU 通过流⽔线技术(Pipline),让⼀条指令需要的 CPU 时钟周期数尽可能的少;
-
时钟周期时间,表示计算机主频,取决于计算机硬件。有的 CPU ⽀持超频技术,打开了超频意味着把 CPU 内部的时钟给调快了,于是 CPU ⼯作速度就变快了,但是也是有代价的,CPU 跑的越快,散热的压⼒就会越⼤,CPU 会很容易奔溃。