Chipyard: Running a simple Hello World binary against a RISC-V Rocket core

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.