MENU

【歪门邪道】利用WSL搭建MIPS32构建环境

May 11, 2020 • 瞎折腾

最近打算参加一个 CPU 设计竞赛,遂开始从头开始看一本叫做《自己动手写 CPU》的书,因为年头比较古老了,其中的干货还很有价值,但是涉及诸如仿真、Mips 环境搭建等事宜,再以书中内容做参考就不太适宜了。

书中给出的 Mips 编译器还是 2013 年的呢,这篇文章我将记述如何一边 google 一边寻找最新版的编译器来完成同样的事情。

当然了,不使用最新版的同样也可以完成,而且直接按书上来的就是了,但是我总是喜欢用最新版的软件,所以这就是这篇文章的目的:如何使用最新版的工具链来编译 MIPS32 程序。

背景展开目录

关于实现这个事情,我最早想到的是直接去 WSL 里面安装 MIPS 的交叉编译链就是了。我的 WSL 使用的是 OpenSUSE,我在 yast 里面直接搜索 mips,然后安装带有 cross 字样的那个包,就是 GCC 交叉编译 MIPS 的包。后来又仔细研究了一下,发现交叉编译有两种不同的工具链。一种是在 yast 里安装的这种,我总结出它的特征就是在编译完的产物中你用 readelf -h 去看编译产物,你会发现它的 Flags 里面多一个 o32 或者其他什么东西,而另一种工具链编译完你去看就只有 mips32 一个 Flag。由于书上的目标是直接在 Verilog 写的 CPU 中运行程序,换句话说编译产物是直接作为二进制读入 CPU,然后被执行的,这中间没有操作系统来做一些脏活累活,因此这就要求你要是用 Bare Metal 版本的工具链,也就是后一者编译出来 Flags 只有一个 mips32 的那个。虽然就后面那个演示程序来说二者编译结果都一样,但是在编译 C 语言或 C++ 的时候,前者可能会直接调用一些一来操作系统的功能,而后者则不会,这样能够有效避免很多问题(它没有操作系统,而你用了依赖操作系统的指令,我觉得十有八九肯定会出毛病)。

MIPS 交叉编译 GNU 工具链展开目录

一开始在谷歌上搜索,我找到这么一个网页,也是 MIPS 官方网站:「ELF (Bare Metal)」,可是问题是我怎么也下不来页面上的文件,总是跟我说服务器 500 错误。我一想这事情必然有蹊跷,遂在官网上搜索。一开始搜索 Bare Metal,毕竟我要下载这个版本的工具链。结果没搜到什么有用的东西,又尝试搜索 ToolChain,工具链嘛,总该有相关的了吧。找到了「Linux Toolchain」,结果上面的文件一点开就会主页了。这时候我觉得可能 MIPS 官方在演我,我不甘心。

最终我在主页顶端导航栏的 Develop 选项卡下面的 Developer Tools 里看到了端倪:Developer Tools,在这个页面的 Compilers 一节,内附一个链接,打开之后跳到标题为「MIPS Compilers」,这回终于是对了。开门见山,直接给出了 Codespace 上用于 GNU 编译器的 MIPS 工具。最终的下载下来 Windows64 位版本的 Bare Metal 工具链。

放在一个固定的文件夹里面,然后在 WSL 里面配置 PATH 环境变量。因为我这边用的是 OpenSUSE,它不自动继承 Windows 系统的环境变量,因此需要单独设置,如果是 Debian 或其他发行版本,我记得他会自动继承 Windows 的 Path 设置。

  • export PATH="/mnt/c/mips-mti-elf/2019.09-02/bin:$PATH"

重新进入后键入 mips 然后按 TAB,如果能自动补全就说明成功了。不过这里不好的一点就在于每一个命令之后你需要追加.exe,但好处是可以在 PowerShell 中运行。如果追求与 Linux 完全兼容的话,可以考虑下载 Linux 版本的,然后直接放在 WSL 里面。我这边为了方便在 PowerShell 中调用,就暂时忍了那个.exe 的小尾巴了。

测试编译链展开目录

源程序展开目录

