Create RISC-V Core using Verilog HDL (1) “setting up a RISC-V cross compiler.”

Yoshi's Tech Blog
3 min readMay 9, 2021

--

About this project

Hi, I’m currently implementing a RISC-V core using Verilog HDL for a deeper understanding of the CPU. Here is the GitHub of this project. If you are interested, please give me a star.

https://github.com/yoshi-ki/RISC-V-core

What is a cross compiler, and why it is needed

This is my first article on this project, and today I will give you chips for the cross compiler for RISC-V. Cross compiler is a compiler that can create executable code for a platform other than the one on which the compiler is running. If you use a normal compiler on the x86 architecture, normally, your compiler gives you a code for x86. Our goal is to get a machine code for RISC-V; thus, the normal compiler does not work for this goal unless your PC runs on RISC-V. This time is exactly when the cross compiler is needed.

Installation

I went to this page https://github.com/riscv/riscv-gnu-toolchain and cloned it.

The first thing I needed to do was to set the configuration. Configuration can be set like this.

./configure --prefix="path for creating compiler" --with-arch="architecture mode" --with-abi=ilp32d

RISC-V has a lot of kinds of instruction sets. Some of it has instructions for floating points, and others do not. I needed to specify it for the compiler. For the first step, I wanted to create a simple architecture; thus, I set architecture mode as rv32ima. Because I did not use floating-point instructions, I did not specify the ABI.

After the configuration, I run the following command.

make linux

It took a very long time, but after that, I got my RISC-V compiler in the specified directory.

Configurations

Then, I wanted to compile a very simple program for the test; thus, I prepared this code.

int test_add_sub(int n){
return n + (n - 1);
}
int main() {
test_add_sub(5);
for(;;) {}
return 0;
}

for(;;){} is for preventing removing from main function.

First, I needed to create “start.S” to disable the initialization. This assembly file calls the main function.

.section .text.init;
.globl _start
_start:
call main

Then, I created the “link.ld” like this. This file makes the initial place of the instruction memory zero.

OUTPUT_ARCH( "riscv" )
ENTRY(_start)

SECTIONS
{
. = 0x00000000;
.text.init : { *(.text.init) }
.tohost : { *(.tohost) }
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
_end = .;
}

Creating binary files

After the configurations, I could finally create the binary file.

First, export the path for my compiler.

export PATH=/your-compiler-path/bin:$PATH

Second, compile it as follows.

riscv32-unknown-linux-gnu-gcc -march=rv32i -c -o start.o start.S
riscv32-unknown-linux-gnu-gcc -march=rv32i -c -o test.o test.c
riscv32-unknown-linux-gnu-ld test.o start.o -L riscv32-unknown-linux-gnu/lib/ -Tlink.ld -static -o test.elf
riscv32-unknown-linux-gnu-objcopy -O binary test.elf test.bin

Finally, I got the final code, “test.bin.”

Let’s look at the code through the following python code.

with open("test.hex", "rb") as file:
with open("out.txt", "w") as f:
data = file.readlines()
for d in data:
d = str(bin(int(d,16)))
d = d[2:]
d = "0" * (32 - len(d)) + d
d = "32\'b" + d + ","
f.write(d)
f.write("\n")
f.close()
file.close()

Seems working! I can use this output directly for the Verilog HDL.

I can also disassemble the output code using this command.

my-compiler-path/riscv32-unknown-linux-gnu/bin/objdump -d test.elfDisassembly of section .text.init:00000000 <_start>:
0: 034000ef jal ra,34 <main>
Disassembly of section .text:00000004 <test_add_sub>:
4: fe010113 addi sp,sp,-32
8: 00812e23 sw s0,28(sp)
c: 02010413 addi s0,sp,32
10: fea42623 sw a0,-20(s0)

It also seems working!

Next plan

Firstly, I will implement an add function. I’m looking forward to finishing this project!

--

--

Yoshi's Tech Blog

I’m a master’s student at the University of Tokyo. My major is Computer Science. Webpage: yoshi-ki.github.io