"Hello, world" on a LEON3
In a previous post I explained how to deploy a softcore LEON3 processor on a Digilent Arty-A7 35T development board. Now it is time to run some code on it.
Toolchain
To compile for the LEON3, you need the LEON Bare-C Cross Compilation System (BCC), which is provided by Gaisler for free. It is based on GCC, so it easy to work with if you are used to those tools. BCC can be downloaded from here.
Installing BCC2 is easy: as explained in the user manual, just extract it somewhere and add the folder with executables
to you $PATH
environment variable, and the folder with manual pages to the $MANPATH
environment variable. For
example, if you have extracted the toolchain to /opt
, adding this to your ~/.bashrc
is all that is needed:
export PATH=/tools/bcc-2.2.0-gcc/bin:$PATH
export MANPATH=/tools/bcc-2.2.0-gcc/man:$MANPATH
Hello, world
In the BCC2 archive there is an directory with examples. In it is a README
file, a Makefile
, and a number of
directories with various examples worked out. The folder hello
contains our “hello, world” example, implemented in a
single file hello.c
:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
return EXIT_SUCCESS;
}
Pretty straight forward. From the root of the examples directory (i.e., where the Makefile
is), execute:
$ make CFLAGS="-mcpu=leon3 -msoft-float" hello.elf
This will produce the executable hello.elf
. With the board connected, start GRMON in UART debug mode (-u
):
grmon -digilent -u
Reset the processor and load the binary that was just compiled:
$ grmon -digilent -u
grmon3> reset
grmon3> load hello.elf
40000000 .text 23.0kB / 23.0kB [===============>] 100%
40005C00 .rodata 128B [===============>] 100%
40005C80 .data 1.2kB / 1.2kB [===============>] 100%
Total size: 24.28kB (19.45kbit/s)
Entry point 0x40000000
Image /path/to/examples/hello.elf loaded
The program can be executed via the run
command. Because GRMON was started in UART debug mode, the output is printed
to the GRMON console:
grmon3> run
hello, world
Program exited normally
Flashing to ROM
Instead of having to manually load and run our program, it can also be flashed to ROM. The [MKPROM] (https://www.gaisler.com/index.php/products/boot-loaders/mkprom2) utility takes any executable and embeds it into a an image that takes care of system initialization, then extracts the executable to RAM and runs it. It is basically a tool that adds your program to a bootloader.
MKPROM2 can be downloaded from Gaisler’s download page.
Preparation
The MKPROM2 utility needs to know a few things of the board that it will be compiling for, in particular the frequency
it runs at and the baudrate of the UART. Both can be extracted via GRMON with the info mkprom2
command:
$ grmon -digilent
...
grmon3> info mkprom2
Mkprom2 switches:
-leon3 -freq 83 -baud 38425
The reported frequency should (and it does) match the frequency that was specified when the FPGA bitstream was built. The baudrate is estimated and the value reported here is not a standard baudrate. We need to use the nearest standard rate, which is 38400.
Compiling the example
The same examples folder used before has an mkprom-hello
folder with a Makefile
and a C file that we will be using.
All commands in this section must be executed in this folder.
The example LEON3 design for the Arty A7 does not have a floating point unit (not available in the GPL version of GRLIB) and has single vector trapping model enabled. Combined with the parameters reported by GRMON, the compilation command for the MKPROM example is:
$ make CFLAGS="-mcpu=leon3 -msoft-float -qsvt" MKPROMOPT="-leon3 -msoft-float -qsvt -freq 83 -baud 38400"
The resulting executable has an entry point at 0x0
, as can be observed via obj-dump
:
$ sparc-gaisler-elf-objdump -x hello.prom
hello.prom: file format elf32-sparc
hello.prom
architecture: sparc, flags 0x00000012:
EXEC_P, HAS_SYMS
start address 0x00000000
Program Header:
LOAD off 0x00000060 vaddr 0x00000000 paddr 0x00000000 align 2**5
filesz 0x0000b940 memsz 0x0000b940 flags rwx
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000b940 00000000 00000000 00000060 2**5
...
In the README
inside the design folder for the Digilent Arty-A7 (designs/leon3-digilent-arty-a7/README.txt
) there
is however a very important message:
Typically the lower part of the SPI flash device will hold the configuration bitstream for the FPGA. The SPIMCTRL core is configured with an offset value that will be added to the incoming AHB address before the address is propagated to the SPI flash device. The default offset is 0x00400000 (this value is set via xconfig and the constant is called CFG_SPIMCTRL_OFFSET). When the processor starts after power-up it will read address 0x0, this will be translated by SPIMCTRL to 0x00400000.
SPIMCTRL can only add this offset to accesses made via the core’s memory area. For accesses made via the register interface the offset must be taken into account. This means that if we want to program the Flash with an application which is linked to address 0x0 (our typical bootloader) then we need to add the offset 0x00400000 before programming the file with GRMON. We load the Flash with our application starting at 0x00400000 and SPIMCTRL will then translate accesses from AMBA address 0x0 + n to Flash address 0x00400000 + n.
You can verify this offset in the configuration code for the board support:
$ grep SPIMCTRL_OFFSET leon3-digilent-arty-a7/config.vhd
constant CFG_SPIMCTRL_OFFSET : integer := 16#400000#;
To add the offset, we need to modify the Load Memory Address (LMA) of our binary. This can be easily done with the following command:
$ sparc-gaisler-elf-objcopy --change-section-lma *+0x400000 hello.prom hello.spim
I changed the extension from .prom
to .spim
for clarity. Verify that the LMA address has changed:
$ sparc-gaisler-elf-objdump -x hello.spim
hello.spim: file format elf32-sparc
hello.spim
architecture: sparc, flags 0x00000012:
EXEC_P, HAS_SYMS
start address 0x00000000
Program Header:
LOAD off 0x00000060 vaddr 0x00000000 paddr 0x00400000 align 2**5
filesz 0x0000b940 memsz 0x0000b940 flags rwx
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000b940 00000000 00400000 00000060 2**5
...
Flashing to ROM
Start GRMON and let it detect the flash memory module before proceeding:
$ grmon -digilent -freq 83
...
grmon3> spim flash detect
Got manufacturer ID 0x01 and device ID 0x2018
Detected device: Cypress S25FL128S
The detection step is mandatory. Now load the offset binary to flash; it will be loaded at 0x40000
:
grmon3> spim flash load hello.spim
400000 .text 46.3kB / 46.3kB [===============>] 100%
Total size: 46.30kB (820.42bit/s)
Entry point 0x00000000
Image /path/to/examples/mkprom-hello/hello.spim loaded
To verify that it was successful, one could run the verify command, but we can also just have a quick look at the first four instructions (the address is the address that the processor sees, not the physical address!):
grmon3> disassemble 0 4
0x00000000: a7580000 mov %tbr, %l3
0x00000004: a1480000 mov %psr, %l0
0x00000008: a60ceff0 and %l3, 0xff0, %l3
0x0000000c: a734e004 srl %l3, 0x4, %l3
Compare it with the binary we just built:
$ sparc-gaisler-elf-objdump -D hello.prom | head -n 11
hello.prom: file format elf32-sparc
Disassembly of section .text:
00000000 <_start_svt_real>:
0: a7 58 00 00 rd %tbr, %l3
4: a1 48 00 00 rd %psr, %l0
8: a6 0c ef f0 and %l3, 0xff0, %l3
c: a7 34 e0 04 srl %l3, 4, %l3
Running the program
Open a second terminal and connect it to the UART:
$ screen /dev/ttyUSB1 38400
In the first terminal, in GRMON, instruct the processor run from address 0:
grmon3> run 0
Program exited normally
On the second terminal, you should see something like this:
MKPROM2 boot loader v2.0.67
Copyright Cobham Gaisler AB - all rights reserved
system clock : 83.0 MHz
baud rate : 38425 baud
prom : 512 K, (15/15) ws (r/w)
sram : 2048 K, 1 bank(s), 3/3 ws (r/w)
decompressing .text to 0x40000000
decompressing .rodata to 0x4000f120
decompressing .data to 0x4000f930
starting hello.elf
--- EXAMPLE BEGIN ---
Hello World!
tick 1
tick 2
tick 3
tick 4
tick 5
tick 6
tick 7
tick 8
tick 9
tick 10
--- EXAMPLE END ---
The executable will be run every time the board is powered on, immediately after flash programming. You can verify this
by attaching a screen
session to the serial device and press the reset button on the board. Note that without GRMON
attached, you’ll see two ttyUSBx
devices in /dev
: the first is the JTAG bridge, the second is the UART. On my
computer (MacOS) I can do:
% screen /dev/tty.usbserial-210319B0C2D81 38400