Skip to content

Latest commit

 

History

History
555 lines (454 loc) · 22.8 KB

README.md

File metadata and controls

555 lines (454 loc) · 22.8 KB

TEMU: Temu is not qEMU

T绝对与清华大学没有任何关系。本命名为致敬QEMU用。

Description

该项目是一个基于C语言编写的RISC-V模拟器,支持rv32ima_zicsr_zicnt_sstc架构和sv32内存分页结构。 模拟器实现了指令级别的模拟,即使用C解释执行其描述的内存操作与运算等。这一过程就像CPython执行Python字节码一样, PVM会根据字节码逐条执行对应的操作,如加载变量、调用函数、执行算术运算等。本模拟器与CPython原理基本相同,TEMU作为片上系统模拟器,实现了SoC 的常见体系结构模拟,支持运行主线LinuxTEMU模拟的SoC,在本文档中,都会基于OpenSBI+U-Boot的方式来进行内核启动前的工作。

2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 12692
Total time (secs): 12.692000
Iterations/Sec   : 315.159155
Iterations       : 4000
Compiler version : GCC12.3.0
Compiler flags   : -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -O2 -g0 -D_FORTIFY_SOURCE=1   -lrt
Memory location  : Please put data memory location here
                        (e.g. code in flash, data on heap etc)
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0x65c5
Correct operation validated. See readme.txt for run and reporting rules.
CoreMark 1.0 : 315.159155 / GCC12.3.0 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -O2 -g0 -D_FORTIFY_SOURCE=1   -lrt / Heap

Getting Started

Dependencies

  • OpenSBI, U-Boot(Optional), Linux Kernel的二进制文件
  • cmake工具链,自动下载TEMU-Linux二进制
  • mkimage (Optional)
  • riscv-gnu-toolchain(Optional,如果你希望自己编译RISC-V平台的目标二进制文件,我这里使用buildroot 制作的riscv32-buildroot-linux-gnu-作为我的交叉编译工具链)
  • 支持的host平台:macOS with LLVM, Linux, Windows with MinGW

TODO

  • (TODO) 将此项目的代码对MSVC友好
  • (TODO) 适配Windows的终端环境
  • (TODO) 使用Ctrl + A + X退出
  • (TODO) 在根文件系统中添加其它软件等
  • (TODO) 添加显示屏,以太网等外设

Compiling (建议略过,直接跳到下一步,体验已经准备好的二进制)

编译TEMU模拟器

mkdir build && cd build
cmake .. -DCMAKE_C_COMPILER=gcc
make

编译OpenSBI固件

这里需要用到本项目移植过llep实验平台的OpenSBI来编译固件。我们使用fw_jump型固件来完成从OpenSBIU-Boot的跳转。

make PLATFORM=llep CROSS_COMPILE=riscv32-buildroot-linux-gnu- -j64

编译通过后,我们需要的fw_jump.bin会出现在build/platform/llep/firmware中。

编译U-Boot

这里也需要用到本项目移植过llep实验平台的U-Boot

make xxxdefconfig # TODO
make CROSS_COMPILE=riscv32-buildroot-linux-gnu- -j64

此时,U-Boot根目录内的u-boot.bin即为u-boot本体的二进制文件。u-boot.dtb就是我们设备树的二进制文件。我们整个模拟系统都会以这个 u-boot.dtb作为我们的设备树。

编译Kernel

这里还需要用到本项目移植过llep实验平台的Kernel。需要注意的是,我们将根文件系统与内核捆绑在一起 ,所以需要保证你有已经制作好的cpio格式的文件系统。我们的文件系统将作为ramfs挂载在内存上。

make xxxdefconfig # TODO
make ARCH=riscv CROSS_COMPILE=riscv32-buildroot-linux-gnu- -j64

