在 FPGA 中通过扩展指令集来加速计算过程,即将某些函数以CPU指令的方式来执行。然后通过将他们用 C 语言进行封装,从而成为标准C库中的一部分。
这里通过简单的添加 opcode 的方式来说明自定义 opcode 中对 gcc 的扩展过程。
环境准备
- 设置环境变量
#!/bin/bash
# [1]
# prepare-env: export required environment variables, create folders.
# 在环境变量中设置 RISCV 主工程目录
export RISCV_HOME=~/riscv-home
export RISCV="${RISCV_HOME}/riscv"
export PATH="${PATH}:${RISCV}/bin"
export RISCV_PK="${RISCV}/riscv32-unknown-elf/bin/pk"
# 创建这些目录
mkdir -p "${RISCV_HOME}" "${RISCV}"
echo '[OK] done'
- 构建 RISCV 工具链
#!/bin/bash
# [2]
# download-repos: clone all repositories to $RISCV_HOME.
if [ -z "${RISCV_HOME}" ]; then
echo '$RISCV_HOME is undefined; run `source prepare-env`'
exit 1
fi
dst="${RISCV_HOME}"
echo "destination folder is ${dst}"
# 基础同步代码函数
function riscv-clone {
repo="${1}"
revision="${2}"
shift 2
git clone -b master $@ "https://github.com/riscv/${repo}" "${dst}/${repo}" &&
cd "${dst}/${repo}" &&
git checkout "${revision}" &&
echo "[OK] ${repo} cloning finished"
}
# 分别同步 riscv-fesvr, riscv-pk, riscv-isa-sim, riscv-opcodes, riscv-tests, riscv-gnu-toolchain 等包
riscv-clone 'riscv-fesvr' '01932a715edd22ee451d86cde38c7c07dc9bfa7e' &&
riscv-clone 'riscv-pk' '66701f82f88d08d3700d8b0bc5d5306abfd0044f' &&
riscv-clone 'riscv-isa-sim' '3a4e89322a8c8dac94185812a238f13789ab392f' &&
riscv-clone 'riscv-opcodes' 'e0abc2255a71afb0236032ae3d92bea26c15716d' &&
riscv-clone 'riscv-tests' '9e313f30205b8172290831c3af18b0779e9b15f2' &&
riscv-clone 'riscv-gnu-toolchain' 'f5fae1c27b2365da773816ddcd92f533867f28ec' --recursive &&
echo '[OK] done'
- 初始化编译
#!/bin/bash
# [3]
# build-repos: build downloaded repositories.
# 安装依赖包
# Get packages that are required to build the GNU toolchain.
# The list provided by https://github.com/riscv/riscv-gnu-toolchain README.
sudo apt-get install \
autoconf \
automake \
autotools-dev \
curl \
libmpc-dev \
libmpfr-dev \
libgmp-dev \
gawk \
build-essential \
bison \
flex \
texinfo \
gperf \
libtool \
patchutils \
bc \
zlib1g-dev \
device-tree-compiler || exit 1
# 编译工具链
function mk-toolchain {
cd "${RISCV_HOME}/riscv-gnu-toolchain" &&
./configure --prefix="${RISCV}" --with-arch=rv32i &&
echo 'this can take more than a hour...' &&
make &&
echo '[OK] mk-toolchain: done'
}
# 编译 front-end server
function mk-fesvr {
cd "${RISCV_HOME}/riscv-fesvr" &&
mkdir -p build &&
cd build &&
../configure --prefix="${RISCV}" &&
make install &&
echo '[OK] mk-fesvr: done'
}
# 编译代理 kernel, 及 bootloader
function mk-pk {
# See issue https://github.com/riscv/riscv-pk/issues/56.
# Remove '-m32' occurences from Makefile
# to fix "error: unrecognized command line option '-m32'".
cd "${RISCV_HOME}/riscv-pk" &&
mkdir -p build &&
cd build &&
../configure --prefix="${RISCV}" --host=riscv32-unknown-elf --enable-32bit &&
sed -i 's/\-m32//g' Makefile &&
make &&
make install &&
echo '[OK] mk-pk: done'
}
# 编译 spike 模拟器
function mk-spike {
cd "${RISCV_HOME}/riscv-isa-sim" &&
mkdir -p build &&
cd build &&
../configure --prefix="${RISCV}" --with-fesvr="${RISCV}" &&
make &&
sudo make install &&
echo '[OK] mk-spike: done'
}
# The order matters.
mk-toolchain &&
mk-fesvr &&
mk-pk &&
mk-spike &&
echo '[OK] done'
- 测试
#!/bin/bash
# [4]
# check-install: try to find out it installation was successful.
cc='riscv32-unknown-elf-gcc'
cxx='riscv32-unknown-elf-g++'
# 测试 command 命令
function check-cmd {
name="${1}"
command -v "${name}" >/dev/null 2>&1 ||
(echo "${name}: command not found" && exit 1)
echo "[OK] located ${name}"
}
# 通过spike 模拟器测试执行环境
function check-spike {
mkdir -p build &&
${cc} data/hello.c -O1 -march=rv32im -o build/cc_hello &&
echo '[OK] C compiler works' &&
${cxx} data/hello.cpp -O1 -march=rv32im -o build/cxx_hello &&
echo '[OK] C++ compiler works' &&
spike --isa=RV32IM "${RISCV_PK}" build/cc_hello &&
spike --isa=RV32IM "${RISCV_PK}" build/cxx_hello &&
echo '[OK] spike works'
}
check-cmd "${cc}" &&
check-cmd "${cxx}" &&
check-cmd 'spike' &&
check-spike &&
echo '[OK] done'
添加 Opcode
定义新指令
mac(a, b, c) => c := c + (a * b)
- 创建 riscv/insns/mac.h 文件
用来描述 mac 指令的功能
// 'M' extension means we require integer mul/div standard extension.
require_extension('M');
// RD = RD + RS1 * RS2
reg_t tmp = sext_xlen(RS1 * RS2);
WRITE_RD(sext_xlen(READ_REG(insn.rd()) + tmp));
- 添加Opcode
cd "${RISCV_HOME}/riscv-opcodes"
echo -e "mac rd rs1 rs2 31..25=1 14..12=0 6..2=0x1A 1..0=3\n" >> opcodes
make install
# 将 mac 指令追加到 riscv_insn_list 中去
sed -i 's/riscv_insn_list = \\/riscv_insn_list = mac\\/g' \
"${RISCV_HOME}/riscv-isa-sim/riscv/riscv.mk.in"
- 重新编译spike模拟器
cd "${RISCV}/riscv-isa-sim/build"
sudo make install
- 测试程序编写
#include <stdio.h>
// Needed to verify results.
int mac_c(int a, int b, int c) {
a += b * c; // Semantically, it is "mac"
return a;
}
// Should not be inlined, because we expect arguments
// in particular registers.
__attribute__((noinline))
int mac_asm(int a, int b, int c) {
// 0x02C5856B 为 mac 的 16进制 执行编码
asm __volatile__ (".word 0x02C5856B\n");
return a;
}
int main(int argc, char** argv) {
int a = 2, b = 3, c = 4;
printf("%d =?= %d\n", mac_c(a, b, c), mac_asm(a, b, c));
}
- 编译执行
riscv32-unknown-elf-gcc test_mac.c -O1 -march=rv32im -o test_mac
spike --isa=RV32IM "${RISCV_PK}" test_mac
输出: 14 =?= 14
执行格式说明
- mac & mul
# file "riscv-opcodes/opcodes"
# differs
# |
# v
mac rd rs1 rs2 31..25=1 14..12=0 6..2=0x1A 1..0=3
mul rd rs1 rs2 31..25=1 14..12=0 6..2=0x0C 1..0=3
# ^ ^ ^ ^ ^ ^ ^
# | | | | | | |
# | | | | | | |
# | | | | | | also opcode 3 bits
# | | | | | opcode 5 bits
# | | | | funct3 3 bits
# | | | funct7 7 bits
# | | rs2 (src2) 5 bits
# | rs1 (src1) 5 bits
# dest 5 bits
- 实际执行
# Encoding used for "mac a0, a1, a2"
0x02C5856B [base 16]
==
10110001011000010101101011 [base 2]
== 对齐后
00000010110001011000010101101011 [base 2]
# Group by related bit chunks:
0000001 01100 01011 000 01010 1101011
^ ^ ^ ^ ^ ^
| | | | | |
| | | | | opcode (6..2=0x0C 1..0=3)
| | | | dest (10 : a0)
| | | funct3 (14..12=0)
| | src1 (11 : a1)
| src2 (12 : a2)
funct7 (31..25=1)
参考
RISC-V: custom instruction and its simulation
gnu-riscv32_ext
Adding the custom instruction to spike ISA simulator