Hands on Risc-V (RV32IMAC) assembler : Part 1

Setting up the development environment

     

I was looking around for a board to tinker with RV32 assembly language as a way of getting to know the architecture a bit better. I tried using a WCH-Link debugger module and a CH32VF103 board but so far I have had no success using OpenOCD with it. I have opted instead to use a Longan Nano GD32VF103 in conjunction with a J-Link Edu debugger. This worked well enough for me to get going although the debug interface appears to be very sensitive to noise.

Using the Jlink tools from Segger a GDB link to the target as follows:
JLinkGDBServer -device GD32VF103C8T6 -if JTAG

First code.

My goal here is to get started into RISC-V assembler with the minimum amount of fuss. When the Longan-Nano GD32VF103 boots it begins executing code at address 0. Typically this code would initialize global and static variables, set the stack pointer and then call on main. For this particular architecture it also needs to set up the interrupt controller. I will do this at a later time. For now I will work without interrupts.

/* init.s
 Initialization routine which sets the stack pointer, 
 sets initial global values and clears those that are not
 specifically initialized.  Assumes that the linker script aligned 
 data sections along a word (4 byte) boundary.
*/
	.global Reset_Handler
	.extern INIT_DATA_VALUES
	.extern INIT_DATA_START
	.extern INIT_DATA_END
	.extern BSS_START
	.extern BSS_END
	.extern main
	.section start
Reset_Handler:
	lui sp,0x20005 # set stack pointer to top of RAM
# Fill global and static variables with initial values
	la	t0,INIT_DATA_VALUES
	la  t1,INIT_DATA_START
	la  t2,INIT_DATA_END
init_data_store_loop:
	beq t1,t2,done_init_data
	lw  a0,0(t0)
	sw  a0,0(t1)
	addi t0,t0,4
	addi t1,t1,4
	j init_data_store_loop
done_init_data:
# Fill uninitialized global and static variables with zero
	la	t0,BSS_START
	la  t1,BSS_END
zero_data_store_loop:
	beq t0,t1,done_zero_data
	sw  x0,0(t1)	
	addi t0,t0,4
	j zero_data_store_loop
done_zero_data:
# call main C code
	jal main
main_exit_spin: /* should not get here. */
	j main_exit_spin

This code needs to be placed at address 0 (aliased from 0x08000000). The linker script helps do this by associating the section name “start” with the first entry in the flash ROM.

/* linker_script.ld */
/* useful reference: www.linuxselfhelp.com/gnu/ld/html_chapter/ld_toc.html */
/* sdata and sbss : the 's' prefix indicates short addressing (32 bit rather than 64 bit) is used */
MEMORY
{
    flash : org = 0x00000000, len = 64k
    ram : org = 0x20000000, len = 20k
}
  
SECTIONS
{
        
	. = ORIGIN(flash);
        .text : {
          *(start);
		  *(.vectors); /* The interrupt vectors */
		  *(.text);
		  *(.rodata);
		  *(.comment);		  
		
		  . = ALIGN(4);
        } >flash
	. = ORIGIN(ram);
        .data : {
	  INIT_DATA_VALUES = LOADADDR(.data);
	  INIT_DATA_START = .;
	    *(.data);
	    *(.sdata);
	  INIT_DATA_END = .;
	  . = ALIGN(4);
        } >ram AT>flash
	BSS_START = .;
	.bss : {	  
	    *(.bss);
	    *(.sbss);
	    . = ALIGN(4);
	} > ram
	BSS_END = .;
}

/* main.c */
int x=0x12345678;
int y=0xabcd1234;
int z;
int main()
{
	
	y += 5;
	z = 4;
	while(1)
	{
		x+=y;
	}
}

The following command compiles the code:

riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 main.c init.s -nostdlib -T linker_script.ld -g3 -O0

The -march parameter is set to rv32imac which matches the gd32vf103. The mabi argument generates code with the following integer and pointer sizes:

long : 64 bits, int : 32 bits, short : 16 bits, pointers : 32 bits

(ref : https://www.sifive.com/blog/all-aboard-part-1-compiler-args)

There are two files in this project : main.c (a simple C program) and init.s.

The nostdlib argument really says that this is a completely bare-metal program that requires no additional components.

The linker script file name is specified with the -T argument.

The -g3 argument turns debugging information up to the maximum which helps debugging

The -O0 argument turns off all optimizations so that the code is left “as is”.

Debug session

Execute the following command to start the debug session (assuming you have started the JLinkGDBServer in another window).

gdb-multiarch a.out  

This starts the following GDB session.
GNU gdb (Ubuntu 13.1-2ubuntu2) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html&gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type “show copying” and “show warranty” for details.
This GDB was configured as “x86_64-linux-gnu”.
Type “show configuration” for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/&gt;.
Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/&gt;.

For help, type “help”.
Type “apropos word” to search for commands related to “word”…
Reading symbols from a.out…
(gdb) target ext :2331
Remote debugging using :2331
main () at main.c:12
12 x+=y;
(gdb) monitor reset
Resetting target
(gdb) load
Loading section .text, size 0x9c lma 0x0
Loading section .data, size 0x8 lma 0x9c
Start address 0x00000000, load size 164
Transfer rate: 160 KB/sec, 82 bytes/write.
(gdb) stepi
Reset_Handler () at init.s:17
17 la t0,INIT_DATA_VALUES
(gdb) i r
ra 0x0 0x0 <Reset_Handler>
sp 0x20005000 0x20005000
gp 0x0 0x0 <Reset_Handler>
tp 0x0 0x0 <Reset_Handler>
t0 0x0 0
t1 0x0 0
t2 0x0 0
fp 0x0 0x0 <Reset_Handler>
s1 0x0 0
a0 0x0 0
a1 0x0 0
a2 0x0 0
a3 0x0 0
a4 0x0 0
a5 0x0 0
a6 0x0 0
a7 0x0 0
s2 0x0 0
s3 0x0 0
s4 0x0 0
s5 0x0 0
s6 0x0 0
s7 0x0 0
s8 0x0 0
s9 0x0 0
s10 0x0 0
s11 0x0 0
t3 0x0 0
t4 0x0 0
t5 0x0 0
t6 0x0 0
pc 0x4 0x4 <Reset_Handler+4>

Commands that are entered are shown in bold in the above listing. The first of these is

target ext :2331

This connects to the JLinkGDBServer over TCP port 2331 on the local machine

monitor reset

This resets (and halts in this case) the GD32VF103

load

Loads the program specified in the command line (a.out) into flash memory

stepi

Execute a single assembler instuction pointed to by the the program counter (pc)

i r

Shorthand for info registers. This displays the contents of the CPU registers.

Now that all of this seems to be working further adventures in RISC-V assembler will follow.

Leave a comment