This guide assumes that you have finished all the steps in my previous post, Setting Up a RISC-V Security Testing Environment and have managed to generate a basic binary that simulates a RISC-V Rocket core using Verilator.
Once Chipyard is basically up and running, you should have a chipyard
folder that looks more or less like this:
~/chipyard$ ls
bootrom CHANGELOG.md CONTRIBUTING.md env.sh lib project riscv-tools-install sims tests tools vlsi
build.sbt common.mk docs generators LICENSE README.md scripts software toolchains variables.mk
We ran our simulation in the earlier guide from within chipyard/sims/verilator
and were able to generate cycle accurate test results by issuing make run-asm-tests
from within that directory. Also in that directory we ought to have a simulator-example-RocketConfig
that constitutes our simulation “emulator.” If we just run the emulator without specifying a target binary, we should get a short help file.
~/chipyard/sims/verilator$ ls
generated-src Makefile output simulator-example-RocketConfig verilator_install verilator.mk
~/chipyard/sims/verilator$ ./simulator-example-RocketConfig
No binary specified for emulator
Usage: ./simulator-example-RocketConfig [EMULATOR OPTION]... [VERILOG PLUSARG]... [HOST OPTION]... BINARY [TARGET OPTION]...
Run a BINARY on the Rocket Chip emulator.
Mandatory arguments to long options are mandatory for short options too.
EMULATOR OPTIONS
-c, --cycle-count Print the cycle count before exiting
+cycle-count
-h, --help Display this help and exit
-m, --max-cycles=CYCLES Kill the emulation after CYCLES
+max-cycles=CYCLES
-s, --seed=SEED Use random number seed SEED
-r, --rbb-port=PORT Use PORT for remote bit bang (with OpenOCD and GDB)
If not specified, a random port will be chosen
automatically.
-V, --verbose Enable all Chisel printfs (cycle-by-cycle info)
+verbose
EMULATOR DEBUG OPTIONS (only supported in debug build -- try `make debug`)
-v, --vcd=FILE, Write vcd trace to FILE (or '-' for stdout)
-x, --dump-start=CYCLE Start VCD tracing at CYCLE
+dump-start
EMULATOR VERILOG PLUSARGS
+tilelink_timeout=INT
Kill emulation after INT waiting TileLink cycles. Off if 0.
(default=0)
+max_core_cycles=INT
Kill the emulation after INT rdtime cycles. Off if 0.
(default=0)
HOST OPTIONS
-h, --help Display this help and exit
+permissive The host will ignore any unparsed options up until
+permissive-off (Only needed for VCS)
+permissive-off Stop ignoring options. This is mandatory if using
+permissive (Only needed for VCS)
--rfb=DISPLAY Add new remote frame buffer on display DISPLAY
+rfb=DISPLAY to be accessible on 5900 + DISPLAY (default = 0)
--signature=FILE Write torture test signature to FILE
+signature=FILE
--chroot=PATH Use PATH as location of syscall-servicing binaries
+chroot=PATH
HOST OPTIONS (currently unsupported)
--disk=DISK Add DISK device. Use a ramdisk since this isn't
+disk=DISK supported
TARGET (RISC-V BINARY) OPTIONS
These are the options passed to the program executing on the emulated RISC-V
microprocessor.
EXAMPLES
- run a bare metal test:
./simulator-example-RocketConfig $RISCV/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-p-add
- run a bare metal test showing cycle-by-cycle information:
./simulator-example-RocketConfig +verbose $RISCV/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-p-add 2>&1 | spike-dasm
- run an ELF (you wrote, called 'hello') using the proxy kernel:
./simulator-example-RocketConfig pk hello
Of interest to us right now is the option to run an ELF (Extensible Linked Format) binary using the emulator.
Creating a RISC-V “Hello World” binary
We’re going to generate a simple C “Hello World” program. It isn’t anything fancy. helloworld.c
:
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
We can also go ahead and put together a simple Makefile
.
CC-X86 = gcc
CFLAGS-X86 = -g
CC-RISCV = riscv64-unknown-elf-gcc
CFLAGS-RISCV = -g
RM = rm -f
default: all
all: hello-x86 hello-riscv
hello-x86:
$(CC-X86) $(CFLAGS-X86) -o hello-x86 helloworld.c
hello-riscv:
$(CC-RISCV) $(CFLAGS-RISCV) -o hello-riscv helloworld.c
I’m doing it this way because we may want to test how the binary runs on our native architecture, in my case x86, just to ensure that the C is behaving as I expect it to. The -g
flag produces extra debugging output that a debugger like gdb
can use to examine the binary later, if that’s necessary.
For x86, the compiler is gcc
. For RISC-V, it’s riscv64-unknown-elf-gcc
, which was compiled as part of the RISC-V toolchain in the earlier blog post. So, to compile our helloworld.c
we’re using gcc -g hello-x86 helloworld.c
for x86 and riscv64-unknown-elf-gcc -g hello-riscv helloworld.c
to cross compile into RISC-V. By default, make
will generate both for us.
~/test-binaries/helloworld$ ls
helloworld.c Makefile
~/test-binaries/helloworld$ make
gcc -g -o hello-x86 helloworld.c
riscv64-unknown-elf-gcc -g -o hello-riscv helloworld.c
~/test-binaries/helloworld$ ls
hello-riscv helloworld.c hello-x86 Makefile
brad@artificer:~/test-binaries/helloworld$ ./hello-x86
Hello world!
~/test-binaries/helloworld$ file hello-riscv
hello-riscv: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped
So far, so good.
The RISC-V Proxy Kernel, Running the Binary
To run a binary against the emulator, we need to use the RISC-V Proxy Kernel. The proxy kernel “handles I/O-related system calls by proxying them to a host computer,” and is necessary to view the STDOUT output of your program.
~/chipyard/sims/verilator$ ./simulator-example-RocketConfig pk ~/test-binaries/helloworld/hello-riscv
This emulator compiled with JTAG Remote Bitbang client. To enable, use +jtag_rbb_enable=1.
Listening on port 40615
Hello world!
After a long while the program should deliver us our output and dump us back to the terminal. Be patient! This takes surprisingly long to run. To illustrate this, here is an execution run using time
and the -c
flag to show us number of cycles.
~/chipyard/sims/verilator$ time ./simulator-example-RocketConfig -c pk ~/test-binaries/helloworld/hello-riscv
This emulator compiled with JTAG Remote Bitbang client. To enable, use +jtag_rbb_enable=1.
Listening on port 42497
Hello world!
*** PASSED *** Completed after 531629 cycles
real 1m41.844s
user 1m41.485s
sys 0m0.361s
On the test machine I used, this puts me at a little over 5,000 cycles per second. It’s not fast, but it’s fine for very short test binaries meant to explore the ISA.
Conclusion
At this point we’ve verified the most critical functionality of the Chipyard toolchain on a machine: instantiating an example core and running a test binary of our own design against it. Now we need to be able to instantiate our own, self-defined RISC-V core and run a binary against that, completing our basic toolchain familiarization.