裸机 RISC-V CPU - 处理器如何知道从哪个地址开始获取指令?

Bare metal RISC-V CPU - how does the processor know which address to start fetching instructions from?

我正在设计自己的 RISC-V CPU 并且已经能够实现一些指令代码。

我已经安装了 GCC 编译器的 RV32I 版本,所以我现在可以使用 assembler riscv32-unknown-elf-as

我正在尝试 assemble 一个只有一条指令的程序:

# simple.asm
add x5,x6,x7

我用 assembler 编译它,然后用这个命令 运行 objdump:

riscv32-unknown-elf-as simple.asm -o simple
riscv32-unknown-elf-objdump -D simple

这将打印出以下内容:

new:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <.text>:
   0:   007302b3                add     t0,t1,t2

Disassembly of section .riscv.attributes:

00000000 <.riscv.attributes>:
   0:   2d41                    jal     0x690
   2:   0000                    unimp
   4:   7200                    flw     fs0,32(a2)
   6:   7369                    lui     t1,0xffffa
   8:   01007663                bgeu    zero,a6,0x14
   c:   00000023                sb      zero,0(zero) # 0x0
  10:   7205                    lui     tp,0xfffe1
  12:   3376                    fld     ft6,376(sp)
  14:   6932                    flw     fs2,12(sp)
  16:   7032                    flw     ft0,44(sp)
  18:   5f30                    lw      a2,120(a4)
  1a:   326d                    jal     0xfffff9c4
  1c:   3070                    fld     fa2,224(s0)
  1e:   615f 7032 5f30          0x5f307032615f
  24:   3266                    fld     ft4,120(sp)
  26:   3070                    fld     fa2,224(s0)
  28:   645f 7032 0030          0x307032645f

我的问题是:

  1. 这是怎么回事?我以为我会有一个简单的单行十六进制,但还有很多事情要做。
  2. 如何指示我的处理器开始读取特定内存地址的指令?看起来 objdump 也不知道指令将从哪里开始。

需要说明的是,此时我将我的处理器视为裸机。我想我会在处理器中硬编码指令从内存地址 X 开始,数据在内存地址 Y 可用,堆栈在内存地址 Z 可用。这是正确的吗?或者这是错误的方法?

how does the processor know which address to start fetching instructions from?

实际的 CPU 本身会有一些硬连线地址,它会在重置/开机时从中获取这些地址。通常,系统将在该物理地址处设计有 ROM 或闪存。 (该 ROM 可能具有用于 ELF 程序加载器的早期引导代码,它将尊重 ELF 入口点元数据以从 ROM 设置 ELF 内核映像,或者您可以 link 具有正确代码的平面二进制文件二进制文件的开头。)

What is going on here? I thought I'd have a simple single line of hex, but there's a lot more going on.

您的 objdump -D 不包含 assemble 所有 ELF 部分,而不仅仅是 .text。如您所见,.text 部分中只有一条指令,如果您使用 objdump -d,就会看到这样的指令。 (我通常使用 objdump -drwC,虽然 -w 没有换行可能与 RISC-V 无关,不像 x86,其中单个 insn 可能很长。)

Would it be possible to pass the file I compiled above as is to my processor?

可能不是您想象的那样。另请注意,您为输出选择了错误的文件名。 as 生成目标文件(通常为 .o),而不是可执行文件。您可以将 link 和 ld 放入平面二进制文件中,或者 link 和 objcopy 中的 .text 部分。

(理论上您可以将整个 ELF 可执行文件甚至目标文件放入 ROM,这样 .text 部分恰好从 CPU 将从中获取的位置开始,但不会查看元数据字节。因此 ELF 可执行文件中的 ELF 入口点地址元数据将无关紧要。)

.o 和可执行文件之间的区别:.o 只是有重定位元数据供 link 用户填写实际地址,绝对用于 la 伪-指令,或 的相关文件,例如多个 .o 文件,其中一个文件引用另一个文件的符号。 (否则可以在assemble时计算相对位移,而不是在link时计算。)

因此,如果您的代码使用任何标签作为内存地址,则需要 linker 在您的代码中填写这些重定位条目。然后,您可以 objcopy 编辑 link 的 ELF 可执行文件中的某些部分。或者使用 linker 脚本来设置平面二进制文件的布局。

对于只有 add、没有 la 或其他任何东西的简单情况,没有重定位条目,因此 .o 中的文本部分与 link编辑可执行文件。

使用 objcopy 也很棘手的是静态数据,例如.data.bss 部分。如果您将 只是 .text 部分复制到平面二进制文件,您将不会在任何地方拥有数据。 (但是在 ROM 中,您需要一个启动函数,将 .data 的静态初始值设定项从 ROM 复制到 RAM,并将 .bss space 归零。如果您想编写 asm 源代码要有一个看起来正常的 .data 非零值部分,你会希望你的构建脚本计算出要复制的大小,这样你的启动函数就可以使用它,而不是必须手动完成所有这些。)

@PeterCordes 的回答让我走上了正确的道路。我终于想出了如何生成我可以使用的原始内存转储文件。

步骤如下:

  1. 修改了程序集文件以具有 .text.data 部分以及 _start 标签。我的 simple.asm 文件现在如下所示:

    .globl _start
    
    .text
    _start:
      add x5,x6,x7
    
    .data
    L1: .word 27
    
  2. Assemble 使用以下命令将 .asm 复制到 .o 文件:

    riscv32-unknown-elf-as simple.asm -o simple.o
    
  3. 为特定处理器创建一个链接描述文件。我遵循了这个令人惊奇的 video,它介绍了从头开始创建链接描述文件的过程。现在,我只需要 .text.data 部分。所以我的链接描述文件(mycpu.ld)如下所示:

    OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv")
    ENTRY(_start)
    
    MEMORY
    {
      DATA (rwx) : ORIGIN = 0x0, LENGTH = 0x80
      INST (rx) : ORIGIN = 0x80, LENGTH = 0x80
    }
    
    SECTIONS
    {
      .data :
      {
        *(.data)
      }> DATA
    
      .text :
      {
        *(.text)
      }> INST
    }
    
    
  4. 使用自动调用 riscv32-unknown-elf-ld:

    riscv32-unknown-elf-gcc 生成 ELF 文件
    riscv32-unknown-elf-gcc -nostdlib -T mycpu.ld -o simple.elf simple.o
    
  5. .elf 文件创建一个原始二进制或十六进制文件,我将使用它来填充内存的内容。

    riscv32-unknown-elf-objcopy -O binary simple.elf simple.hex
    

最终 simple.hex 包含以下内容(使用 hexyl):

┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 1b 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │•0000000┊00000000│
│00000010│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000040│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000050│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000060│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000070│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000080│ b3 02 73 00             ┊                         │וs0    ┊        │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘

其中 b3027300add x5,x6,x7 的十六进制值。

就是这样!非常感谢@PeterCordes 的帮助! :)