Close
0%
0%

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.

Similar projects worth following
Bare metal RISC-V assembler on a Spartkfun RISC-V board.
We use a Raspberry PI to program it..

People have asked me why waste time learning a bare metal pure RISC-V assembler when you could just use a C compiler or a RISC-V emulator.

Answer: Because I can, and because I want to do it the hard way. My brain needs big challenges every now and then to push me out of my comfort zone, knowing that I might fail.

And I am curious what this RISC-V assembler looks like.

Also, let's explore new creative ideas on how to design firmware that has no basis in existing technologies.

There is a git repository but I on purpose will not share it. I want other people get inspired in RISC-V and create their own code from scratch independently not as a team effort.


  • bricked my Red-V board

    Olaf Baeyens3 days ago 0 comments

    I appear to have bricked my Red-V board.
    I can upload the firmware but normally I should see a blue LED flash and this is not happening anymore.

    I was trying to fix an issue that appears that data was not copied correctly from FLASH MEMORY  to .data in RAM. I modified the linker file and I somehow think that overwritten some flash copy function that was originally in the board.

    Unless I wrote too many times to flash.

  • Introducing sleep timer

    Olaf Baeyens11/06/2024 at 20:28 0 comments

    We use MTIME that is a 64 bit counter that runs at a frequency of  32768 Khz

    MTIME in intended to count machine time, unlike RTC time which also exists in on the board but is more intended for measuring seconds and used as OS time.

    # CLINT memory map ====================================================

    .equ MTIME_FREQUENCY, 32768 .equ MTIME_LOW_ADDR, 0x0200bff8 # MTIME LOW address .equ MTIME_HIGH_ADDR, 0x0200bffc # MTIME HIGH address 

    To measure in, milliseconds we split the high and low count

    .equ ms_to_cycles_int, 32       # Integer part of 32.768 cycles per millisecond
    .equ ms_to_cycles_rem, 768      # Fractional remainder as parts per 1000
    

    We can now create a function  where a0 is the time to sleep in milliseconds

    .globl _sleep_time
    _sleep_time:
        # Save a0 on the stack (assuming it is the delay in milliseconds)
        PUSH        a0
    
        # Convert milliseconds to RTC cycles
        li      t0, 32              # Load the integer part of cycles per millisecond (32)
        mul     t0, t0, a0          # Multiply by the number of milliseconds (a0)
        
        li      t1, 768             # Load the remainder part (768)
        mul     t1, t1, a0          # Multiply remainder by the number of milliseconds
        li      t2, 1000            # Load the scaling factor (1000)
        div     t1, t1, t2          # Divide to get the fractional part in cycles (as an integer)
        add     t0, t0, t1          # Add both the integer and fractional parts
    
        # Load mtime base addresses for the Red-V board
        li      t1, MTIME_LOW_ADDR  # Load address of mtime low into t1
        li      t2, MTIME_HIGH_ADDR # Load address of mtime high into t2
    
        # Get the current mtime (with consistency check)
    1:  lw      t3, 0(t1)           # Load mtime low
        lw      t4, 0(t2)           # Load mtime high
        lw      t5, 0(t1)           # Re-read mtime low for consistency
        bne     t3, t5, 1b          # Retry if inconsistent
    
        # Set target time
        add     t6, t3, t0          # Set target time (low part)
    
        # Poll until mtime reaches the target value
    2:  lw      t3, 0(t1)           # Reload current mtime low
        lw      t4, 0(t2)           # Reload current mtime high
        bge     t3, t6, 3f          # Exit loop if current time >= target (low part)
        j       2b                  # Keep looping
    
    3:  
        POP        a0
        ret

     We now create a macro

    .macro SLEEP_MS ms
        PUSH                a0
        # Load the precomputed cycle delay based on the input milliseconds
        li                  a0, \ms
        jal                 _sleep_time
        POP                 a0
        .endm

     And can be used like this

        LOG_INFO            STRING_TIMING_START_MSG
        SLEEP_MS            3000
        LOG_INFO            STRING_TIMING_END_MSG

  • Dealing with macro's duplicates

    Olaf Baeyens11/02/2024 at 20:44 0 comments

    The compiled expects this syntax to prevent duplicate definitions of macro's

    .ifndef TRACE_MACROS_S_INCLUDED
    TRACE_MACROS_S_INCLUDED = 1
    ...
    .endif

    Example

    .ifndef TRACE_MACROS_S_INCLUDED
    TRACE_MACROS_S_INCLUDED = 1
    
    
    .include "./src/include/global_constants.s"
    
    .align 2
    .section .rodata
    
    STRINGS_TRACE_START_MSG:        .asciz "[TRACE] "
    
    .section .text
    .align 2
    # ===============================================================
    # TRACE message_ptr 
    # ===============================================================
    .if TRACE_LEVEL > 0
        .macro TRACE message_ptr
            la      a0, \message_ptr
            jal     puts
            la      a0, CRLF
            jal     puts
        .endm
    .else
        .macro TRACE message_ptr
            # No operation when TRACE is disabled
        .endm
    .endif
    
    .endif  
    

  • Compiled date-time into the binary code

    Olaf Baeyens11/02/2024 at 19:32 5 comments

    A simple idea of to include the build date and time as information turned out to be a a struggle to get the toolchain and project files correct.

    We needed to change the extension from ".s" to capital ".S" which goes through the C++ preprocess. Small s is pure assembler.

    We now take the date-time of the build.

    # -----------------------------------------------------------------------
    # Build Date and Time
    # -----------------------------------------------------------------------
    # Get the current build date and time
    BUILD_DATE := $(shell TZ=$(shell date +"%Z") date +"%Y-%m-%d_%H:%M:%S")

     We add these to the flags.

    # -----------------------------------------------------------------------
    # Compiler and Linker Flags
    # -----------------------------------------------------------------------
    ASFLAGS = -march=rv32g -mabi=ilp32 -static -mcmodel=medany \
              -fvisibility=hidden -nostdlib -nostartfiles -I$(INCLUDE_DIR)
    ASFLAGS += -DDEBUG_LEVEL=$(DEBUG_LEVEL) -DTRACE_LEVEL=$(TRACE_LEVEL) -DLOG_LEVEL=$(LOG_LEVEL)
    ASFLAGS += -DBUILD_DATE=\"$(BUILD_DATE)\"
    ASFLAGS += -I$(INCLUDE_DIR)

    I have an rodata.S file that will be C++ preprocessed from src to build, and later we link that generated build file into the compiled end result and ignore the one found in the src

    # -----------------------------------------------------------------------
    # Rule to Generate Build-Specific rodata.S
    # -----------------------------------------------------------------------
    $(BUILD_DIR)/data/rodata.S: $(DATA_DIR)/rodata.S
        @mkdir -p $(dir $@)          # Ensure the output directory exists
        @echo "Generating rodata.S with build date: $(BUILD_DATE)"
        sed 's/BUILD_DATE_PLACEHOLDER/$(BUILD_DATE)/' $< > $@ 

     Compilation should now take ".s" and ".S" files

    # -----------------------------------------------------------------------
    # Source Files
    # -----------------------------------------------------------------------
    GLOBAL_CONSTANTS_FILE = $(INCLUDE_DIR)/global_constants.S
    
    SRC = $(GLOBAL_CONSTANTS_FILE) \
          $(INCLUDE_DIR)/global_constants.S \
          $(DATA_DIR)/data.S \
          $(BUILD_DIR)/data/rodata.S \
          $(DATA_DIR)/bss.S \
          $(wildcard $(UNIT_TESTS_DIR)/strings-module/*.[sS]) \
          $(wildcard $(MODULES_DIR)/debug-module/*.[sS]) \
          $(wildcard $(MODULES_DIR)/timing-module/*.[sS]) \
          $(wildcard $(MODULES_DIR)/strings-module/*.[sS]) \
          $(wildcard $(MODULES_DIR)/info-module/*.[sS]) \
          $(BOOT_DIR)/boot.S \
          $(SRC_DIR)/main.S

    Output now send to the UART

    ATE0--> Send Flag error: #255 #255 #255 #255 AT+BLEINIT=0--> Send Flag error: #255 #255 #255 #255 AT+CWMODE=0--> Send Flag error: #255 #255 #255 #255 
    [LOG] SparkFun Red-V bare metal v1.0.
    [LOG] Build Date: 2024-11-01_16:49:21
    [LOG] 

    The rodata.S
    Note the BUILD_DATE_PLACEHOLDER  inside the string as defined in the make file

    # rodata.S
    .align 2
    .section .rodata
    
    .globl VERSION_INFO
    VERSION_INFO:
        .asciz "SparkFun Red-V bare metal v1.0." 
    
    .globl BUILD_DATE_STR
    BUILD_DATE_STR:
        .asciz "Build Date: BUILD_DATE_PLACEHOLDER"

     Complete update make file

    # Makefile for building assembly code for the SparkFun Red-V development board
    
    # -----------------------------------------------------------------------
    # Toolchain Configuration
    # -----------------------------------------------------------------------
    TOOLCHAIN_PREFIX = riscv64-unknown-elf-
    CC       = $(TOOLCHAIN_PREFIX)gcc
    OBJCOPY  = $(TOOLCHAIN_PREFIX)objcopy
    
    # -----------------------------------------------------------------------
    # Default Flag Values (can be overridden from the command line)
    # -----------------------------------------------------------------------
    DEBUG_LEVEL ?= 2
    TRACE_LEVEL ?= 1
    LOG_LEVEL   ?= 5
    
    # -----------------------------------------------------------------------
    # Build Date and Time
    # -----------------------------------------------------------------------
    # Get the current build date and time
    BUILD_DATE := $(shell TZ=$(shell date +"%Z") date +"%Y-%m-%d_%H:%M:%S")
    
    # -----------------------------------------------------------------------
    # Compiler...
    Read more »

  • Creating debug/trace and logging macros

    Olaf Baeyens10/27/2024 at 17:37 0 comments

    We need some kind of debugging/logging/tracing when we build our OS

    So we need to define in constants.cs we now defined logging levels

    # Debugging Levels =====================================================
    
    # Define constants for debug levels
    .set DEBUG_LEVEL_RELEASE, 0 
    .set DEBUG_LEVEL_NORMAL, 1 
    .set DEBUG_LEVEL_VERBOSE, 2 
    
    # Set default DEBUG_LEVEL if not defined
    .ifndef DEBUG_LEVEL
        .set DEBUG_LEVEL, DEBUG_LEVEL_NORMAL
    .endif
    
    # Tracing Levels =======================================================
    
    .set TRACE_LEVEL_NO_TRACING, 0
    .set TRACE_LEVEL_TRACING, 1
    
    # Set default TRACE_LEVEL if not defined
    .ifndef TRACE_LEVEL
        .set TRACE_LEVEL, TRACE_LEVEL_TRACING
    .endif
    
    # Logging Levels =======================================================
    
    .set LOG_LEVEL_NONE, 0
    .set LOG_LEVEL_INFO, 1
    .set LOG_LEVEL_WARN, 2
    .set LOG_LEVEL_ERROR, 3
    .set LOG_LEVEL_DEBUG, 4
    .set LOG_LEVEL_TRACE, 5
    
    # Set default LOG_LEVEL if not defined
    .ifndef LOG_LEVEL
        .set LOG_LEVEL, LOG_LEVEL_INFO
    .endif

    Then we define the debug macro.

    (I think we need to do some push and pop of registers in the future but for now this is enough.)

    # debug-macros.s
    .include "./src/include/constants.s"
    
    # ===============================================================
    # DEBUG message_ptr 
    # ===============================================================
    .if DEBUG_LEVEL > 0
        .macro DEBUG message_ptr
            la      a0, STRINGS_DEBUG_START_MSG
            jal     puts    
            la      a0, \message_ptr
            jal     puts
            la      a0, CRLF
            jal     puts            
        .endm
    .else
        .macro DEBUG message_ptr
            # No operation when DEBUG is disabled
        .endm   
    .endif

    And we are creating logging macros:

    (I think we need to do some push and pop of registers in the future but for now this is enough.)

    # -----------------------------------------------------------------------
    # Generic LOG macro
    # -----------------------------------------------------------------------
    .macro LOG message_ptr, level
        # Only log if the current LOG_LEVEL is greater than or equal to the specified level
        .if LOG_LEVEL >= \level
            la      a0, STRINGS_LOG_START_MSG
            jal     puts    
            la      a0, \message_ptr
            jal     puts
            la      a0, CRLF
            jal     puts
        .endif
    .endm
    
    # ===============================================================
    # Specific log level macros for convenience
    # ===============================================================
    
    # LOG_INFO message_ptr
    .macro LOG_INFO message_ptr
        .if LOG_LEVEL >= LOG_LEVEL_INFO
            LOG \message_ptr, LOG_LEVEL_INFO
        .endif
    .endm
    
    # LOG_WARN message_ptr
    .macro LOG_WARN message_ptr
        .if LOG_LEVEL >= LOG_LEVEL_WARN
            LOG \message_ptr, LOG_LEVEL_WARN
        .endif
    .endm
    
    # LOG_ERROR message_ptr
    .macro LOG_ERROR message_ptr
        .if LOG_LEVEL >= LOG_LEVEL_ERROR
            LOG \message_ptr, LOG_LEVEL_ERROR
        .endif
    .endm

     Now we can use it like this:

        LOG_INFO            VERSION_INFO
    
        LOG_INFO            STRINGS_TEST_START_MSG
        LOG_INFO            SEPARATOR_DOUBLE_LINE
    
        LOG_INFO            STRINGS_TEST_STRCPY_MSG
        STRCPY              scratch1024, STRING_TEST_MSG
        LOG_INFO            scratch1024

  • Extending makefile for tracing, debug and log levels

    Olaf Baeyens10/27/2024 at 17:13 0 comments

    Improving the make files, we now want to to compile with debug, trace and logging level.

    make DEBUG_LEVEL=2 TRACE_LEVEL=1 LOG_LEVEL=5

    Complete makefile

    # Toolchain
    TOOLCHAIN_PREFIX = riscv64-unknown-elf-
    CC       = $(TOOLCHAIN_PREFIX)gcc
    OBJCOPY  = $(TOOLCHAIN_PREFIX)objcopy
    
    # Default flag values (can be overridden from the command line)
    DEBUG_LEVEL ?= 2
    TRACE_LEVEL ?= 1
    LOG_LEVEL   ?= 5
    
    # Flags
    ASFLAGS = -march=rv32g -mabi=ilp32 -static -mcmodel=medany \
              -fvisibility=hidden -nostdlib -nostartfiles -I$(INCLUDE_DIR)
    LDFLAGS = -T sparkfun-red-v.ld
    
    # Add flag definitions to ASFLAGS
    ASFLAGS += -DDEBUG_LEVEL=$(DEBUG_LEVEL) -DTRACE_LEVEL=$(TRACE_LEVEL) -DLOG_LEVEL=$(LOG_LEVEL)
    
    # Directories
    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
    MODULES_DIR      = $(SRC_DIR)/modules
    BUILD_DIR        = build
    
    # Source Files
    GLOBAL_CONSTANTS_FILE = $(INCLUDE_DIR)/global_constants.s
    
    SRC = $(GLOBAL_CONSTANTS_FILE) \
          $(INCLUDE_DIR)/constants.s \
          $(DATA_DIR)/data.s \
          $(DATA_DIR)/rodata.s \
          $(DATA_DIR)/bss.s \
          $(wildcard $(UNIT_TESTS_DIR)/strings-module/*.s) \
          $(wildcard $(MODULES_DIR)/debug-module/*.s) \
          $(wildcard $(MODULES_DIR)/strings-module/*.s) \
          $(wildcard $(MODULES_DIR)/info-module/*.s) \
          $(BOOT_DIR)/boot.s \
          $(SRC_DIR)/main.s 
    
    # Object Files
    OBJ = $(patsubst $(SRC_DIR)/%.s,$(BUILD_DIR)/%.o,$(SRC))
    
    # Targets
    ELF = $(BUILD_DIR)/main.elf
    HEX = $(BUILD_DIR)/main.hex
    
    .PHONY: all clean
    
    # Default Target
    all: $(BUILD_DIR) $(HEX)
    
    # Create build directory
    $(BUILD_DIR):
    	@mkdir -p $(BUILD_DIR)
    
    # Rule to build hex file from ELF
    $(HEX): $(ELF)
    	$(OBJCOPY) -O ihex $< $@
    
    # Rule to build ELF from object files
    $(ELF): $(OBJ)
    	$(CC) $(ASFLAGS) $(LDFLAGS) $^ -o $@
    
    # Rule to compile .s files to .o files
    $(BUILD_DIR)/%.o: $(SRC_DIR)/%.s
    	@mkdir -p $(dir $@)
    	$(CC) $(ASFLAGS) -c $< -o $@
    
    # Clean up build artifacts
    clean:
    	rm -rf $(BUILD_DIR)
    

  • PUSH and POP macro's

    Olaf Baeyens10/24/2024 at 18:00 0 comments

    RISC-V has no push and pop instructions to my surprise, so we need a way to make it easier to push registers and pop again

    # macros's 
    
    # ----------------------------------------------------------------------- 
    # ***PUSH reg
    # *** example PUSH t0
    # -----------------------------------------------------------------------
    .macro PUSH reg
        addi sp, sp, -4
        sw \reg, 0(sp)
    .endm
    
    # ----------------------------------------------------------------------- 
    # *** POP reg
    # *** example POP t0
    # -----------------------------------------------------------------------
    .macro POP reg
        lw \reg, 0(sp)
        addi sp, sp, 4
    .endm
    
    # ----------------------------------------------------------------------- 
    # *** PUSH2 reg1, reg2
    # *** example PUSH2 t0 t1
    # -----------------------------------------------------------------------
    .macro PUSH2 reg1, reg2
        addi sp, sp, -8
        sw \reg1, 0(sp)
        sw \reg2, 4(sp)
    .endm
    
    # ----------------------------------------------------------------------- 
    # *** POP2 reg1, reg2
    # *** example POP2 t0 t1
    # -----------------------------------------------------------------------
    .macro POP2 reg1, reg2
        lw \reg2, 4(sp)
        lw \reg1, 0(sp)
        addi sp, sp, 8
    .endm
    
    ...

    I am still figuring pout what would be what would be most intuitive to use.

    ...
    PUSH2 t0 t1
    # do something
    POP2 t0 t1   # We write the registers in the same order as the push
    ...
    
    # OR
    
    ...
    PUSH2 t0 t1
    # do something
    POP2 t1 t0   # We write the registers in the reverse order as the stack
    ...
    
    

    Pop same order as the push For me this is the most intuitive way and you can immediately spot if you made a error when you choose the registers to restore.

    ...
    PUSH2 t0 t1
    # do something
    POP2 t0 t1   # We write the registers in the same order as the push
    ...

    We extend  the macros so we can for example push and pop all the arguments

    # ----------------------------------------------------------------------- 
    # *** PUSH all arguments
    # -----------------------------------------------------------------------
    .macro PUSH_ARG_ALL 
        PUSH8 a0, a1, a2, a3, a4, a5, a6, a7
    .endm
    
    # ----------------------------------------------------------------------- 
    # *** POP all arguments
    # -----------------------------------------------------------------------
    .macro POP_ARG_ALL
        POP8 a0, a1, a2, a3, a4, a5, a6, a7
    .endm

    Example usage.to save all argument registers a0..a7, but we can also do that for temp registers because it has its own macro.

    PUSH_ARG_ALL 
    # Your code here
    POP_ARG_ALL 

  • Unit testing the strings macro's

    Olaf Baeyens10/24/2024 at 17:45 0 comments

    Time to define some test data to test if our string macro's actually work as expected.

    Unit tests will in the end not be part of the the release code, just for now until we know that the code actually does what we think it should do.

    We define some Read-Only data constants.

    # rodata.s
    
    .align 2
    
    # Read only data
    .section .rodata
    VERSION_INFO:                   .string "SparkFun Red-V bare metal v26b.\n\r"
    CRLF:                           .string "\n\r"
    SEPARATOR_SINGLE_LINE:          .string "------------------------------------------------------------\n\r" 
    SEPARATOR_DOUBLE_LINE:          .string "============================================================\n\r" 
    
    .section .rodata.tests
    # String testing code
    STRINGS_TEST_START_MSG:         .string "Starting strings test.\n\r"
    STRINGS_TEST_END_MSG:           .string "Ended strings test.\n\r"
    STRINGS_TEST_STRCPY_MSG:        .string "STRCPY test = "
    STRINGS_TEST_STRNCPY_MSG:       .string "STRNCPY test = "
    STRINGS_TEST_FILL_BYTES_MSG:    .string "FILL_BYTES test = "
    
    STRING_TEST_MSG:                .string "rodata line string test data message.\n\r" 
    
    .byte   0 
    

    In the strings-macros.s I also defined a PRINT macro statement that directly sends to the UART

    The idea is to have macro's that makes the assembler code more human friendly, we are basically starting to create some kind of language from scratch.

    # ----------------------------------------------------------------------- 
    # print message 
    # -----------------------------------------------------------------------
    .macro PRINT message_ptr
        la    a0, \message_ptr
        jal   puts
    .endm
    

    We need a bss.s  that defines a memory block  (1024 bytes) that we can use as scratch buffer (We do not yet have memory management)

    # bss.s
    
    .align 2
    
    # Uninitialized data
    .section .bss
    scratch1024:
        .skip 1024,0
    
    

    We have now a system that can test the string macros from the previous post.

    This becomes part of our logging in the future.

     # strings-tests.s
    
    .align 2
    
    .include "./src/data/data.s"
    .include "./src/data/rodata.s"
    .include "./src/data/bss.s"  
    
    .include "./src/include/string-macros.s" 
    
    .section .text
    .globl _strings_tests
    
    _strings_tests:
        PRINT               STRINGS_TEST_START_MSG
        PRINT               SEPARATOR_DOUBLE_LINE
    
        PRINT               STRINGS_TEST_STRCPY_MSG
        STRCPY              scratch1024, STRING_TEST_MSG
        PRINT               scratch1024
    
        PRINT               STRINGS_TEST_STRNCPY_MSG
        STRNCPY             scratch1024, STRING_TEST_MSG, 5
        PRINT               scratch1024
        PRINT               CRLF
    
        PRINT               STRINGS_TEST_FILL_BYTES_MSG
        FILL_BYTES          scratch1024, '*', 5
        PRINT               scratch1024
        PRINT               CRLF
       
        STRNCPY             scratch1024, destmsg2, 10
        PRINT               scratch1024
        PRINT               CRLF
    
        FILL_BYTES          scratch1024, '-', 2
        PRINT               scratch1024
        PRINT               CRLF
    
        STRNCPY             scratch1024, destmsg2, 10
        PRINT               scratch1024
        PRINT               CRLF    
    
        FILL_STRING         scratch1024, '=', 10
        PRINT               scratch1024    
        PRINT               CRLF
    
        PRINT               SEPARATOR_SINGLE_LINE
        PRINT               STRINGS_TEST_END_MSG
       
        ret
    
    
    

  • Creating string methods for logging

    Olaf Baeyens10/24/2024 at 17:33 0 comments

    For debugging we need a way to handle strings, so lets first build C like string functions.

    I start with macro's, I wand the ability to create then inline when needed for speed and as a function call.

    List contains a small sample of the string macro's.
    Still in development I need to think how this will be used in my code.

    I can give you a clue: In this case chatGPT can create code that helps a lot, but you need to manually check every generated code line. We can optimize later on, for now we need something working.

    # string-macros.s
    
    # ----------------------------------------------------------------------- 
    # Copy string (ASCIIZ)
    # -----------------------------------------------------------------------
    .macro STRCPY dest_string_ptr, src_string_ptr
        la a0, \dest_string_ptr         # dest_string_ptr
        la a1, \src_string_ptr          # src_string_ptr 
    
        jal _strcpy
    .endm
    
    # ----------------------------------------------------------------------- 
    #  String copy with specific length
    # -----------------------------------------------------------------------
    .macro STRNCPY dest_string_ptr, src_string_ptr, len
        la a0, \dest_string_ptr         # dest_string_ptr
        la a1, \src_string_ptr          # src_string_ptr 
        li a2, \len                     # Load the specified length into a0
    
        jal _strncpy
    .endm
    
    # ----------------------------------------------------------------------- 
    # FILL BYTES
    # -----------------------------------------------------------------------
    .macro FILL_BYTES dest_ptr, value, length
        la a0, \dest_ptr            # Load destination address into t3
        li a1, \value               # Load value into t0
        li a2, \length              # Load length into t1
    
        jal _fill_bytes    
    .endm
    
    # ----------------------------------------------------------------------- 
    # FILL BYTES STRING (ASCIIZ)
    # -----------------------------------------------------------------------
    .macro FILL_STRING dest_ptr, value, length
        li t0, \value               # Load the value to be repeated into t0
        li t1, \length              # Load the length into t1
        la t3, \dest_ptr            # Load the destination address into t3
    
    fill_string_loop:
        sb t0, 0(t3)                # Store the value into the destination address
        addi t3, t3, 1              # Increment the destination address
        addi t1, t1, -1             # Decrement the length
        bnez t1, fill_string_loop   # If length is not zero, repeat the loop
    
        sb zero, 0(t3)              # Store null character at the end
    .endm
    
    # ----------------------------------------------------------------------- 
    # Get string length (ASCIIZ)
    # -----------------------------------------------------------------------
    .macro STRLEN dest_reg, input_string_ptr
        la t0, \input_string_ptr
        li \dest_reg, 0
    
    .loop_strlen:
        lb t1, 0(t0)
        beqz t1, .end_strlen          # If t1 is zero (null-terminator), exit loop
        addi \dest_reg, \dest_reg, 1
        addi t0, t0, 1                # Move to the next character
        j .loop_strlen
    
    .end_strlen:
    .endm

     Accompanying code

    # strings.s
    
    .section .text
    
    .text
    
    .globl _fill_bytes
    .globl _strcpy
    .globl _strncpy
    
    # ----------------------------------------------------------------------- 
    # FILL BYTES
    # -----------------------------------------------------------------------
    _fill_bytes:
        addi t0, a0, 0              # Load destination address into t0
        addi t1, a1, 0              # Load value into t1
        addi t2, a2, 0              # Load length into t2
    
    fill_bytes_loop:
        sb t1, 0(t0)                # Store byte from t0 to the destination address
        addi t0, t0, 1              # Increment destination address
        addi t2, t2, -1             # Decrement length
        bnez t2, fill_bytes_loop    # If length is not zero, repeat the loop
        ret
    
    # ----------------------------------------------------------------------- 
    # Copy string (ASCIIZ)
    # -----------------------------------------------------------------------
    _strcpy:
        addi t0, a0, 0              # Load the destination string pointer to t0
        addi t1, a1, 0              # Load the source string pointer to t1
    
    .loop_strcpy:
        lbu t2, 0(t1)
        beqz t2, .end_strcpy        # If t2 is zero (null-terminator), exit loop
        sb t2, 0(t0)                # Copy the character to the destination
        addi t0, t0, 1              # Move to the next character in src
        addi t1, t1, 1              # Move to the next character in dest
        j .loop_strcpy
    
    .end_strcpy:
        sb zero, 0(t0)                  #...
    Read more »

  • UART constants

    Olaf Baeyens10/23/2024 at 19:07 0 comments

    Previous post became too big but these are the constants.s file with the RED-V constants for the UART.

    # constants.s
    
    .equ UART_REG_TXFIFO, 0
    .equ UART_BASE, 0x10013000
    
    .equ UART0_BASE, 0x10013000
    
    .equ UART_TXDATA, UART0_BASE + 0x00
    .equ UART_RXDATA, UART0_BASE + 0x04
    .equ UART_TXCTRL, UART0_BASE + 0x08
    .equ UART_RXCTRL, UART0_BASE + 0x0C
    .equ UART_IE,     UART0_BASE + 0x10
    .equ UART_IP,     UART0_BASE + 0x14
    .equ UART_DIV,    UART0_BASE + 0x18
    
    
    .equ GPIO_BASE,     0x10012000
    
    .equ GPIO_IOF_EN,   GPIO_BASE + 0x38
    .equ GPIO_IOF_SEL,  GPIO_BASE + 0x3C
    
    .equ IOF_UART0_RX,  (1 << 16)    # GPIO16
    .equ IOF_UART0_TX,  (1 << 17)    # GPIO17
    .equ IOF_UART0_MASK, IOF_UART0_RX | IOF_UART0_TX

View all 16 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates