GNU tools for ARC-V

The GNU toolchain for ARC-V processors is a pre-built and verified distribution of a GCC-based set of tools for building applications targeting supported ARC-V processors.

This toolchain is built on the pillars of proven and widely used open-source components such as GCC, Binutils, GDB, and Newlib. While many pre-built GCC-based toolchains are available around the Internet, and it is reasonably easy to build your own, the ARC GNU toolchain is prepared specifically to produce the best possible results for ARC processors. It also makes sure the results produced are reliable in all supported environments.

GNU Toolchain for ARC-V Processors v2024.06

This is the 2024.06 version of the GNU Toolchain for Synopsys ARC-V Processor IP. This is the first release that supports the latest GNU tools for both ARC Classic and ARC-V processors simultaneously.

More information about these processors can be found on Synopsys website:

New Features and Enhancements

There are no major changes for ARC targets except ARC-specific fixes. All components are updated on top of upstream releases.

Binary Distribution

  • Supported host operating systems: Windows 10/11 64-bit, 20.04.x, CentOS/RHEL 7.x

  • Prebuilt bare-metal toolchains for 64-bit Windows and Linux hosts; see table with references and list of artifacts.

Toolchain Components

Known Issues

  • The size-optimized Newlib Nano configuration (used when -specs=nano.specs is passed to GCC) does not support printf() for float and double by default. Nano printf() is size-optimized and does not include support of float and double. If you need that feature, pass -u _printf_float to GCC when you compile your applications. This option picks up support of float and double for size optimized printf() on demand.

  • Some complex combinations of -march= and -mabi= options might lead to unpredictable errors during compilation. Such issues are related to general support of RISC-V extensions in GCC. See #610.

  • GCC 14 generates incorrect include directories if it’s configured with the --with-sysroot=... and --with-native-system-header-dir=... options. This is a valid set of options for building GCC, but it leads to GCC including invalid paths in an include search list when -save-temps is used while building binaries. This issue is not critical and will be addressed later. See #628.

Getting Help

Visit the GitHub discussions link for a community forum tailored to ARC-V processors. To report issues and enhancement requests, use GitHub issues (if you are not sure, start with discussions, as a discussion can always be converted into an issue).

Prebuilt Toolchains Available for Download

Archive SHA-256 checksums are:

b7c2b3f1ce3cda1d1472a0412c474e9a6ca1fe5ee48db4910664050456e28ceb arc_gnu_2024.06-rc1_prebuilt_riscv64_elf_le_win_install.tar.bz2
02580b4b8194695c3f5a6248fc52c1c0e16b252d7b4c2a24a059f653e2eb9eef arc_gnu_2024.06-rc1_prebuilt_riscv64_elf_le_linux_install.tar.bz2

GNU Toolchain for ARC-V Processors v2023.12

This is the 2023.12 version of the GNU Toolchain for Synopsys ARC-V Processor IP. This release introduces support for the ARC-V RMX and RHX processors series based on the RISC-V instruction-set architecture.

More information about these processors can be found on Synopsys website:

Note

This release of the ARC GNU tools doesn’t include prebuilt toolchains for ARC Classic processor families. Users of ARC 600, ARC 700, ARC EM, ARCv2 HS (HS3x and HS4x) and ARCv3 HS (HS5x and HS6x) should keep using ARC GNU Tools 2023.09 (latest version).

New Features and Enhancements

Support for RISC-V based ARC-V RMX and RHX Processors

  • In this release the following RISC-V extensions are supported: Zba, Zbb, Zbs, Zdinx, Zfinx, Zicbom, Zicbop, and Zicboz.

  • Support for ARC-V RMX and RHX processors:

    • Instruction scheduling tuning for ARC-V RMX-100 (-mtune=rmx100) and RMX-500 (-mtune=rmx500)

    • Instruction fusion for the ARC-V RHX series (-mtune=rhx)

    • Implementation of the code-size reduction extensions Zcb and Zcmp

  • As this is the first release of prebuilt GNU tools with ARC-V support, some functionality might have issues, some processor features might not yet be fully supported, and performance might be lower than expected performance of the final product.

