【问题标题】:What are the main steps to write an Instruction Set simulator?编写指令集模拟器的主要步骤是什么?
【发布时间】:2011-06-03 00:06:32
【问题描述】:

我将从事一个需要为特定指令集(可能不是真正的处理器)编写模拟器的项目。这个模拟器最好是类似于 MIPS ISA 的 SPIM 模拟器。它将显示所有寄存器、内存位置等的内容,并让我逐步完成说明。是否有编写模拟器的标准步骤集?从哪里开始?

我知道 Java 和 C++,我完成了两门计算机体系结构课程,并在一个 3 人的团队中工作。

【问题讨论】:

  • 您的意见是什么?指令列表或以二进制格式编码的指令?

标签: assembly simulator


【解决方案1】:

第一步是为您的 CPU 创建一个汇编程序。汇编器与模拟器没有任何关系,在我看来,在模拟器中包含一个汇编解析器会使事情变得不必要地复杂化。

使用单独的汇编程序可以保持模块化,它允许您的模拟器将简单的二进制代码作为输入 - 您甚至可以单独创建、验证和修改这些代码。也符合现行做法。

至于您的模拟器,在 Google 上搜索 writing a CPU simulator 会首先找到 this,并且还有很多更有趣的结果。

【讨论】:

    【解决方案2】:

    我会说您需要先使用反汇编程序。模拟器是反汇编程序之外的下一步。例如,x86(或其他可变字长指令集)反汇编程序将需要跟随代码的执行。首先,它必须根据操作码知道该指令使用了多少字节,其次这是一个分支,如果是这样,是有条件的还是无条件的,并做出相应的反应。模拟器完成了所有这些,而且它模拟了寄存器。反汇编程序将用于教育目的,而不是在 sourceforge 上的精美发布。足以了解如何在位级别解析指令,计算跳转偏移量。

    我会从一个简单的指令集开始,比如 12 位 pic 或 6502 或类似的东西(msp430 是另一个可能的候选者)。如果您使用现有的指令集来学习如何编写模拟器,则可以利用该 isa 的现有工具链(汇编器和编译器)。将任务分成两半。对于新指令集,如果您想手动编写机器代码,您将需要一个工具链或至少一个好的反汇编程序(反汇编程序会仔细检查您是否拥有您认为想要的机器代码)。或者您可以在模拟器中选择按执行顺序输出反汇编代码,无论如何都适合调试。

    一开始不要担心中断,但请注意,您将需要一种机制来停止模拟流程、保存状态并在如此多的时钟周期内重复使用模拟器。你会看到很多模拟器基本上是每条指令单步执行的。由于周期性中断或其他需要与模拟处理器同步的模拟硬件的硬件原因,评估每条指令上的时钟滴答数。因此,最好先以这种方式建模,然后根据需要提出性能改进。

    如果您没有使用状态机进行编程,我强烈建议您了解一下状态机并编写一些练习程序。

    我正在做一个项目,我希望发布这个项目,如果我完成了,可能会对此类主题有所帮助。这些天我的指令集模拟器实际上是用硬件设计语言编写的(例如,verilog,不是我使用的 hdl,但它转换为 verilog)然后使用带有 C/C++ 包装器的 verilator 之类的东西。因此,与提取操作码和位相关的工作由一种旨在管理它的语言处理。想要模拟外围设备或提供 gui 或其他显示正在发生的事情的方式的部分由为此设计的语言处理。如果您碰巧在一个项目中使用 vhdl 或 verilog 实现了这个新指令集,我强烈推荐混合 hdl 模拟器和软件解决方案,如果它是商业 hdl 模拟器,则通过 VPI 或通过其他更简单的方法,如果您使用 verilator或 icarus verilog 或 ghdl 或其他。 (icarus 可能需要类似商业的 vpi 接口)。这里的美妙之处在于你更接近真实的硬件,在进入芯片之前可以发现并处理硬件中的错误/缺陷等,硬件的更改或改进在模拟中立即实现,而不必进行匹配模拟器等方面的更改或改进。 hdl 模拟器具有脚本语言或系统 C 等,可以很好地对硬件进行功能测试,但您不能在硅片上运行它。如果您不鼓励使用这些工具并让真实的二进制文件在模拟逻辑上运行,并为真实工具提供一个抽象层来加载或调试或监控(例如将后端写入 openocd 以通过主机传递到 hdl sim 层并与芯片上的模拟 jtag 调试器交谈,具体取决于此设计的复杂性(非常可行,因为我自己做过))。这些真实的程序既可以在仿真中运行,也可以在芯片上运行,从而节省编写测试的时间,并且在芯片到达之前就编写好测试软件,而不是在它到达之后开始编写。

    抱歉,HDL 正切。如果这是一个新的或没有工具链指令集的新指令集,您将不得不花时间在该工具链上,至少是一个汇编器和链接器,我会做某种反汇编器,然后将该反汇编器或其部分转换为第一个切割在模拟器上。至于我在指令集模拟器中看到的标准东西,基本上考虑单步,以便您可以处理时钟和中断以及其他模拟硬件。模拟的核心功能要么模拟一个可能不是完整指令的时钟周期(状态机方法),要么模拟单个指令并根据指令的要求返回修改寄存器、标志和内存(并计算模拟时钟)。根据您从一开始就希望该模拟器具有的功能丰富程度,使您对 ram/rom 和寄存器功能进行读写,以便您可以在一个地方捕获内存和寄存器更改以向用户显示。只能通过这些抽象函数访问或至少修改寄存器和 ram。除此之外,它只是大量的输入,指令集越复杂,指令越多,要输入的代码行数就越多。

    【讨论】:

    • 我有很多问题......但第一,你能把你的想法整理一下,这样我就可以整理我的问题了。非常感谢!
    • 在核心,试试这个,有一个函数,你传递下一条要执行的指令的地址。对于初学者,您的处理器寄存器、标志等可以是全局的,或者如果您愿意,也可以将指针传递给包含这些项目的结构。让该函数获取下一条指令,对其进行解码,如果需要获取更多数据(可变长度指令或加载/存储操作),更新处理器寄存器变量/状态并返回。
    • 每当该函数想要读取内存时,例如调用 ReadMemory() 类型的函数,或 WriteMemory(),而不是 x=mem[y]。注册访问也是如此。
    • 使用读取二进制文件、重置机器状态(带有寄存器和标志等的结构)的代码包装此函数,具体取决于处理器通过重置向量找到入口点还是从零开始执行或其他什么,并将该地址传递给核心函数。
    • 如果处理器中的程序计数器是通用寄存器之一,理想情况下,您希望只有一个程序计数器来管理下一条指令的地址等。但这有时会变得很棘手我保留了两份。您必须小心地在此处正确模拟硬件以进行分支表和其他与 PC 相关的操作。例如,在 ARM 模式下的 ARM 上,PC (r15) 是当前执行指令之前的一条指令,因此当前地址+4。在拇指模式下,即当前地址+2。
    【解决方案3】:
    1. 起点是指令集本身和处理器的内部架构 [有时称为“编程模型”] 以及寻址模式是什么。
    2. 全面了解每条指令 [在处理器内部] 的预期操作以及处理器预期为该指令发送 [到内存或 i/o 端口] 的输出
    3. 为机器码指令设计一种指令格式:指令的机器码中应该有多少位/字节;其中,多少位构成操作码,多少位表示寻址模式,多少位表示操作数[寄存器,数据或地址]等必须确定。还决定这些位组的顺序和组合。您可以方便地保留总数。机器码指令中的位数为 8 的倍数[即长度为 1 或 2 或 3 字节的机器码指令对 u 来说比 12 位或 17 位更方便]。
    4. 在模拟器软件[您可以用 c/cpp/java 或任何您喜欢的语言编写] 中,您将分配变量来表示 cpu 的内部寄存器; ALU 的工作将由您 SW 中的功能完成;您的软件将从二进制/ascii-hex 文件中读取机器代码指令;每条指令都将根据不同的位组所指示的内容进行分析];然后根据这些指示调用必要的函数。
    5. 为模拟器软件设计一个好的用户界面。我猜你可能会使用模拟器进行演示/教学/培训。在动作进行时显示 sw [cpu 内部正在进行的动作] 的动作;例如,当从这些位置写入或读取数据时,您可以显示堆栈内存位置或寄存器。它将有助于用户更好地了解 CPU 操作。更重要的是,它可以帮助您在开发/测试模拟器软件时轻松定位错误。

    【讨论】:

      猜你喜欢
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-06
      • 2018-08-03
      • 2022-10-02
      • 2015-05-09
      • 2015-07-22
      相关资源
      最近更新 更多