-
Setting up for debugging
10/23/2024 at 18:49 • 0 commentsThe first challenge we need to tackle with the RED-V board is to have some kind of debugging possibility. Nothing exist so we create code that sends characters to the UART so we can grab debugging states reading the COM port in Linux.
# debug.s .align 2 .include "./src/include/constants.s" .section .text # Function to output a null-terminated string to UART .globl puts puts: li t0, UART_BASE puts_loop: lbu t1, (a0) beqz t1, puts_leave puts_waits: lw t2, UART_REG_TXFIFO(t0) bltz t2, puts_waits sw t1, UART_REG_TXFIFO(t0) addi a0, a0, 1 j puts_loop puts_leave: ret .globl uart_init uart_init: # Input: # a0 - baud rate divisor value (e.g., 139 for 115200 baud with 16 MHz clock) # Configure GPIO pins for UART0 # Enable IOF on GPIO16 (TX) and GPIO17 (RX) lui t0, %hi(IOF_UART0_MASK) # Load upper 20 bits of IOF_UART0_MASK into t0 ori t0, t0, %lo(IOF_UART0_MASK) # Or lower 12 bits into t0 (t0 = IOF_UART0_MASK) # Load address of GPIO_IOF_EN into t2 lui t2, %hi(GPIO_IOF_EN) # Load upper 20 bits of GPIO_IOF_EN into t2 addi t2, t2, %lo(GPIO_IOF_EN) # Add lower 12 bits to t2 (t2 = GPIO_IOF_EN) sw t0, 0(t2) # Store t0 to GPIO_IOF_EN # Select IOF0 (UART0) for GPIO16 and GPIO17 # Load address of GPIO_IOF_SEL into t3 lui t3, %hi(GPIO_IOF_SEL) addi t3, t3, %lo(GPIO_IOF_SEL) lw t1, 0(t3) # Load GPIO_IOF_SEL into t1 not t0, t0 # t0 = ~IOF_UART0_MASK and t1, t1, t0 # Clear bits for UART0 pins in t1 sw t1, 0(t3) # Store updated t1 back to GPIO_IOF_SEL # Set baud rate divisor lui t2, %hi(UART_DIV) addi t2, t2, %lo(UART_DIV) sw a0, 0(t2) # Store baud rate divisor to UART_DIV # Enable transmitter and receiver li t0, 0x1 # Transmit/Receive enable bit (bit 0) # Load address of UART_TXCTRL into t3 lui t3, %hi(UART_TXCTRL) addi t3, t3, %lo(UART_TXCTRL) sw t0, 0(t3) # Enable transmitter # Load address of UART_RXCTRL into t3 lui t3, %hi(UART_RXCTRL) addi t3, t3, %lo(UART_RXCTRL) sw t0, 0(t3) # Enable receiver # Disable UART interrupts (optional) lui t2, %hi(UART_IE) addi t2, t2, %lo(UART_IE) sw zero, 0(t2) # Disable UART interrupts ret .globl uart_putc uart_putc: # Input: # a0 - byte to send (lower 8 bits) # Load the address of UART_TXDATA into t2 lui t2, %hi(UART_TXDATA) # Load upper 20 bits of UART_TXDATA addi t2, t2, %lo(UART_TXDATA) # Add lower 12 bits to t2 # Load the mask into t1 (outside the loop) li t1, 0x80000000 # Load the 'full' bit mask into t1 uart_putc_wait: # Check if the TX FIFO is full lw t0, 0(t2) # Load UART_TXDATA into t0 and t3, t0, t1 # t3 = t0 & 0x80000000 bnez t3, uart_putc_wait # If 'full' bit is set, wait # Write the character to the TXDATA register andi t0, a0, 0xFF # Ensure only lower 8 bits are used sw t0, 0(t2) # Write t0 to UART_TXDATA ret .globl uart_writeln uart_writeln: # Input: # a0 - address of the null-terminated string uart_writeln_loop: lbu t0, 0(a0) # Load byte from string into t0 beqz t0, uart_writeln_newline # If null terminator, proceed to newline addi a0, a0, 1 # Increment string pointer mv a1, t0 # Move character to a1 for uart_putc jal uart_putc # Call uart_putc to send character j uart_writeln_loop # Repeat for next character uart_writeln_newline: # Send newline character li a0, '\n' # Load newline character jal uart_putc # Send newline ret
In linux we now catch the debug lines through this command:
sudo screen /dev/ttyACM0 or sudo screen /dev/ttyACM0 115200
Kill the monitoring with command Ctrl-a k
Main.s initialization becomes like this.
# main.s .align 2 .include "./src/include/constants.s" .include "./src/include/macros.s" .include "./src/include/string-macros.s" .include "./src/data/data.s" .include "./src/data/rodata.s" .include "./src/data/bss.s" .section .text .globl _start _start: jal _boot_init # Initialize boot sequence for Sparkfun Red-V board # Initialize UART li a0, 139 # Baud rate divisor for 115200 baud jal uart_init # Use uart_writeln to send the message la a0, STRINGS_TEST_START_MSG # Load address of the message into a0 jal uart_writeln # Call uart_writeln to send the string halt: j halt
You must press the reset button to see if it captures data..
If nothing shows up then you have some kind of bug ;-) -
Boot sequence
10/21/2024 at 17:03 • 0 commentsI created a separate boot sequence file called boot.cs
# boot.s .align 2 # Stack section (adjust the size as per your requirements) .section .bss _stack_start: .space 4096 # 4KB stack size (adjust as needed) .section .text # ----------------------------------------------------------------------- # Initialization boot sequence for Sparkfun Red-V board # ----------------------------------------------------------------------- .globl _boot_init _boot_init: csrr t0, mhartid # Get machine hardware thread 0 (multi threads will run in parallel the moment the CPU gets powered on) bnez t0, halt # If no machine hardware thread 0 0 then halt # Set stack pointer la sp, _estack bss_clear_init: # Clear .bss la a0, _sbss la a1, _ebss bgeu a0, a1, skip_bss_clear bss_clear_loop: sw zero, 0(a0) addi a0, a0, 4 bltu a0, a1, bss_clear_loop skip_bss_clear: data_init: # Initialize .data la a0, _sdata la a1, _edata la a2, _data_flash bgeu a0, a1, skip_data_init data_init_loop: lw t0, 0(a2) sw t0, 0(a0) addi a0, a0, 4 addi a2, a2, 4 bltu a0, a1, data_init_loop skip_data_init: ret # Infinite loop after main finishes halt: j halt
It basically does this:
- Get thread 0
- Set up stack pointer.
- Set memory of the .bss section to zero. because at startup it probably has some random bits.
- .bss section is the uninitialized data
- Copy from flash memory the content of the .data section
- .data section contains data that has has initialization.
This is called from main.cs
# main.s .align 2 .include "./src/data/data.s" .include "./src/data/rodata.s" .include "./src/data/bss.s" .section .text .globl _start _start: jal _boot_init # Initialize boot sequence for Sparkfun Red-V board # Your code goes here halt: j halt
-
The make file for the RED-V compilation
10/21/2024 at 16:48 • 0 commentsThe correct compiler and the linker options is now in the make file.
The make file is still in its basic trial and error form.
# Compiler and Linker options CC = riscv64-unknown-elf-gcc OBJCOPY = riscv64-unknown-elf-objcopy ASFLAGS = -march=rv32g -mabi=ilp32 -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles LDFLAGS = -T sparkfun-red-v.ld #LDFLAGS = -T red-v.ld # Source files SRC_DIR = src BOOT_DIR = $(SRC_DIR)/boot INCLUDE_DIR = $(SRC_DIR)/include DATA_DIR = $(SRC_DIR)/data UNIT_TESTS_DIR = $(SRC_DIR)/unit-tests STRINGS_TEST_UNIT_TESTS_DIR = $(UNIT_TESTS_DIR)/strings-module MODULES_DIR= $(SRC_DIR)/modules DEBUG_MODULE_DIR = $(MODULES_DIR)/debug-module STRINGS_MODULE_DIR= $(MODULES_DIR)/strings-module SRC = $(SRC_DIR)/main.s \ $(STRINGS_TEST_UNIT_TESTS_DIR)/strings-tests.s \ $(DEBUG_MODULE_DIR)/debug.s \ $(STRINGS_MODULE_DIR)/strings.s \ $(BOOT_DIR)/boot.s INCLUDES = $(INCLUDE_DIR)/constants.s \ $(INCLUDE_DIR)/macros.s \ $(INCLUDE_DIR)/string-macros.s DATA = $(DATA_DIR)/data.s \ $(DATA_DIR)/rodata.s \ $(DATA_DIR)/bss.s # Output files ELF = ./build/main.elf HEX = ./build/main.hex all: $(HEX) $(HEX): $(ELF) $(OBJCOPY) -O ihex $< $@ $(ELF): $(SRC) $(INCLUDES) $(DATA) $(CC) $(ASFLAGS) $(LDFLAGS) $(SRC) -o $@ clean: rm -f $(ELF) $(HEX) .PHONY: all clean
-
Upload the compiled image to the board
10/21/2024 at 16:21 • 0 commentsUploading the compiled hex image to the RED-V board connected to the a Raspberry Pi it is basically moving to:
/media/pi/HiFive
Bash script: move-to-device.sh
#!/bin/bash cp ./build/main.hex /media/pi/HiFive/main.hex echo Copied
A blue led will flash, you almost don't see it.
-
SparkFun RED-V board linker file
10/21/2024 at 15:58 • 0 commentsNot a lot of documentation was found for the SparkFun RED-V board about a good working linker file but this one works great.
Took a lot of trial and error to get it functioning,
/* Specify the architecture */ OUTPUT_ARCH("riscv") /* Define memory regions */ MEMORY { FLASH (rx) : ORIGIN = 0x20010000, LENGTH = 16K RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 16K } /* Entry point of the program */ ENTRY(_start) /* Define sections */ SECTIONS { /* Code and read-only data goes into FLASH */ .text : { _stext = .; KEEP(*(.text)) KEEP(*(.text.*)) KEEP(*(.rodata)) KEEP(*(.rodata.*)) _etext = .; } > FLASH .gnu_build_id : { *(.note.gnu.build-id) } > FLASH /* Read-write data goes into RAM */ .data : { _sdata = .; _data_flash = LOADADDR(.data); KEEP(*(.data)) KEEP(*(.data.*)) _edata = .; } > RAM AT> FLASH /* Uninitialized data goes into RAM */ .bss : { _sbss = .; KEEP(*(.bss)) KEEP(*(.bss.*)) KEEP(*(.noinit)) _ebss = .; } > RAM /* The stack also goes into RAM */ .stack ORIGIN(RAM) + LENGTH(RAM) - 0x1000 : { . = . + 0x1000; _estack = .; } > RAM /* Define symbols for the memory boundaries */ _sflash = ORIGIN(FLASH); _eflash = ORIGIN(FLASH) + LENGTH(FLASH); _sram = ORIGIN(RAM); _eram = ORIGIN(RAM) + LENGTH(RAM); }
-
Setting up the toolchain
10/18/2024 at 19:03 • 0 commentsThe tool-chain is designed for Linux, so I use a Raspberry PI to program the RED-V board.
Run "sudo apt install gcc-riscv64-unknown-elf"
The tool-chain is 64 but it can generate the 32 bit RISC-V instruction-set.
The inspiration is taken from this YouTube: This is the BEST Board to Learn RISC-V Assembly.