Binary Distribution

  • Supported host operating systems: Windows 10/11 64-bit, Ubuntu 18.04.x, 20.04.x, CentOS/RHEL 7.x

  • Prebuilt bare-metal toolchains for 64-bit Windows & Linux hosts; see table with references and list of artifacts.

Toolchain Components

  • GCC 13.2 with ARC patches

  • Binutils pre-2.41 with ARC patches

  • GDB 12.1 with ARC patches

  • Newlib 4.3.0 with ARC patches

    • Updated sources of 4.3.0 release; see Newlib release announcement.

    • Improved start-up code to initialize GP (global pointer), SP (stack pointer), clear .bss section, and similar enhancements.

    • Provided custom linker command file for use with fast on-chip memories (DCCM and ICCM).

Known Issues

No known issues for this release so far.

Getting Help

Also visit the GitHub discussions link for a community forum tailored to ARC-V processors. To report issues and enhancement requests, use GitHub issues (if you are not sure, start with discussions, since a discussion can always be converted into an issue).

Prebuilt Toolchains Available for Download

Archive SHA-256 checksums are:

4e555c1b214eb97fd3bf9e1ffa43d69a0d274deb295fec66bdfdebef714b7bf7 arc_gnu_2023.12_prebuilt_riscv64_elf_le_win_install.tar.bz2
9dedb6e496e52a45ce536c9a1970a44401319c42ea17b7b8f6655b6204b9b444 arc_gnu_2023.12_prebuilt_riscv64_elf_le_linux_install.tar.bz2

Command-Line Reference

Because the ARC-V GNU toolchain is built on top of standard components such as GCC, Binutils, and GDB, all functionality of these tools stays in place and can be studied in detail in the corresponding manuals:

On top of the functionality that comes with the standard releases of these tools, the ARC GNU toolchain adds some ARC-V specific options:

The ARC-V GNU compiler has support for the ARC-V RMX-100, RMX-500, and RHX CPU families. To enable compiler optimizations for those CPU families you must use certain -mtune options. The valid -mtune options are:

  • arc-v-rmx-100-series for the ARC-V RMX-100 family (rmx100 in v2023.12 and v2024.06 releases)

  • arc-v-rmx-500-series for the ARC-V RMX-500 family (rmx500 in v2023.12 and v2024.06 releases)

  • arc-v-rhx-100-series for the ARC-V RHX-100 family (rhx in v2023.12 and v2024.06 releases)

The architecture options used by the ARC-V RMX-100, RMX-500 and RHX-100 families are -march=rv32im_zba_zbb_zbs_zicsr_zca_zcb_zcmp -mabi=ilp32.

ARC-V .specs File

The new arcv.specs file uses a custom starting routine based on the original crt0.S with a special linker command file.

The latter is highly configurable, introducing four new linker defines:

  • txtmem_addr if defined sets the ICCM start address; otherwise the ICCM start address is set to 0x0

  • txtmem_len if defined sets the ICCM length; otherwise the length is set to 128K

  • datamem_addr if defined sets the DCCM start address; otherwise the DCCM start address is set to 0x8000_0000

  • datamem_len if defined sets the DCCM length, otherwise the length is set to 128K

Where:

  • ICCM is usually flash memory or ROM.

  • DCCM is usually SRAM.

  • You can set a linker define as follows (using the GCC driver): For example: -Wl,-defsym=datamem_addr=0x10000

This spec file can be used with other spec files too. For example, using semihosting: --specs=semihost.specs  --specs=arcv.specs.

The First Sample Application

Consider the following sample application:

#include <stdio.h>

extern void init_semihosting ();

int main(void) {
   init_semihosting();
   printf("Hello world!\n");
   return 0;
}

Build and Run a Simple Application for Synopsys nSIM