arch/riscv/boot/Image就是我们内核的无压缩的镜像。这个镜像包含了内核从虚拟地址0xc0000000 开始一直到结束的内存的所有内容。0xc0000000是我们 进入内核的入口的虚拟地址,TEMU将从起始地址(此时内核没有开启虚拟内存,这个地址通常为0x80000000 ,等页表生效后这个地址就被映射到了0xc0000000)开始取第一条内核的指令并继续运行。但是U-Boot 并不能直接运行这个纯二进制文件,因为U-Boot 不知道这是什么类型的操作系统,不知道应该怎么传参或者运行它。所以我们需要使用mkimage工具将arch/riscv/boot/Image 打包为U-Boot能识别的uImage文件,其实就是给arch/riscv/boot/Image加了一个0x40大小的头。

mkimage -A riscv -O linux -T kernel -C none -a 0x80000000 -e 0x80000000 -d arch/riscv/boot/Image uImage

由于我们没有实现rvc压缩指令,所以我们的内核二进制文件会比较大。我们需要压缩uImageuImage.gz。这个uImage.gz 不像arm架构的zImage,可以实现自解压,因此我们在进入U-Boot后还需要手动解压uImage.gz到指定的地址(即0x80000000)。

gzip -c uImage > uImage.gz

使用TEMU运行主线Linux系统

  • 确保你拥有RISC-V指令集的二进制文件,本项目目前仅支持装载bin,即objcopy的输出,暂时不支持直接加载ELF,且加载ELF对于系统级别的模拟意义不大
  • 这些二进制文件可以在这里下载: TEMU Booting Binaries v0.1.2,可以直接使用wget下载到linux中。
    • Changes since TEMU Booting Binaries v0.1.1
      • 使能内核中的CONFIG_PREEMPT
      • 在根文件系统中添加Coremark跑分工具
    • Changes since TEMU Booting Binaries v0.1.0
      • 添加htop工具
      • 修复了U-Boot中一个引起Store/AMO Access Fault异常的bug
    • Changes since TEMU Booting Binaries v0.0.2
      • 由于根文件系统的增大(rootfs预留量由6MiB扩大到了12MiB),因此U-Boot的启动参数发生了改变
      • 这个版本的U-Boot不需要手动输启动参数,等3秒就会autoboot
      • 先前的二进制由于受到大小限制,偷偷删去了libstdc++.so,这次libstdc++.so得到了保留
      • 去掉了Buildroot自动配置DHCP导致启动时间超极长的问题。由于体积原因内核裁掉了网络,而Buildroot 一直在尝试等待eth0出现
      • 在根文件系统中添加了以下包:
        • ascii_invaders: An ASCII-art game like Space Invaders using ncurses.
        • micropython: Micro Python is a lean and fast implementation of the Python 3 programming language that is optimised to run on a microcontroller.
        • lrzsz: 用串口传文件。
        • nano: 文本编辑工具
  • 这里给出使用TEMU加载OpenSBIU-BootKernel二进制文件的使用例。
