User:Vazhnov/PlatformIO + VIM + ccls + Sipeed Longan nano RISC-V

From Gentoo Wiki
Jump to:navigation Jump to:search
Note
Even though this page is in the user namespace, corrections and additions are much appreciated! This is simply wiki policy, this page can be moved to the main wiki as soon as it achieves critical mass more.

Intro

I'm not a developer.

For my job, I have to edit some shell and Python scripts. I use Vim + vim-syntastic + linters (pylint, shellcheck).

As a hobby, I'm trying to learn how to program in Asm and C for architecture RISC-V.

PlatformIO

PlatformIO is a popular platform with good support of multiple boards (like Arduino, STM32, …).

PlatformIO install

PlatformIO can be installed by Python's package management system pip. Here is an example how to install PlatformIO with Python virtualenv venv, to not interfere with your system:

user $python3 -m venv --system-site-packages /tmp/venv_platformio
user $source /tmp/venv_platformio/bin/activate
user $pip3 install platformio

Disable telemetry, if you want:

user $pio settings set enable_telemetry No
user $pio settings get

Test:

user $pio boards
Platform: gd32v
============================================================================================
ID                       MCU            Frequency    Flash    RAM    Name
-----------------------  -------------  -----------  -------  -----  -----------------------
gd32vf103v-eval          GD32VF103VBT6  108MHz       128KB    32KB   GD32VF103V-EVAL
sipeed-longan-nano       GD32VF103CBT6  108MHz       128KB    32KB   Sipeed Longan Nano
sipeed-longan-nano-lite  GD32VF103C8T6  108MHz       64KB     20KB   Sipeed Longan Nano Lite
wio_lite_risc-v          GD32VF103CBT6  108MHz       128KB    32KB   Wio Lite RISC-V

PlatformIO init

user $mkdir -pv /tmp/longan-sandbox && cd /tmp/longan-sandbox
user $pio project init --ide vim --board sipeed-longan-nano

When run at the first time, PlatformIO downloads:

After the last command, pio creates an empty structure + file .ccls which is important for a syntax checker(s):

FILE .ccls
clang

%c -std=gnu11 -Os -Wall -march=rv32imac -mabi=ilp32 -mcmodel=medlow -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -fno-common
%cpp -std=gnu++17 -Os -Wall -march=rv32imac -mabi=ilp32 -mcmodel=medlow -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -fno-common

-I/tmp/longan-sandbox/include
-I/tmp/longan-sandbox/src
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/GD32VF103_standard_peripheral
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/GD32VF103_standard_peripheral/Include
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/GD32VF103_usbfs_driver
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/GD32VF103_usbfs_driver/Include
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/RISCV/drivers
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/RISCV/env_Eclipse
-I/home/user/.platformio/packages/framework-gd32vf103-sdk/RISCV/stubs
-I/home/user/.platformio/packages/toolchain-gd32v/riscv-nuclei-elf/include/c++/9.2.0
-I/home/user/.platformio/packages/toolchain-gd32v/riscv-nuclei-elf/include/c++/9.2.0/riscv-nuclei-elf
-I/home/user/.platformio/packages/toolchain-gd32v/lib/gcc/riscv-nuclei-elf/9.2.0/include
-I/home/user/.platformio/packages/toolchain-gd32v/lib/gcc/riscv-nuclei-elf/9.2.0/include-fixed
-I/home/user/.platformio/packages/toolchain-gd32v/riscv-nuclei-elf/include

-DPLATFORMIO=50203
-DUSE_STDPERIPH_DRIVER
-DHXTAL_VALUE=8000000U

PlatformIO integration with different IDE

Inside a directory with source code, it is possible to create a file structure to help your favourite IDE to catch settings.

user $pio project init --ide [atom|clion|codeblocks|eclipse|emacs|netbeans|qtcreator|sublimetext|vim|visualstudio|vscode]

In common, PlatformIO creates such structure:

user $tree -a
.
├── .gitignore
├── include
│   └── README
├── lib
│   └── README
├── .pio
│   └── build
│       ├── project.checksum
│       └── sipeed-longan-nano
│           └── idedata.json
├── platformio.ini
├── src
└── test
    └── README

7 directories, 7 files

Differences described below:

  • --ide atom: creates configuration files .clang_complete and .gcc-flags.json,
  • --ide clion: creates configuration files CMakeLists.txt and CMakeListsPrivate.txt,
  • --ide codeblocks: creates configuration file platformio.cbp (XML),
  • --ide eclipse: creates configuration files .cproject and .project (both are XML) and a directory .settings/,
  • --ide emacs: creates a configuration file .ccls with clang options,
  • --ide netbeans: creates a configuration files nbproject/configurations.xml and nbproject/project.xml,
  • --ide qtcreator: creates a configuration file platformio.pro,
  • --ide sublimetext: creates a configuration files .ccls and platformio.sublime-project (JSON),
  • --ide vim: creates a configuration file .ccls with clang options,
  • --ide visualstudio: creates a configuration files platformio.vcxproj and platformio.vcxproj.filters (both are XML),
  • --ide vscode: creates a configuration files .vscode/c_cpp_properties.json, .vscode/extensions.json and .vscode/launch.json.