The following commands can be used for compiling the sample for later execution in the nSIM simulator:

  • For an ARC-V RMX-100 processor:

    $ riscv64-elf-gcc --specs=semihost.specs --specs=arcv.specs -mabi=ilp32 \
                      -mtune=arc-v-rmx-100-series -march=rv32im_zba_zbb_zbs_zca_zcb_zcmp_zicsr \
                      -T arcv.ld test.c -o a.out
    
  • For an ARC-V RMX-500 processor:

    $ riscv64-elf-gcc --specs=semihost.specs --specs=arcv.specs -mabi=ilp32 \
                      -mtune=arc-v-rmx-500-series -march=rv32im_zba_zbb_zbs_zca_zcb_zcmp_zicsr \
                      -T arcv.ld test.c -o a.out
    
  • For an ARC-V RHX-100 processors:

    $ riscv64-elf-gcc --specs=semihost.specs --specs=arcv.specs -mabi=ilp32 \
                      -mtune=arc-v-rhx-100-series -march=rv32im_zba_zbb_zbs_zca_zcb_zcmp_zicsr \
                      -T arcv.ld test.c -o a.out
    

After building, use the following command line to run the sample in nSIM:

$ nsimdrv -prop=nsim_isa_family=rv32 \
  -prop=nsim_isa_ext=-all.i.zicsr.zifencei.zihintpause.b.zca.zcb.zcmp.zcmt.a.m.zbb.zba.zbs \
  -prop=nsim_semihosting=1 -off=enable_exceptions a.out

Hello world!

For more details on use of the Synopsys nSIM simulator, see the section dedicated to it here: nSIM and NCAM.

Build and Run a Simple Application for QEMU (32-Bit Targets)

It is best to use the latest official QEMU version for the following exercises. As of today, it’s v9.0; see the release notes here: https://wiki.qemu.org/ChangeLog/9.0. The sources are on GitLab (https://gitlab.com/qemu-project/qemu) or in the official GitHub mirror (https://github.com/qemu/qemu).

Note

To get the latest version of QEMU on your system it might be much easier to build it from sources rather than trying to find a pre-built version which suits your host type and operating system.

Instructions for building on Linux are on QEMU’s Wiki: https://wiki.qemu.org/Hosts/Linux

First you need to install required additional packages, which depend on the Linux distribution used on the host.

For example, for Ubuntu 20.04 the following needs to be done:

$ apt install build-essential git libglib2.0-dev libfdt-dev \
              libpixman-1-dev zlib1g-dev ninja-build python3-venv

For AlmaLinux 8 the following needs to be done:

$ dnf install git glib2-devel libfdt-devel pixman-devel zlib-devel \
              bzip2 ninja-build python3 python38

After installation of prerequisites you can download sources, configure, and build in the following way:

# Clone v9.0 from GitHub repository
$ git clone --depth 1 -b stable-9.0 https://github.com/qemu/qemu.git

# Configure project for build selecting only RISCV32 full system emulation
$ ./configure --target-list=riscv32-softmmu

# Build QEMU
$ make

After the build is complete qemu-system-riscv32 is placed in the build folder, which you can add to the PATH environment variable for convenience, and use the short binary name instead of the full path.

The following commands can be used for compiling the sample for later execution in QEMU. Basically the same compiler options are used, except that you need to make sure DDR memory is used and the .text section starts in the DDR beginning at 0x8000_0000, where the board jumps on start automatically. For that, you need to override two linker symbols otherwise defined in ARC-V’s default linker script arcv.ld: txtmem_addr and datamem_addr.