Usage: temu [-ram/-rom/-addr 0x80000000] [-printreg] -exec=program.bin [-with=addr#file.bin]

Example:
--addr=0x81fa0000
--exec=fw_jump.bin
--with=0x80000000#u-boot.bin
--with=0x81ffd800#u-boot.dtb
--with=0x81000000#uImage.gz

使用以下命令启动TEMU

./temu --addr=0x81fa0000 --exec=fw_jump.bin \
--with=0x80000000#u-boot.bin \
--with=0x81ffd800#u-boot.dtb \
--with=0x81000000#uImage.gz

如果成功启动,TEMU首先会打印OpenSBI的启动信息,接着打印U-Boot的信息,然后就进入内核。

OpenSBI v1.4
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name             : Low-speed Linux Experimental Platform
Platform Features         : medeleg
Platform HART Count       : 1
Platform IPI Device       : ---
Platform Timer Device     : --- @ 0Hz
Platform Console Device   : uart8250
Platform HSM Device       : ---
Platform PMU Device       : ---
Platform Reboot Device    : ---
Platform Shutdown Device  : ---
Platform Suspend Device   : ---
Platform CPPC Device      : ---
Firmware Base             : 0x81fa0000
Firmware Size             : 178 KB
Firmware RW Offset        : 0x20000
Firmware RW Size          : 50 KB
Firmware Heap Offset      : 0x24000
Firmware Heap Size        : 34 KB (total), 2 KB (reserved), 8 KB (used), 23 KB (free)
Firmware Scratch Size     : 4096 B (total), 160 B (used), 3936 B (free)
Runtime SBI Version       : 2.0

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*
Domain0 Region00          : 0x12500000-0x12500fff M: (I,R,W) S/U: (R,W)
Domain0 Region01          : 0x81fc0000-0x81fcffff M: (R,W) S/U: ()
Domain0 Region02          : 0x81fa0000-0x81fbffff M: (R,X) S/U: ()
Domain0 Region03          : 0x00000000-0xffffffff M: () S/U: (R,W,X)
Domain0 Next Address      : 0x80000000
Domain0 Next Arg1         : 0x81ffd800
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes
Domain0 SysSuspend        : yes

Boot HART ID              : 0
Boot HART Domain          : root
Boot HART Priv Version    : v1.12
Boot HART Base ISA        : rv32ia
Boot HART ISA Extensions  : sstc,zicntr
Boot HART PMP Count       : 0
Boot HART PMP Granularity : 0 bits
Boot HART PMP Address Bits: 0
Boot HART MHPM Info       : 0 (0x00000000)
Boot HART Debug Triggers  : 0 triggers
Boot HART MIDELEG         : 0x00000222
Boot HART MEDELEG         : 0x0000b109


U-Boot 2024.04-rc2-ge92a78c7e7-dirty (Apr 28 2024 - 21:39:07 +0800) Low-speed Linux Experimental Platform

DRAM:  31.6 MiB
Core:  11 devices, 8 uclasses, devicetree: separate
Loading Environment from nowhere... OK
In:    uart@12500000
Out:   uart@12500000
Err:   uart@12500000
Net:   No ethernet found.
Hit any key to stop autoboot:  0
Uncompressed size: 11712300 = 0xB2B72C
## Booting kernel from Legacy Image at 80000000 ...
   Image Name:
   Created:      2024-04-28  14:03:36 UTC
   Image Type:   RISC-V Linux Kernel Image (uncompressed)
   Data Size:    11712236 Bytes = 11.2 MiB
   Load Address: 80000000
   Entry Point:  80000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at 81ffd800
   Booting using the fdt blob at 0x81ffd800
Working FDT set to 81ffd800
   Loading Kernel Image to 80000000
   Loading Device Tree to 81f32000, end 81f365e3 ... OK
Working FDT set to 81f32000

Starting kernel ...

[    0.000000][    T0] Linux version 6.8.0-rc4-00008-g6f4f7080ab52-dirty (root@hyz-wsl) (riscv32-buildroot-linux-gnu-gcc.br_real (Buildroot -ge725bb3-dirty) 12.3.0, GNU ld (GNU Binutils) 2.40) #178 Sun Apr 28 22:03:29 CST 2024
[    0.000000][    T0] SBI specification v2.0 detected
[    0.000000][    T0] SBI implementation ID=0x1 Version=0x10004
[    0.000000][    T0] SBI TIME extension detected
[    0.000000][    T0] SBI IPI extension detected
[    0.000000][    T0] SBI RFENCE extension detected
[    0.000000][    T0] SBI DBCN extension detected
[    0.000000][    T0] earlycon: sbi0 at I/O port 0x0 (options '')
[    0.000000][    T0] printk: legacy bootconsole [sbi0] enabled
[    0.000000][    T0] [paging_init] Test of earlycon printk
[    0.000000][    T0] Zone ranges:
[    0.000000][    T0]   Normal   [mem 0x0000000080000000-0x0000000081f9ffff]
[    0.000000][    T0] Movable zone start for each node
[    0.000000][    T0] Early memory node ranges
[    0.000000][    T0]   node   0: [mem 0x0000000080000000-0x0000000081f9ffff]
[    0.000000][    T0] Initmem setup node 0 [mem 0x0000000080000000-0x0000000081f9ffff]
[    0.000000][    T0] riscv: base ISA extensions aim
[    0.000000][    T0] riscv: ELF capabilities aim
[    0.000000][    T0] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[    0.000000][    T0] pcpu-alloc: [0] 0
[    0.000000][    T0] Kernel command line: earlycon=sbi console=ttyS0,115200 root=/dev/ram0
[    0.000000][    T0] Dentry cache hash table entries: 4096 (order: 2, 16384 bytes, linear)
[    0.000000][    T0] Inode-cache hash table entries: 2048 (order: 1, 8192 bytes, linear)
[    0.000000][    T0] Built 1 zonelists, mobility grouping on.  Total pages: 8032
[    0.000000][    T0] mem auto-init: stack:off, heap alloc:off, heap free:off
[    0.000000][    T0] Memory: 20516K/32384K available (2002K kernel code, 177K rwdata, 408K rodata, 8845K init, 68K bss, 11868K reserved, 0K cma-reserved)
[    0.000000][    T0] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[    0.000000][    T0] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[    0.000000][    T0] riscv-intc: 32 local interrupts mapped
[    0.000000][    T0] plic: interrupt-controller@c000000: mapped 16 interrupts with 1 handlers for 2 contexts.
[    0.000000][    T0] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x171024e7e0, max_idle_ns: 440795205315 ns
[    0.000000][    T0] sched_clock: 64 bits at 100MHz, resolution 10ns, wraps every 4398046511100ns
[    0.006451][    T0] Console: colour dummy device 80x25
[    0.007573][    T0] Calibrating delay loop (skipped), value calculated using timer frequency.. 200.00 BogoMIPS (lpj=400000)
[    0.008577][    T0] pid_max: default: 32768 minimum: 301
[    0.012323][    T0] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.013313][    T0] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.048807][    T1] ASID allocator using 9 bits (512 entries)
[    0.061496][    T1] devtmpfs: initialized
[    0.094684][    T1] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.095702][    T1] futex hash table entries: 256 (order: 0, 7168 bytes, linear)
[    0.121384][    T1] platform soc: Fixed dependency cycle(s) with /soc/interrupt-controller@c000000
[    0.156950][    T1] clocksource: Switched to clocksource riscv_clocksource
[    0.478313][    T1] workingset: timestamp_bits=30 max_order=13 bucket_order=0
[    0.498619][    T1] io scheduler mq-deadline registered
[    0.499108][    T1] io scheduler kyber registered
[    3.868154][    T1] Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled
[    3.992753][    T1] printk: legacy console [ttyS0] disabled
[    4.024866][    T1] 12500000.uart: ttyS0 at MMIO 0x12500000 (irq = 2, base_baud = 72000) is a 16550A
[    4.026612][    T1] printk: legacy console [ttyS0] enabled
[    4.026612][    T1] printk: legacy console [ttyS0] enabled
[    4.036093][    T1] printk: legacy bootconsole [sbi0] disabled
[    4.036093][    T1] printk: legacy bootconsole [sbi0] disabled
[    4.134996][    T1] brd: module loaded
[    4.141977][    T1] start plist test
[    4.438989][    T1] end plist test
[    4.957636][    T1] clk: Disabling unused clocks
[    5.231312][    T1] Freeing unused kernel image (initmem) memory: 8844K
[    5.234550][    T1] Kernel memory protection not selected by kernel config.
[    5.237485][    T1] Run /init as init process
[    5.239592][    T1]   with arguments:
[    5.240876][    T1]     /init
[    5.242666][    T1]   with environment:
[    5.244228][    T1]     HOME=/
[    5.246093][    T1]     TERM=linux
Saving 256 bits of non-creditable seed for next boot
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Starting network: ip: socket: Function not implemented
ip: socket: Function not implemented
FAIL

