以基于堆栈的CPU为例的开发模型

您是否曾经遇到过“处理器如何工作?”的问题。 是的,是的,正是您的PC /笔记本电脑/智能手机中的那一台。 在本文中,我想给出一个使用Verilog设计的自发明处理器的示例。 Verilog并不完全像它的编程语言。 这是硬件描述语言。 编写的代码不会被任何东西执行(当然,除非您在模拟器中运行它),而是变成物理电路的设计,或者变成FPGA感知的形式(现场可编程门阵列)。


免责声明:本文是某大学项目工作的结果,因此工作时间有限,该项目的许多部分仍处于开发的初始阶段。


请注意,本文中创建的处理器与现代广泛使用的处理器几乎没有共同点,但是我尝试通过其创建来实现略有不同的目标。


为了真正理解编程过程,您需要想象使用的每种工具是如何工作的:语言的编译器/解释器,虚拟机(如果有),中间代码,当然还有处理器本身。 很多时候,学习编程的人都处于很长一段时间的第一阶段-他们只考虑语言及其编译器的工作方式。 这通常会导致错误,其解决方案对于新手程序员来说是未知的,因为他不知道这些问题的根源在哪里。 我本人看到了一些现场示例,其中的情况类似于上面的描述,因此我决定尝试解决这种情况,并创建一套可以帮助初学者理解所有步骤的东西。


该套件包括:


  • 实际发明的语言
  • VS Code的高亮插件
  • 编译到它
  • 指令集
  • 一个能够执行此指令集的简单处理器(用Verilog编写)

我再次提醒您,本文并未描述任何与现代真实处理器类似的内容,而是描述了一个易于理解而又无需详细介绍的模型。


如果您想自己做,则需要做的事情:


要运行CPU仿真,您需要ModelSim,您可以从Intel网站下载。


要运行OurLang编译器,您需要Java版本> = 8。


链接到项目:
https://github.com/IamMaxim/OurCPU
https://github.com/IamMaxim/OurLang


扩展名:
https://github.com/IamMaxim/ourlang-vscode


要构建Verilog部分,我通常使用bash脚本:


#/bin/bash vlib work vlog *.v vsim -c testbench_1 -do "run; exit" 

但这可以通过GUI重复进行。


使用Intellij IDEA与编译器一起使用很方便。 最主要的是要跟踪依赖项中所需的模块包含哪些模块。 我没有发布现成的.jar来打开访问权限,因为我希望读者可以阅读编译器的源代码。


启动的模块是编译器和解释器。 编译器一切都清楚了,Interpreter只是Java中OurCPU的仿真器,但在本文中我们将不予考虑。


指令集


我认为最好从指令集开始。


有几种指令集架构:


  • 本文介绍了基于堆栈的内容。 一个独特的功能是所有操作数都被压入堆栈并从堆栈中弹出,这立即排除了并行执行的可能性,但这是处理数据的最简单方法之一。
  • 基于累加器-最重要的是,只有一个寄存器可存储由指令修改的值。
  • 基于寄存器的是现代处理器中使用的,因为它允许您通过使用各种优化(包括执行并行化,流水线化等)来实现最高性能。

我们的处理器指令集包含30条指令


接下来,我建议看一下处理器的实现:


该代码包含几个模块:


  • 中央处理器
  • 内存
  • 每个指令的模块

RAM是直接包含内存本身以及访问其中数据的模块。


CPU-直接控制程序进度的模块:读取指令,将控制权转移到所需的指令,存储必要的寄存器(指向当前指令的指针,等等)。


几乎所有指令仅适用于堆栈,因此只需遵循它们即可。 有些(例如putw,putb,jmp和jif)在指令本身中有一个附加参数。 他们需要传递整个指令,以便可以读取必要的数据。


以下是处理器工作原理的概述:



指令级设备设计的一般原则


我认为是时候直接了解程序本身的设备了。 从上图可以看到,每条指令之后,地址将移至下一条。 这为程序提供了线性过程。 当有必要打破这种线性关系(条件,循环等)时,将使用分支指令(在我们的指令集中,这些指令为jmp和jif)。


调用函数时,我们需要保存所有内容的当前状态,为此,存在激活记录-存储此信息的记录。 它们不以任何方式与处理器或指令绑定;这只是编译器在生成代码时使用的概念。 OurLang中的激活记录具有以下结构:



从该图可以看出,局部变量也存储在激活记录中,这使您可以在编译时(而不是在运行时)计算内存中变量的地址,从而加快程序执行速度。


对于函数调用,我们的指令集提供了使用CPU模块中包含的两个寄存器(操作指针和激活地址指针)的方法-putopa / popopa,putara / popara。


编译器


现在,让我们看一下最接近最终程序员的那部分-编译器。 通常,作为程序的编译器包括三个部分:


  • Lexer
  • 解析器
  • 编译器

词法分析器负责将程序的源代码转换为解析器可以理解的词法单元。


解析器从这些词法单元构建一个抽象语法树。


编译器遍历此树并生成某种由低级指令组成的代码。 它可以是准备由处理器执行的字节码或二进制代码。


在OurLang编译器中,这些部分分别由类表示


  • Lexer.java
  • 解析器
  • 编译器

语言能力


OurLang尚处于起步阶段,也就是说,它可以正常工作,但是到目前为止,它并不多,甚至语言的核心部分都尚未完成。 但是要了解编译器的本质,当前状态就足够了。


作为理解语法的程序示例,提出了以下代码片段(也用于测试功能):


 // single-line comments /* * Multi-line comments */ function print(int arg) { instr(putara, 0); instr(putw, 4); instr(add, 0); instr(lw, 0); instr(printword, 0); } function func1(int arg1, int arg2): int { print(arg1); print(arg2); if (arg1 == 0) { return arg2; } else { return func1(arg1 - 1, arg2); }; } function main() { var i: int; i = func1(1, 10); if (i == 0) { i = 1; } else { i = 2; }; print(i); } 

我不会专注于语言,而是将其留给您学习。 通过编译器代码,当然;)。


在编写它时,我试图使自解释代码清晰可见,没有注释,因此理解编译器代码应该没有问题。


好吧,当然,最有趣的是编写代码,然后观察它变成了什么。 幸运的是,OurLang编译器会生成带有注释的类似于汇编的代码,
这有助于避免混淆内部发生的事情。


我还建议安装Visual Studio Code扩展,这将有助于使用该语言。


祝您学习项目顺利!

Source: https://habr.com/ru/post/zh-CN430450/


All Articles