Then the command lines look like this:

  • For an ARC-V RMX-100 processor:

    $ riscv64-elf-gcc -specs=semihost.specs -specs=arcv.specs -mabi=ilp32 \
                      -mtune=arc-v-rmx-100-series -march=rv32im_zba_zbb_zbs_zca_zcb_zcmp_zicsr \
                      -Wl,-defsym=txtmem_addr=0x80000000 -Wl,-defsym=datamem_addr=0x80100000 \
                      -T arcv.ld test.c -o a.out
    
  • For an ARC-V RMX-500 processor:

    $ riscv64-elf-gcc -specs=semihost.specs -specs=arcv.specs -mabi=ilp32 \
                      -mtune=arc-v-rmx-500-series -march=rv32im_zba_zbb_zbs_zca_zcb_zcmp_zicsr \
                      -Wl,-defsym=txtmem_addr=0x80000000 -Wl,-defsym=datamem_addr=0x80100000 \
                      -T arcv.ld test.c -o a.out
    
  • For an ARC-V RHX-100 processor:

    $ riscv64-elf-gcc -specs=semihost.specs -specs=arcv.specs -mabi=ilp32 \
                      -mtune=arc-v-rhx-100-series -march=rv32im_zba_zbb_zbs_zca_zcb_zcmp_zicsr \
                      -Wl,-defsym=txtmem_addr=0x80000000 -Wl,-defsym=datamem_addr=0x80100000 \
                      -T arcv.ld test.c -o a.out
    

After building, use the following command line to run the sample in QEMU:

$ qemu-system-riscv32 -semihosting -nographic -machine virt -cpu rv32 -bios none -kernel a.out

Hello world!

For more details on use of the QEMU simulator, see the official documentation here: https://www.qemu.org/docs/master/.

Build and Run a Simple Application for QEMU (64-Bit Targets)

According to the RISC-V ABI specification:

The medium low code model, or medlow, allows the code to address the whole RV32 address space or the lower
2 GiB and highest 2 GiB of the RV64 address space (0xFFFFFFFF7FFFF800 ~ 0xFFFFFFFFFFFFFFFF and
0x0 ~ 0x000000007FFFF7FF). By using the lui and load / store instructions, when referring to an
object, or addi, when calculating an address literal, for example, a 32-bit address literal can
be produced.

The medium any code model, or medany, allows the code to address the range between -2 GiB and +2 GiB
from its position. By using auipc and load / store instructions, when referring to an object, or addi,
when calculating an address literal, for example, a signed 32-bit offset, relative to the value of the
pc register, can be produced.

All pseudo-options like li, la, and so on, are transformed to real instructions according to the memory model used. By default, GCC uses -mcmodel=medlow. That means that the final binary cannot use memory regions above 0x000000007FFFF7FF and below 0xFFFFFFFF7FFFF800 for 64-bit targets.

To overcome those restrictions, it is necessary to pass -mcmodel=medany to choose the medany memory model. Note that the Synopsys GNU toolchain for ARC-V contains precompiled libraries for both the medlow and medany memory models. When -mcmodel=... is passed the appropriate libraries are linked with an application.

Consider this example:

#include <stdio.h>

int main(int argc, char *argv[])
{
        printf("argc: %d\n", argc);
        for (int i = 0; i < argc; i++)
        {
                printf("argv[%d] = %s\n", i, argv[i]);
        }
        return 0;
}

Compiler this program and run using QEMU:

$ riscv64-elf-gcc \
      -march=rv64imac -mabi=lp64 -mcmodel=medany -O2 -g3 \
      -Wl,-defsym=txtmem_addr=0x80000000 -Wl,-defsym=datamem_addr=0x80100000 \
      -specs=semihost.specs -specs=arcv.specs -T arcv.ld \
      main.c -o main.elf
$ qemu-system-riscv64 \
      -semihosting -nographic -display none -monitor none \
      -machine virt -cpu rv64 -bios none -kernel main.elf -append "1 2 3"
argc: 4
argv[0] = main.elf
argv[1] = 1
argv[2] = 2
argv[3] = 3

Debugging a Simple Application

With the Synopsys LLDB Debugger and Visual Studio Code IDE

For detailed information on how to debug an application with the Synopsys LLDB Debugger and Visual Studio Code IDE, see the following section: The MetaWare Development Toolkit.