Also,

user $pio run -t compiledb

creates compilation database compile_commands.json which can be used by both ccls and clangd.

vim

When using Vim + vim-syntastic, it is hard to work with C/C++ code because of errors like

/usr/lib/gcc/riscv64-linux-gnu/11/include/stdint.h|9 col 16 error| fatal error: stdint.h: No such file or directory [c/gcc]

Solution: use language server + LSP client. As an option, use language server dev-util/ccls + Native LSP client from app-editors/neovim.

Assembly in VIM

Attempt to open RISC-V assembly file with Vim will result with a bunch of errors:

hello.s|4 error| Error: no such instruction: `li s1,0x10000000' [asm/gcc]
hello.s|5 error| Error: no such instruction: `la s2,message' [asm/gcc]
hello.s|6 error| Error: no such instruction: `addi s3,s2,14' [asm/gcc]
hello.s|8 error| Error: no such instruction: `lb s4,0(s2)' [asm/gcc]
hello.s|9 error| Error: no such instruction: `sb s4,0(s1)' [asm/gcc]

Because vim-syntastic tries to check syntax with system gcc, which doesn't know about RISC-V commands.

Put one of examples into any vimrc file:

  • To use RISC-V check for all assembly files:
    FILE ~/.config/nvim/filetype.vim
    augroup AsmRISCV
      autocmd FileType asm
       \ let g:syntastic_asm_generic = 1 |
       \ let b:syntastic_asm_gcc_exec = '~/.platformio/packages/toolchain-gd32v/bin/riscv-nuclei-elf-gcc'
    augroup END
    
  • or, to use RISC-V check for some paths only:
    FILE ~/.config/nvim/filetype.vim
    augroup AsmRISCV
      autocmd FileType asm
        \ if stridx(expand("%:path"), $HOME.'/path/to/risc-v/asm/project-1/') == 0 ||
        \    stridx(expand("%:path"), $HOME.'/path/to/risc-v/asm/project-2/') == 0 |
        \   let g:syntastic_asm_generic = 1 |
        \   let b:syntastic_asm_gcc_exec = '~/.platformio/packages/toolchain-gd32v/bin/riscv-nuclei-elf-gcc' |
        \ endif
    augroup END
    

ccls

dev-util/ccls is required by Neovim to check code syntax/style. It is still in "testing" state in Gentoo Portage, so:

root #echo "dev-util/ccls ~amd64" >> /etc/portage/package.accept_keywords
root #emerge --ask dev-util/ccls

Check:

user $ccls --version
ccls version 0.20210330
clang version 13.0.0

Neovim

app-editors/neovim v0.5 supports the Language Server Protocol (LSP), which is needed, so testing versions should be allowed:

root #echo "app-editors/neovim ~amd64" >> /etc/portage/package.accept_keywords

Install Neovim and Vim plug as it described in Neovim.

Create a configuration file:

FILE ~/.config/nvim/init.vim
call plug#begin('~/.local/share/nvim/site/plugged')
Plug 'vim-syntastic/syntastic'
Plug 'neovim/nvim-lspconfig'
call plug#end()

Run nvim, execute:

:PlugStatus
:PlugInstall
:UpdateRemotePlugins
:q

Then add a code from https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#ccls at the end of the configuration file:

FILE ~/.config/nvim/init.vim
local lspconfig = require'lspconfig'
lspconfig.ccls.setup {
  init_options = {
    compilationDatabaseDirectory = "build";
    index = {
      threads = 0;
    };
    clang = {
      excludeArgs = { "-frounding-math"} ;
    };
  }
}

udev rules

Create an udev configuration file:

FILE /etc/udev/rules.d/99-platformio-udev.rules
# GD32V DFU Bootloader
ATTRS{idVendor}=="28e9", ATTRS{idProduct}=="0189", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"

Or follow the official documentation: 99-platformio-udev.rules.

dfu-util

Note
PlatformIO uses its own binary from ~/.platformio/packages/tool-dfuutil/, so the step below is not mandatory.

app-mobilephone/dfu-util — device firmware update (DFU) USB programmer.

root #emerge --ask app-mobilephone/dfu-util

Check:

Boot your Sipeed Longan nano board with "Boot0" button pressed.

user $dfu-util -l
dfu-util 0.11

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2021 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Found DFU: [28e9:0189] ver=1000, devnum=2, cfg=1, intf=0, path="5-1", alt=1, name="@Option Bytes  /0x1FFFF800/01*016 g", serial="3CBJ"
Found DFU: [28e9:0189] ver=1000, devnum=2, cfg=1, intf=0, path="5-1", alt=0, name="@Internal Flash  /0x08000000/512*002Kg", serial="3CBJ"

QEMU

Let's play with assembler without a real hardware:

FILE hello_poweroff_qemu.s
# Based on https://git.sr.ht/~matthias_t/hello-riscv/tree/master/item/src/hello.s

.section .init
.global _start
_start:
    li s1, 0x10000000 # s1 := 0x1000_0000
    la s2, message    # s2 := <message>
    addi s3, s2, 14   # s3 := s2 + 14
L1:
    lb s4, 0(s2)      # s4 := (s2)
    sb s4, 0(s1)      # (s1) := s4
    addi s2, s2, 1    # s2 := s2 + 1
    blt s2, s3, L1    # if s2 < s3, branch back to L1

    # Exit from QEMU, see QEMU sources: `hw/misc/sifive_test.c`, `include/hw/misc/sifive_test.h`.
    # Based on https://github.com/rtfb/riscv64-in-qemu/blob/master/src/baremetal-poweroff.s
    .equ FINISHER_FAIL,  0x3333
    .equ FINISHER_PASS,  0x5555
    .equ FINISHER_RESET, 0x7777
    li t0, FINISHER_PASS
    li t1, 0x100000
    sw t0, 0(t1)

# If QEMU exit doesn't work:
# Infinitive wait for interrupt — good to not heat up CPU
L2:
    wfi
    j L2

.section .data
message:
  .string "Hello, world!\n"

If binaries from PlatformIO are used:

user $export PATH="$PATH:$HOME/.platformio/packages/toolchain-gd32v/bin"

Compile:

user $riscv64-unknown-elf-as hello_poweroff_qemu.s -g -o hello_poweroff_qemu.elf

Create a file, to tell the linker we want to take manual control of the memory layout:

FILE qemu-riscv64-virt.ld
MEMORY
{
  ram (rwxai) : ORIGIN = 0x80000000, LENGTH = 0x08000000
}

Do some magic:

user $riscv64-unknown-elf-ld -T qemu-riscv64-virt.ld -o hello_poweroff_qemu_linked.elf hello_poweroff_qemu.elf

And run:

user $qemu-system-riscv64 -machine virt -bios none -kernel hello_poweroff_qemu_linked.elf -nographic
Hello, world!

Test project: gd32v-lcd

Prepare a test project:

user $git clone -b dev 'git@github.com:vazhnov/gd32v-lcd.git'
user $cd gd32v-lcd
user $pio project init --ide vim --board sipeed-longan-nano

Run neovim:

user $nvim -n -- src/main.c

Execute:

:LspInfo

You should see:

Language client log: /home/user/.cache/nvim/lsp.log
Detected filetype:   c


Client: ccls (id: 1, pid: 5947, bufnr: [1])
 filetypes:       c, cpp, objc, objcpp
 autostart:       true
 root directory:  /tmp/longan-sandbox/gd32v-lcd
 cmd:             ccls

Configured servers list: ccls

Compile project with PlatformIO:

user $pio run

Flash the SoC (will also download and install ~/.platformio/packages/tool-openocd-gd32v):

user $pio run --target upload

(error "dfuse_download: libusb_control_transfer returned -4" is OK).

Board will restart automatically with a new code, and you will see an bouncing amiga ball.

Dig inside

PlatformIO uses ~/.platformio/packages/toolchain-gd32v/bin/riscv-nuclei-elf-gcc to compile the code.

It is possible to pass additional options to the GCC with build_flags option of platformio.ini in your project. For example, lets keep all assembly source files after compilation:

FILE platformio.ini
[env:sipeed-longan-nano]
platform = gd32v
framework = gd32vf103-sdk
board = sipeed-longan-nano
monitor_speed = 115200
upload_protocol = dfu
build_flags =
  -save-temps
  -fverbose-asm

Also, let's create a disassemble of whole binary firmware:

user $~/.platformio/packages/toolchain-gd32v/bin/riscv-nuclei-elf-objdump -D -S --wide .pio/build/sipeed-longan-nano/firmware.elf > firmware.lss

See also

  • Arduino — how to prepare the toolchain, how to use Eclipse IDE;
  • RISC-V Multilib — but rv32i part is not written yet;
  • RISC-V ABIs — describe their properties: pointer sizes, compatibility, gcc flags, default linker path.

External links