Welcome to Buildroot
buildroot login:

进入内核后,可以尝试以下玩法:

Welcome to Buildroot
buildroot login: root
login[60]: root login on 'console'
# uname -a
Linux buildroot 6.8.0-rc4-00008-g6f4f7080ab52-dirty #178 Sun Apr 28 22:03:29 CST 2024 riscv32 GNU/Linux
# cat /proc/cpuinfo
processor       : 0
hart            : 0
isa             : rv32ima
mmu             : sv32
mvendorid       : 0x0
marchid         : 0x0
mimpid          : 0x0
hart isa        : rv32ima

# cat /proc/interrupts
           CPU0
  2:        161  SiFive PLIC  10 Level     ttyS0
  5:      10132  RISC-V INTC   5 Edge      riscv-timer
# micro
microcom     micropython
# micropython
MicroPython v1.22.0 on 2024-04-28; linux [GCC 12.3.0] version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> 0.1 + 0.2
0.3
>>> print("Because we don't use the real IEEE754 float points")
Because we don't use the real IEEE754 float points
>>> a = "Hello"
>>> a[-1]
'o'
>>>
# ascii_invaders



                          _ _   _                     _
            __ _ ___  ___(_|_) (_)_ ____   ____ _  __| | ___ _ __ ___
           / _` / __|/ __| | | | | '_ \ \ / / _` |/ _` |/ _ \ '__/ __|
          | (_| \__ \ (__| | | | | | | \ V / (_| | (_| |  __/ |  \__ \
           \__,_|___/\___|_|_| |_|_| |_|\_/ \__,_|\__,_|\___|_|  |___/


                                _/MM\_  = ?  points
                                qWAAWp

                                 {@@}   = 30 points
                                 /""\

                                 dOOb   = 20 points
                                 ^/\^

                                 /MM\   = 10 points
                                 |~~|



                   https://github.com/macdice/ascii-invaders

Documentation (TODO)

CPU Architect

TEMU 模拟的 SoC 实现了TLB 与I-Cache和D-Cache, 由于通用计算机难以高效模拟Content Associate Memory, 因此缓存都是以组相连的方式实现,这一选择在保持较高命中率的同时, 确保了仿真效率与现实硬件行为的一致性。

考虑到CPU执行的本质串行性,我们优化了总线架构的设计, 避免了不必要的模拟开销,使得模拟更加聚焦于核心功能。 模拟SoC的架构设计理念借鉴了操作系统中宏内核的思想, 其中指令解析模块与外设模拟部件相互集成, 如同宏内核中各服务模块之间的直接交互, 这样的设计促进了高效的数据交换与功能协同, 提升了模拟的整体性能与响应速度。

TEMU 体系结构

以下是 CPU 解释执行指令的有关代码:

  • src/cache.c:实现I/D-cache
  • src/decode.c:解释并执行指令
  • src/machine.c:取指,模拟外设,中断等特权态处理
  • src/mmu.c:实现对虚拟地址的转换,以及对物理内存的读写
  • src/tlb.c:实现快表,加速虚拟地址转换
  • src/trap.c:实现对异常与中断的处理(统称为trap)
  • src/zicsr.c:实现zicsr与特权架构

支持的ISA

rv32ima_zicsr_zicnt_sstc

  • i:整数指令
  • m:乘法指令
  • a:原子指令
  • zicsr:特权架构支持
  • zicnt:CSR寄存器的计数器支持
  • sstc:特权架构Supervisor级别定时器中断支持。这个扩展给运行在Supervisor模式的软件 (后续都称为操作系统)也添加了timetimecmp寄存器,允许操作系统读写这两个寄存器来实现直接在Supervisor模式进行处理的 定时器中断。(如果你学习过xv6,你会发现xv6只会在Machine特权态下处理定时器的中断,并且它还在手册里明确指出了risc-v 规定 定时器中断只能在Machine Mode下处理。但是如果实现了sstc拓展,这个过程可以得到很大的简化。)

内存映射

类型 Base Address Size
ROM 0x0000_0000 64KiB
RAM 0x8000_0000 32MiB
UART 0x1250_0000 0x100
PLIC 0x0c00_0000 0x400000

对于OpenSBI+U-Boot+Linux启动方案,RAM在上电时应该由TEMU模拟first stage bootloader,将二进制文件加载到以下位置。

地址 可用空间 描述
0x8000_0000 (x) 上电时这里为u-boot.bin。u-boot重定位后,内核将被解压到此处,并从此处开始运行
0x8100_0000 10MiB 压缩过的内核,即uImage.gz,存储于此地址
0x81da_0000 2MiB U-Boot将在上电后从0x8000_0000重定位至此处
0x81fa_0000 384KiB OpenSBI的固定地址。这一段逻辑上是只读的
0x81ff_d800 10KiB 设备树的二进制文件

32MiB内存方案下,内核可用的内存大小约为30.625MiB

进入系统后执行free -h得到的结果是:

# free -h
              total        used        free      shared  buff/cache   available
Mem:          28.7M        4.4M       13.4M        8.0K       10.9M       12.9M
Swap:             0           0           0

进入系统后执行df -h得到的结果是:

# df -h
Filesystem                Size      Used Available Use% Mounted on
devtmpfs                 10.0M         0     10.0M   0% /dev
tmpfs                    14.3M         0     14.3M   0% /dev/shm
tmpfs                    14.3M         0     14.3M   0% /tmp
tmpfs                    14.3M      8.0K     14.3M   0% /run

MMU模型

实现sv32内存模型。具体的risc-v特权态手册里都写了。这个页表就两级,实现起来比较容易。

CSR寄存器支持

实现的CSR请参考include/zicsr.h,但是真正使用过的CSR是以下几个:

  • sstatus
  • sie
  • stvec
  • sepc
  • scause
  • stval
  • sip
  • stimecmp
  • stimecmph
  • satp
  • mstatus
  • medeleg
  • mideleg
  • mie
  • mtvec
  • mepc
  • mcause
  • mcause
  • mtval
  • mip
  • scontext
  • cycle
  • time
  • cycleh
  • timeh
  • mvendorid

需要注意的是,以上列出的寄存器,只是我们代码中显式调用了的。对于某些软件,你必须实现我们在include/zicsr.h的这些寄存器。它必须存在,尽管 我并没有实现它们具体的功能。

异常与中断

我们在trap.c中实现对异常与中断的处理。异常包括指令执行时的错误与系统调用, 中断包括

  • 监管者与机器模式的软件中断(这个是做核间通信的,TEMU中没有用到)
  • 监管者与机器模式的定时器中断(大多数实现都是通过CLINT,而TEMU使用 zicsr的定时器来触发此中断)
  • 监管者与机器模式的外部中断。这个外部中断由PLIC触发,内核陷入后会读取PLICcause寄存器的值,判断外部中断是来自哪个外设,并对其进行处理。

模拟器在译码阶段会产生指令异常或系统调用,通过trap_throw_exception来 令CPU陷入Trap Handler。首先会判断该异常是否会委托给监管者模式,并且 设置对应模式的causetvalstatusepc等寄存器,再将PC指针与 tvec交换,保存当前的特权状态,从而进入陷入处理程序。中断同理。

uart 16550a

(WIP)

PLIC中断控制器

(WIP)

我们实现的是t-headplic,而不是sifiveplic控制器。

Authors

Obvious.

Version History

This is blamed to git history.

Acknowledgments

Inspiration, code snippets, etc.