With QEMU and GDB

You can debug an application that is being run on QEMU with help of the built-in GDB server of QEMU. On the QEMU side, you need to enable the GDB sever and optionally instruct the QEMU not to start the application execution automatically, but instead wait for user input. For that you need to pass -s -S commands to qemu-system-riscv32 so that the full command-line looks like this:

$ qemu-system-riscv32 -semihosting -nographic -machine virt -cpu rv32 -bios none -kernel a.out -s -S

Then you can attach the GDB client and start debugging. Do the following in a separate console:

$ riscv64-elf-gdb a.out

(gdb) target remote :1234
Remote debugging using :1234
0x00001000 in ?? ()

(gdb) b main
Breakpoint 1 at 0x800000b4: file ~/test.c, line 10.

(gdb) continue
Continuing.

Breakpoint 1, main () at ~/test.c:10
10              init_semihosting();

(gdb) c
Continuing.
[Inferior 1 (process 1) exited normally]

Note that the first location where the CPU is waiting for commands is 0x0000_1000. It’s a virt board reset vector location; see https://elixir.bootlin.com/qemu/v8.2.0/source/hw/riscv/boot.c#L396. It is not possible to suppress execution of that reset vector code as it’s an essential part of the QEMU board (think of it as a boot-ROM). You can only bypass it if you load the target executable from the GDB client with the load command. Then execution starts from the loaded application entry point.

Testing Specific RISC-V Extensions

One of the benefits of RISC-V ISA is its extensibility. As a matter of fact, only a handful of instructions form the minimally required base. But the most interesting part is in utilizing application-specific extensions, which let you achieve benefits in certain situations.

In embedded and especially deeply embedded applications, one of the key objectives is to keep the memory footprint of the target application as tiny as possible. And the RISC-V Zc* extensions help to achieve exactly that. This section demonstrates a couple of those extensions in action. See also the documentation and related information on each extension on the corresponding GitHub resource here: https://github.com/riscv/riscv-code-size-reduction.

Zcmp Extension

Consider the following code snippet:

$ cat t01.c

void test (void) {
    asm volatile (""
             :
             :
             : "s0", "s1", "s2", "s3",
               "s4", "s5", "s6", "s7",
               "s8", "s9", "s10", "s11");
}

Here is a generated assembly listing targeting the Zcmp extension:

$ riscv64-elf-gcc -march=rv32im_zcmp -mabi=ilp32 t01.c -S -O1 -o -

    .file   "t01.c"
    .option nopic
    .attribute arch, "rv32i2p1_m2p0_zca1p0_zcmp1p0"
    .attribute unaligned_access, 0
    .attribute stack_align, 16
    .text
    .align  1
    .globl  test
    .type   test, @function
test:
     cm.push {ra, s0-s11}, -64
     cm.popret       {ra, s0-s11}, 64
     .size   test, .-test
     .ident  "GCC: (RISC-V elf toolchain - build 6278) 13.2.0"

Note the generated cm.push and cm.popret instructions, which confirm that the compiler is capable of using the extension.

Zcb Extension

Another RISC-V extension that is easy to see in action also comes from the code-size-reduction group: Zcb. Consider the following C function:

$ cat t03.c

int test (int a, int b) {
    return a + b;
}

Now compile and disassemble it:

$ riscv64-elf-gcc -march=rv32im_zca_zcb -mabi=ilp32 t03.c -c -O1
$ riscv64-elf-objdump -d t03.o

t03.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <test>:
   0:   952e                    add     a0,a0,a1
   2:   8082                    ret

Note the short 16-bit encoding of the add instruction. Without use of Zc extensions, compiler generates a full 32-bit encoding as in the example below.

$ riscv64-elf-gcc -march=rv32im -mabi=ilp32 t03.c -c -O1
$ riscv64-elf-objdump -d t03.o