文件名 inst_rom.S

  • .org 0x0
  • .global _start
  • .set noat
  • _start:
  • ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100
  • ori $2,$0,0x0020 # $2 = $0 | 0x0020 = 0x0020
  • ori $3,$0,0xff00 # $3 = $0 | 0xff00 = 0xff00
  • ori $4,$0,0xffff # $4 = $0 | 0xffff = 0xffff

程序很简单,就是四个使用不同寄存器的逻辑与运算。最终这段程序应当转化为一个只包含这四条指令的二进制文件,然后再通过其他工具转换成 Verilog 内置函数 $readmemh 可以直接读入的文件。

编译展开目录

编译需要使用 mips-mti-elf-as.exe

  • mips-mti-elf-as.exe -mips32 inst_rom.S -o inst_rom.o

这里 inst_rom.S 是上面的源文件,inst_rom.o 是编译产物。

连接展开目录

连接需要使用 mips-mti-elf-ld.exeram.ld 文件。后者是描述编译产物的各个块是如何安排的(我也没学过编译原理,大概就是这个意思),内容如下:

  • /* default.ld. Default linker script for Or1ksim test programs
  • Copyright (C) 1999-2006 OpenCores
  • Copyright (C) 2010 Embecosm Limited
  • Contributors various OpenCores participants
  • Contributor Jeremy Bennett <jeremy.bennett@embecosm.com>
  • This file is part of OpenRISC 1000 Architectural Simulator.
  • This program is free software; you can redistribute it and/or modify it
  • under the terms of the GNU General Public License as published by the Free
  • Software Foundation; either version 3 of the License, or (at your option)
  • any later version.
  • This program is distributed in the hope that it will be useful, but WITHOUT
  • ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  • FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  • more details.
  • You should have received a copy of the GNU General Public License along
  • with this program. If not, see <http: www.gnu.org/licenses/>. */
  • /* ----------------------------------------------------------------------------
  • This code is commented throughout for use with Doxygen.
  • --------------------------------------------------------------------------*/
  • MEMORY
  • {
  • ram : ORIGIN = 0x00000000, LENGTH = 0x00001000
  • }
  • SECTIONS
  • {
  • /*
  • For some reason the linker script can't see the _reset_vector symbol
  • (even if we declare it global), so we explicitly set it. */
  • .text :
  • {
  • *(.text)
  • } > ram
  • .data :
  • {
  • *(.data)
  • } > ram
  • .bss :
  • {
  • *(.bss)
  • } > ram
  • .stack ALIGN(0x10) (NOLOAD):
  • {
  • *(.stack)
  • _ram_end = .;
  • } > ram
  • }
  • ENTRY (_start)

保存后进行连接:

  • mips-mti-elf-ld.exe -T ram.ld inst_rom.o -o inst_rom.om

这时候 inst_rom.om 就是最终的 ELF 文件了。这里不详细说明什么是 ELF 了,总是他是一个面向操作系统的可执行文件,如果使用 readelf 去看的话,你可以发现一些数据和书上列出来的有些差距。

首先是 Start of section headers,我这边的值比书上列的大了 200,而后面 Number of section headersSection header string table index 分别比书上的多了 2 和 4。我觉得相同的文件怎么编译也应该差不出这么多。但是我也没在意(因为确实是不懂),后来导出的时候发现四条指令竟然导出了 10 条指令的量,前四个是正常的指令,后面六个对照 MIPS32 的指令表,怎么看怎么不像正常的指令。

经过搜索 om 文件和导出的数据,配合 readelf -S 查看块的数据,发现编译产物中多了一个.MIPS.abiflags 的块,需要在导出时排除掉。

复制出指令部分展开目录

这部分使用 mips-mti-elf-objcopy.exe,需要排除上面说的那个不相关的块:

  • mips-mti-elf-objcopy.exe -R .MIPS.abiflags -O binary inst
  • _rom.om inst_rom.bin

其中 -R 就是排除块,最终产物 inst_rom.bin 是二进制形式,可以使用 hexdump 查看其内容,与书上得到的结果一样。

