Close

Setting up for debugging

A project log for Sparkfun RED-V bare metal

I wanted to learn RISC-V, not on an emulator, but on a real bare metal RISC-V processor. Even though Sparkfun RED-V is no longer produced.

olaf-baeyensOlaf Baeyens 10/23/2024 at 18:490 Comments

The 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 ;-)

Discussions