t03.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <test>:
   0:   00b50533                add     a0,a0,a1
   4:   00008067                ret

Building a Toolchain for a Particular Architecture

The GNU toolchain for ARC-V targets contains a set of precompiled standard libraries for different combinations of -march, -mabi and -mcmodel. You can find a list of available configurations this way:

$ riscv64-elf-gcc --print-multi-lib
.;
rv32i/ilp32;@march=rv32i@mabi=ilp32
rv32i/ilp32/medany;@march=rv32i@mabi=ilp32@mcmodel=medany
rv32iac/ilp32;@march=rv32iac@mabi=ilp32
rv32iac/ilp32/medany;@march=rv32iac@mabi=ilp32@mcmodel=medany
rv32im/ilp32;@march=rv32im@mabi=ilp32
rv32im/ilp32/medany;@march=rv32im@mabi=ilp32@mcmodel=medany
rv32imac/ilp32;@march=rv32imac@mabi=ilp32
rv32imac/ilp32/medany;@march=rv32imac@mabi=ilp32@mcmodel=medany
rv32imafc_zicsr/ilp32f;@march=rv32imafc_zicsr@mabi=ilp32f
rv32imafc_zicsr/ilp32f/medany;@march=rv32imafc_zicsr@mabi=ilp32f@mcmodel=medany
rv32imafd_zicsr/ilp32d;@march=rv32imafd_zicsr@mabi=ilp32d
rv32imafd_zicsr/ilp32d/medany;@march=rv32imafd_zicsr@mabi=ilp32d@mcmodel=medany
rv32imafdc_zicsr/ilp32d;@march=rv32imafdc_zicsr@mabi=ilp32d
rv32imafdc_zicsr/ilp32d/medany;@march=rv32imafdc_zicsr@mabi=ilp32d@mcmodel=medany
rv64imac/lp64;@march=rv64imac@mabi=lp64
rv64imac/lp64/medany;@march=rv64imac@mabi=lp64@mcmodel=medany
rv64imafdc_zicsr/lp64d;@march=rv64imafdc_zicsr@mabi=lp64d
rv64imafdc_zicsr/lp64d/medany;@march=rv64imafdc_zicsr@mabi=lp64d@mcmodel=medany

Libraries for this toolchain were built with these combinations of configurations:

  • -march=rv32i -mabi=ilp32 -mcmodel=medlow

  • -march=rv32i -mabi=ilp32 -mcmodel=medany

  • -march=rv32imafc_zicsr -mabi=ilp32f -mcmodel=medlow

The easiest way to build a toolchain for another custom configuration is to add a combination to the Crosstool-NG configuration. You can find a comprehensive guide about how to get and prepare Crosstool-NG here: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain.

First, set up a standard ARC-V configuration file and enter a configuration menu:

./ct-ng snps-riscv64-unknown-elf
./ct-ng menuconfig

For example, if you want to build Crosstool-NG with just these configurations:

  • -march=rv32imafc_zicond_zicsr_zifencei -mabi=ilp32f

  • -march=rv64imafdc_zicsr -mabi=lp64d

Navigate to the C compiler menu and replace the value of Generator of RISC-V multilib variants with this string: rv32imafc_zicond_zicsr_zifencei-ilp32f--;rv64imafdc_zicsr-lp64d--; (place it right after the gcc extra config field).

Then build the toolchain as usual:

./ct-ng build

The output toolchain is located in the riscv64-unknown-elf directory:

$ ./riscv64-unknown-elf/bin/riscv64-elf-gcc --print-multi-lib
.;
rv32imafc_zicond_zicsr_zifencei/ilp32f;@march=rv32imafc_zicond_zicsr_zifencei@mabi=ilp32f
rv64imafdc_zicsr/lp64d;@march=rv64imafdc_zicsr@mabi=lp64d

Now when you use -march=rv32imafc_zicond_zicsr_zifencei -mabi=ilp32f or compatible combinations, a corresponding precompiled library is used.