转换为 $readmemh 可读格式展开目录

随书资料中附带了一个转换的工具,只可惜我这边提示此应用无法在你的电脑上运行,所以这里只好自己写一个了。本来想用 Java 的,但是考虑到要在 Make 里面调用,还是 C++ 相对好点,代码如下:

  • #include <iostream>
  • #include <fstream>
  • #include <string>
  • using namespace std;
  • std::ifstream fin;
  • std::ofstream fout;
  • string source_file_name;
  • string target_file_name;
  • int main(int argc, char * argv[])
  • {
  • if(argc != 3){
  • cout << "Usage: bin2mem raw target" << endl;
  • return 0;
  • }
  • source_file_name = argv[1];
  • target_file_name = argv[2];
  • fin.open(source_file_name);
  • if(!fin)
  • {
  • cout << "failed to open source file " << source_file_name << endl;;
  • exit(-1);
  • }
  • fout.open(target_file_name);
  • if(!fout)
  • {
  • cout << "failed to create target file " << source_file_name << endl;
  • exit(-1);
  • }
  • fout << hex; // 十六进制形式输出
  • char ch;
  • int count = 0;
  • while(fin.get(ch))
  • {
  • count ++;
  • int value = static_cast<unsigned char>(ch);
  • // cout << value << endl;
  • if(value < 0x10)
  • fout << '0';
  • fout << value;
  • if(count % 4 == 0) // 4个字节,32位,一条指令
  • fout << '\n';
  • }
  • fin.close();
  • fout.close();
  • return 0;
  • }

有一说一我的 C++ 确实是不咋地,就这点东西连查带调试,其中有些还是复制粘贴来的代码,就整了半个多小时。之前用 Java 测试的时候 5 分钟就写完了。

使用 g++ 编译成可执行文件:

  • g++ -o bin2mem ./bin2mem.cpp

使用:

  • ./bin2mem inst_rom.bin inst_rom.data

最终输出的 inst_rom.data 就是 $readmemh 可以直接读取的格式了。这里针对的是 MIPS32,如果是 64 位字长的话,需要按照实际指令的长度调整上面 44 行的数。

自动化展开目录

每次都来一套这些命令确实不太行,因此这里使用 Makefile 借助工具 Make 来做自动化编译。

  • ifndef CROSS_COMPILE
  • CROSS_COMPILE = mips-mti-elf-
  • endif
  • CC = $(CROSS_COMPILE)as.exe
  • LD = $(CROSS_COMPILE)ld.exe
  • OBJCOPY = $(CROSS_COMPILE)objcopy.exe
  • OBJDUMP = $(CROSS_COMPILE)objdump.exe
  • OBJECTS = inst_rom.o
  • export CROSS_COMPILE
  • # ********************
  • # Rules of Compilation
  • # ********************
  • all: inst_rom.om inst_rom.bin inst_rom.asm inst_rom.data
  • %.o: %.S
  • $(CC) -mips32 $< -o $@
  • inst_rom.om: ram.ld $(OBJECTS)
  • $(LD) -T ram.ld $(OBJECTS) -o $@
  • inst_rom.bin: inst_rom.om
  • $(OBJCOPY) -R .MIPS.abiflags -O binary $< $@
  • inst_rom.asm: inst_rom.om
  • $(OBJDUMP) -D $< > $@
  • inst_rom.data: inst_rom.bin
  • ./bin2mem $< $@
  • clean:
  • rm -f *.o *.om *.bin *.data *.mif *.asm

没啥好说的,根据书上代码修改的。关键就是告诉 make 要用到的工具都在哪里,怎么调用。

- 全文完 -


知识共享许可协议
【歪门邪道】利用 WSL 搭建 MIPS32 构建环境天空 Blond 采用 知识共享 署名 - 非商业性使用 - 相同方式共享 4.0 国际 许可协议进行许可。
本许可协议授权之外的使用权限可以从 https://skyblond.info/about.html 处获得。

Last Modified: November 6, 2020
Archives QR Code
QR Code for this page
Tipping QR Code