Skip to content

harsh13713/vsdRiscvSoc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 

Repository files navigation

πŸ“˜ RISC-V Bare-Metal Programming – Weekly Progress

This repository contains all task outputs for 34 weeks of RISC-V programming practice.
πŸ‘‡ Click on a week below to view its tasks.


πŸ“… Weekly Index


Sure! Here’s the full complete markdown block including all the C and bash code parts fully wrapped with proper syntax, ready to paste directly:

🧠 Week 1: Toolchain, Hello World & Basics

πŸ“˜ RISC-V Bare-Metal Programming – Week 1 Tasks

πŸ”§ Task 1: Toolchain Setup & Sanity Check

Steps Performed:

  • Extracted the toolchain using:
    tar -xzf riscv-toolchain-rv32imac-x86_64-ubuntu.tar.gz
    
      Added following to ~/.bashrc:
    

export PATH=$HOME/riscv/bin:$PATH

Verified installation:

riscv32-unknown-elf-gcc --version
riscv32-unknown-elf-gdb --version
riscv32-unknown-elf-objdump --version

Screenshot from 2025-06-08 12-09-36

πŸ‘‹ Task 2: Compile β€œHello, RISC-V”

βœ… Simple C code:

#include <stdio.h>

int main() {
    printf("Hello, RISC-V!\n");
    return 0;
}

βœ… Compiled with:

riscv32-unknown-elf-gcc -march=rv32imc -mabi=ilp32 -o hello.elf hello.c

βœ… Confirmed with:

file hello.elf

hello c elf chk

πŸ“œ Task 3: Generate .s File & Analyze Prologue

Command Used:

riscv32-unknown-elf-gcc -S -O0 hello.c -o hello.s

cat hello s Understanding Prologue & Epilogue πŸ”Έ Prologue (function entry):

addi    sp,sp,-16        # Reserve 16 bytes on the stack
sw      ra,12(sp)        # Save return address (ra) at offset 12

➑️ This sets up the stack frame and saves the return address so the function can safely return later. πŸ”Έ Epilogue (function exit):

lw      ra,12(sp)        # Restore return address
addi    sp,sp,16         # Restore the stack pointer
ret                      # Return to the caller

➑️ This restores the state before the function was called and jumps back using the saved return address.

πŸ” Task 4: Hex Dump & Disassembly

Commands Used:

riscv32-unknown-elf-objdump -d hello.elf > hello.dump
riscv32-unknown-elf-objcopy -O ihex hello.elf hello.hex

You can inspect it with:

cat hello.dump

hello dump Create an Intel HEX file

riscv32-unknown-elf-objcopy -O ihex hello.elf hello.hex

You can view it with:

cat hello.hex

hello hex

🧾 Task 5: ABI & Register Cheat-Sheet
Register ABI Name Usage
x0 zero Constant zero
x1 ra Return address
x2 sp Stack pointer
x3 gp Global pointer
x4 tp Thread pointer
x5–x7 t0–t2 Temporaries
x8–x9 s0–s1 Saved registers
x10–x17 a0–a7 Function args/ret
x18–x27 s2–s11 Saved registers
x28–x31 t3–t6 Temporaries

Calling convention:

  • a0–a7 β†’ Function arguments and return values
  • s0–s11 β†’ Callee-saved (preserved across function calls)
  • t0–t6 β†’ Caller-saved (can be overwritten by callees)
🐞 Task 6: GDB Debugging

πŸ”§ Tool Versions

which riscv32-unknown-elf-gdb
riscv32-unknown-elf-gdb --version
file hello.elf
riscv32-unknown-elf-objdump -h hello.elf
riscv32-unknown-elf-readelf -l hello.elf

GDB Session

riscv32-unknown-elf-gdb hello.elf
Disassemble main:

(gdb) disassemble main
(gdb) info symbol 0x10170
(gdb) x/10i 0x10162
(gdb) info symbol 0x100e2
(gdb) x/5i 0x100e2
(gdb) x/s 0x1245c
(gdb) x/1xw 0x10162
(gdb) x/1xw 0x10170

Screenshot from 2025-06-08 15-19-40 Screenshot from 2025-06-08 15-20-00 Screenshot from 2025-06-08 15-20-15 Screenshot from 2025-06-08 15-20-25

πŸ–₯️ Task 7: Emulator Run using QEMU

Step 1: Install Required Packages (if not done)

Just in case:

sudo apt update
sudo apt install build-essential device-tree-compiler libglib2.0-dev libpixman-1-dev git \
libexpat-dev libgmp-dev libmpc-dev libmpfr-dev libz-dev python3 gawk bison flex texinfo \
libtool autoconf automake

Step 2: Clone and Build OpenSBI

cd ~/riscv-projects/week1
git clone https://github.com/riscv-software-src/opensbi.git
cd opensbi

Build for 32-bit:

make PLATFORM=generic CROSS_COMPILE=riscv32-unknown-elf-

Check if hello.elf exists In your terminal:

find ~/riscv-projects/ -name hello.elf

If this shows a path like:

/home/harshini123/riscv-projects/week1/hello.elf

Run QEMU like this (replace the path with yours):

qemu-system-riscv32 -nographic \
  -machine virt \
  -bios ~/riscv-projects/week1/opensbi/build/platform/generic/firmware/fw_dynamic.elf \
  -kernel ~/riscv-projects/week1/hello.elf

Hello c program

// hello.c
volatile char *uart = (volatile char *)0x10000000;
void _start() {
    const char *str = "Hello, RISC-V!\n";
    while (*str) *uart = *str++;
    while (1);  // hang
}

Linker code

MEMORY
{
  ROM (rx) : ORIGIN = 0x80200000, LENGTH = 512K
  RAM (rw) : ORIGIN = 0x84000000, LENGTH = 128K
}

SECTIONS
{
  . = ORIGIN(ROM);

  .text : {
    *(.text*)
  } > ROM

  .rodata : {
    *(.rodata*)
  } > ROM

  .data : {
    *(.data*)
  } > RAM

  .bss : {
    *(.bss*)
    *(COMMON)
  } > RAM
}

Compile using

riscv32-unknown-elf-gcc -T linker.ld -nostartfiles -o hello.elf hello.c

uart hello riscv

πŸš€ Task 8: GCC Optimization (-O0 vs -O2)

Commands Used:

riscv32-unknown-elf-gcc -S -O0 hello.c -o hello_O0.s
riscv32-unknown-elf-gcc -S -O2 hello.c -o hello_O2.s

hello no opt hello o2

Comparison:

-O0 (no optimization): includes full function call overhead, redundant instructions.

-O2 (optimized): inlines functions, removes dead code, reuses registers efficiently.
βš™οΈ Task 9: Inline Assembly – Reading Cycle Counter

C Code with Inline Assembly:

#define UART0 0x10000000
#define uart_tx (*((volatile char *)UART0))

#include <stdint.h>

void uart_putchar(char c) {
    uart_tx = c;
}

void uart_puts(const char *s) {
    while (*s) {
        uart_putchar(*s++);
    }
}

void uart_putnum(uint32_t num) {
    char buf[10];
    int i = 0;
    if (num == 0) {
        uart_putchar('0');
        return;
    }
    while (num > 0 && i < 10) {
        buf[i++] = '0' + (num % 10);
        num /= 10;
    }
    while (i--) {
        uart_putchar(buf[i]);
    }
}

static inline uint32_t add_inline(uint32_t a, uint32_t b) {
    uint32_t result;
    asm volatile ("add %0, %1, %2" : "=r"(result) : "r"(a), "r"(b));
    return result;
}

static inline uint32_t demo_volatile(uint32_t input) {
    uint32_t output;
    asm volatile ("slli %0, %1, 1" : "=r"(output) : "r"(input));
    return output;
}

void _start() {
    uart_puts("=== Inline Assembly: No CSRs ===\n");

    uart_puts("15 + 25 = ");
    uart_putnum(add_inline(15, 25));
    uart_putchar('\n');

    uart_puts("5 << 1 = ");
    uart_putnum(demo_volatile(5));
    uart_putchar('\n');

    while (1) {}
}

Compile

riscv32-unknown-elf-gcc -nostdlib -march=rv32imc -mabi=ilp32 -Wl,-e,_start -o inline_assembly_nocsr.elf inline_assembly.c

Generate assembly file

riscv32-unknown-elf-gcc -S inline_assembly.c

View inline assembly in generated code

echo "=== Generated Assembly with Inline Code ==="
grep -A 5 -B 5 -E "(add|slli|mv)" inline_assembly.s

Complete verification

echo "=== Task 9: Inline Assembly Implementation ==="

echo -e "\n1. Source code created:"
ls -la inline_assembly.c

echo -e "\n2. Compilation:"
riscv32-unknown-elf-gcc -nostdlib -nostartfiles -nodefaultlibs -march=rv32imc -mabi=ilp32 -Wl,-e,_start -o inline_assembly.elf inline_assembly.c \                     
  && echo "βœ“ Compiled!" \
  || echo "❌ Compilation failed"
                                   
echo -e "\n3. Assembly generation:"                                                                                  
riscv32-unknown-elf-gcc -S inline_assembly.c && echo "βœ“ Assembly generated!" || echo "❌ Failed to generate assembly"
                                                       
echo -e "\n4. Inline assembly found in generated code:"
grep -A 2 -B 2 -E "add|slli|mv" inline_assembly.s | head -10
file inline_assembly.elf

Screenshot from 2025-06-08 16-00-23 Screenshot from 2025-06-08 16-09-57 Screenshot from 2025-06-08 16-10-46 Screenshot from 2025-06-08 16-11-21 Screenshot from 2025-06-08 16-11-55 Screenshot from 2025-06-08 16-12-01 Screenshot from 2025-06-08 16-12-08 Screenshot from 2025-06-08 16-12-36 Screenshot from 2025-06-08 16-12-41

πŸ”Œ Task 10: Memory-Mapped I/O using Volatile vs Non-Volatile

πŸ§ͺ Objective:

Demonstrate the importance of using volatile for memory-mapped I/O in RISC-V bare-metal programming by comparing two versions:

  • βœ… gpio_vol.c – with volatile
  • ❌ gpio_novol.c – without volatile

🧠 What is volatile?

  • Tells the compiler not to optimize memory accesses.
  • Required for memory-mapped I/O since values can change outside the program's control (via hardware).
  • Prevents removal or reordering of *gpio = ... operations.

gpio_vol.c

#include <stdint.h>

#define UART0 0x10000000
#define GPIO_ADDR 0x10012000

#define uart_tx (*((volatile char *)UART0))
#define gpio_reg (*((volatile uint32_t *)GPIO_ADDR))

// Send a character to UART
void uart_putchar(char c) {
    uart_tx = c;
}

// Send a string to UART
void uart_puts(const char *s) {
    while (*s) {
        uart_putchar(*s++);
    }
}

// Convert number to decimal and print to UART
void uart_putnum(uint32_t num) {
    char buf[10];
    int i = 0;
    if (num == 0) {
        uart_putchar('0');
        return;
    }
    while (num > 0) {
        buf[i++] = '0' + (num % 10);
        num /= 10;
    }
    while (i--) {
        uart_putchar(buf[i]);
    }
}

// Perform GPIO operations
void gpio_task10_demo() {
    uart_puts("=== Task 10: GPIO Demo ===\n");

    // Write 0x1 to GPIO (set pin high)
    gpio_reg = 0x1;
    uart_puts("GPIO written: 0x1\n");

    // Read back and toggle
    uint32_t current = gpio_reg;
    gpio_reg = ~current;
    uart_puts("GPIO toggled.\n");

    // Set bit 0
    gpio_reg |= (1 << 0);
    uart_puts("Bit 0 set.\n");

    // Clear bit 1
    gpio_reg &= ~(1 << 1);
    uart_puts("Bit 1 cleared.\n");
}

// Entry point (no main)
void _start() {
    gpio_task10_demo();

    while (1) {
        // Infinite loop (bare-metal style)
    }
}

gpio_novol.c

#include <stdint.h>

#define UART0 0x10000000
#define GPIO_ADDR 0x10012000

#define uart_tx (*((volatile char *)UART0))
#define gpio_ptr ((uint32_t *)GPIO_ADDR)  // ❌ Not volatile on purpose

void uart_putchar(char c) {
    uart_tx = c;
}

void uart_puts(const char *s) {
    while (*s) {
        uart_putchar(*s++);
    }
}

// This function omits `volatile`, so the compiler may optimize away writes
void toggle_gpio_no_volatile(void) {
    uart_puts("Writing to GPIO without volatile...\n");

    *gpio_ptr = 0x1;  // Set high
    *gpio_ptr = 0x0;  // Set low
    *gpio_ptr = 0x1;  // Set high again β€” may be optimized away

    uart_puts("Done writing GPIO without volatile.\n");
}

// Bare-metal entry point
void _start() {
    toggle_gpio_no_volatile();

    while (1) {}
}

Compilation

riscv32-unknown-elf-gcc -nostdlib -nostartfiles -nodefaultlibs \
  -march=rv32imc -mabi=ilp32 -Wl,-e,_start -o gpio_vol.elf gpio_vol.c

riscv32-unknown-elf-gcc -nostdlib -nostartfiles -nodefaultlibs \
  -march=rv32imc -mabi=ilp32 -Wl,-e,_start -o gpio_novol.elf gpio_novol.c

Assembly Analysis (Optimized with -O2)

riscv32-unknown-elf-gcc -S -O2 gpio_vol.c -o gpio_vol.s
riscv32-unknown-elf-gcc -S -O2 gpio_novol.c -o gpio_novol.s

With volatile (gpio_vol.s) – memory operations preserved:

105:	sw	a3,0(a4)
115:	lw	a4,0(a3)
119:	sw	a4,0(a3)
128:	lw	a4,0(a3)
132:	sw	a4,0(a3)

Without volatile (gpio_novol.s) – some writes optimized away:

54:	sw	a3,0(a4)         # Only one memory write remains
70:	sw	ra,12(sp)        # Function prologue, not GPIO

βœ… Memory instructions (sw, lw) are optimized out in the non-volatile version. Screenshot from 2025-06-08 16-48-56 Screenshot from 2025-06-08 16-51-16 Screenshot from 2025-06-08 17-18-39 Screenshot from 2025-06-08 17-18-51

πŸ“¦ Task 11: Custom Linker Script and Bare-Metal Setup

πŸ”§ Objective:

Create and test a custom linker script for a bare-metal RISC-V RV32IMC program. Ensure correct placement of .text, .data, and .bss sections in memory.


πŸ—‚οΈ Files Used:

File Purpose
minimal.ld Custom linker script
test_linker.c C file with variables in .data and .bss
start.S Minimal _start assembly to call main()

min_link.ld Highlights:

  • Places .text at 0x00000000 (Flash/ROM)
  • Places .data and .bss at 0x10000000 (SRAM)
  • Defines _stack_top at top of SRAM
MEMORY {
    FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 256K
    SRAM  (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}

SECTIONS {
    .text : { *(.text.start) *(.text*) *(.rodata*) } > FLASH
    .data : { _data_start = .; *(.data*) _data_end = .; } > SRAM
    .bss  : { _bss_start = .; *(.bss*) _bss_end = .; } > SRAM
    _stack_top = ORIGIN(SRAM) + LENGTH(SRAM);
}

test_link.c

uint32_t counter = 0x12345678; // Goes to .data
uint32_t status_flag;          // Goes to .bss

void write_pattern(void) {
    counter = 0xCAFEBABE;
    status_flag = 0x0000DEAD;
}

void main(void) {
    write_pattern();
    while (1) {}
}

start.S (Minimal Entry Point):

.section .text.start
.globl _start

_start:
    la sp, _stack_top
    call main

halt:
    j halt

Compile with Custom Linker Script

Compile assembly and C code with custom linker script

riscv32-unknown-elf-gcc -c start.S -o start.o
riscv32-unknown-elf-gcc -c test_link.c -o test_link.o

Link with custom linker script

riscv32-unknown-elf-ld -T min_link.ld start.o test_link.o -o test_link.elf

Complete working build script

#!/bin/bash

echo "=== Task 11: Linker Script Implementation ==="

# Step 1: Compile all sources
echo "1. Compiling with custom linker script..."
riscv32-unknown-elf-gcc -c start.S -o start.o
riscv32-unknown-elf-gcc -c test_link.c -o test_link.o
riscv32-unknown-elf-ld -T min_link.ld start.o test_link.o -o test_link.elf

if [ $? -ne 0 ]; then
    echo "βœ— Compilation failed!"
    exit 1
else
    echo "βœ“ Compilation successful!"
fi

# Step 2: Verify memory layout
echo -e "\n2. Verifying memory layout:"
echo "Text section should be at 0x00000000:"
riscv32-unknown-elf-objdump -h test_link.elf | grep ".text"

echo "Data section should be at 0x10000000:"
riscv32-unknown-elf-objdump -h test_link.elf | grep -E "\.data|\.sdata"

# Step 3: Display symbol table (for verification)
echo -e "\n3. Symbol addresses:"
riscv32-unknown-elf-nm test_link.elf | head -10

echo -e "\nβœ“ Linker script test completed successfully!"
chmod +x build_link_test.sh
./build_link_test.sh

Screenshot from 2025-06-08 17-36-27 Screenshot from 2025-06-08 17-39-26 Screenshot from 2025-06-08 18-18-59 Screenshot from 2025-06-08 19-09-12 Screenshot from 2025-06-08 19-29-39

🧠 Flash Memory (0x00000000 - 0x0003FFFF)

This section of memory is meant for storing the program's code permanently. It holds things like the instructions, constant values, and the entry point of the program (like _start). Flash memory is non-volatile, which means its contents stay even when the power is off. In my linker script, I’ve allocated 256KB for it. It's mostly read-only during execution. ⚑ SRAM (0x10000000 - 0x1000FFFF)

SRAM is where the program keeps temporary data while it's running. It stores global variables, the BSS segment (uninitialized data), and also supports the heap and stack. Unlike Flash, SRAM is volatile β€” meaning it loses everything when power is cut. It allows both reading and writing, and it's very fast. I’ve allocated 64KB for SRAM in the linker script. πŸ“Œ Flash vs SRAM – Why They're at Different Addresses

These memory regions are placed at different base addresses for several good reasons:

Architecture Design: Flash and SRAM often sit on separate memory buses, so their address ranges are distinct.

Performance: Flash is great for fetching instructions, while SRAM is better for handling data read/write operations quickly.

Power Saving: In low-power modes, Flash can be turned off while SRAM stays active to preserve temporary data.

Security: Flash is more secure since it can be made read-only during execution, while SRAM needs to be flexible for runtime changes.
πŸ’‘ Task 12: LED Blink Using Memory-Mapped GPIO

🎯 Objective

Create a bare-metal program that toggles an LED using memory-mapped I/O, controlled via GPIO register access. This exercise also uses a custom linker script and manual startup code (_start).


πŸ—‚οΈ Files Created

File Description
task12_led_blink.c Blinks LED using GPIO register toggling
led_start.s Assembly _start that sets up stack and calls main
led_blink.ld Custom linker script for code/data placement

🧠 Memory Mapping

  • GPIO_BASE: 0x10012000
  • Output Register: GPIO_BASE + 0x00
  • Direction Register: GPIO_BASE + 0x04
  • Only GPIO pin 0 is used (toggled repeatedly)

πŸ”§ Code Overview

task12_led_blink.c

#define GPIO_BASE 0x10012000
#define GPIO_OUT  (*(volatile uint32_t *)(GPIO_BASE + 0x00))
#define GPIO_DIR  (*(volatile uint32_t *)(GPIO_BASE + 0x04))

void delay(volatile uint32_t count) {
    while (count--) {
        __asm__ volatile ("nop");
    }
}

void main(void) {
    GPIO_DIR |= (1 << 0);       // Set GPIO pin 0 as output
    while (1) {
        GPIO_OUT ^= (1 << 0);   // Toggle pin 0
        delay(100000);
    }
}

led_start.s

.section .text.start
.globl _start

_start:
    lui  sp, %hi(_stack_top)
    addi sp, sp, %lo(_stack_top)
    call main

hang:
    j hang

led_blink.ld

ENTRY(_start)

MEMORY {
    FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 256K
    SRAM  (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}

SECTIONS {
    .text : { *(.text.start) *(.text*) *(.rodata*) } > FLASH
    .data : { _data_start = .; *(.data*) _data_end = .; } > SRAM
    .bss  : { _bss_start = .; *(.bss*) _bss_end = .; } > SRAM
    _stack_top = ORIGIN(SRAM) + LENGTH(SRAM);
}

Compile the LED blink program

riscv32-unknown-elf-gcc -c led_start.s -o led_start.o
riscv32-unknown-elf-gcc -c led_blink.c -o led_blink.o

Link with custom linker script

riscv32-unknown-elf-ld -T led_blink_link.ld led_start.o led_blink.o -o led_blink.elf

Complete build script

#!/bin/bash
echo "=== Task 12: LED Blink Implementation ==="

Compile everything
echo "1. Compiling LED blink program..."
riscv32-unknown-elf-gcc -c led_start.s -o led_start.o
riscv32-unknown-elf-gcc -c task12_led_blink.c -o task12_led_blink.o
riscv32-unknown-elf-ld -T led_blink.ld led_start.o task12_led_blink.o -o task12_led_blink.elf

echo "βœ“ Compilation successful!"

Verify results
echo -e "\n2. Verifying LED blink program:"
file task12_led_blink.elf

echo -e "\n3. Checking memory layout:"
riscv32-unknown-elf-objdump -h task12_led_blink.elf | grep -E "(text|data)"

echo -e "\n4. GPIO register usage in disassembly:"
riscv32-unknown-elf-objdump -d task12_led_blink.elf | grep -A 5 -B 5 "0x10012000"

echo -e "\nβœ“ LED blink program ready!"
chmod +x build_led_blink.sh
./build_led_blink.sh

Screenshot from 2025-06-08 19-52-04 Screenshot from 2025-06-08 19-54-31 Screenshot from 2025-06-08 19-58-45 Screenshot from 2025-06-08 20-05-53 Screenshot from 2025-06-08 20-06-18

LED Blink Algorithm:

Initialization: Set GPIO pin 0 as output using direction register
Main Loop: Infinite loop with LED toggle and delay
Toggle Operation: XOR output register bit 0 to alternate LED state
Timing Control: Delay function with configurable count for visible blinking
⏱️ Task 13: Machine Timer Interrupt Using RISC-V CSRs

πŸ“„ File Overview

File Description
timer_interrupt.c Sets up timer and defines C interrupt handler
start_inter.S Assembly _start and trap redirection
link.ld Custom linker script for .text, .data, etc.

timer_inter.c

#include <stdint.h>

#define CLINT_BASE      0x02000000
#define MTIMECMP        (*(volatile uint64_t *)(CLINT_BASE + 0x4000))
#define MTIME           (*(volatile uint64_t *)(CLINT_BASE + 0xBFF8))

#define GPIO_BASE       0x10012000
#define GPIO_OUT        (*(volatile uint32_t *)(GPIO_BASE + 0x00))
#define GPIO_DIR        (*(volatile uint32_t *)(GPIO_BASE + 0x04))

#define MIE_MTIE        (1 << 7)
#define MSTATUS_MIE     (1 << 3)

static inline void write_csr(const char *csr, uint32_t value) {
    if (csr == "mstatus") {
        __asm__ volatile("csrw mstatus, %0" :: "r"(value));
    } else if (csr == "mie") {
        __asm__ volatile("csrw mie, %0" :: "r"(value));
    }
}

void timer_init() {
    uint64_t now = MTIME;
    MTIMECMP = now + 500000;  // Schedule next timer interrupt

    write_csr("mie", MIE_MTIE);        // Enable machine timer interrupt
    write_csr("mstatus", MSTATUS_MIE); // Global interrupt enable
}

// Interrupt handler attribute
void __attribute__((interrupt)) machine_timer_handler(void) {
    // Toggle GPIO pin 0
    GPIO_OUT ^= (1 << 0);

    // Schedule next interrupt
    MTIMECMP = MTIME + 500000;
}

// Dummy main loop
void main(void) {
    GPIO_DIR |= (1 << 0); // Make GPIO pin 0 output
    timer_init();

    while (1) {
        // Wait for timer interrupt
    }
}

start_inter.s

.section .text.start
.global _start

_start:
    # Set up stack pointer
    lui sp, %hi(_stack_top)
    addi sp, sp, %lo(_stack_top)
    
    # Initialize trap vector
    la t0, trap_handler
    csrw mtvec, t0
    
    # Call main program
    call main
    
    # Infinite loop (shouldn't reach here)
1:  j 1b

# Simple trap handler (if needed)
trap_handler:
    # Save context
    addi sp, sp, -64
    sw ra, 0(sp)
    sw t0, 4(sp)
    sw t1, 8(sp)
    sw t2, 12(sp)
    sw a0, 16(sp)
    sw a1, 20(sp)
    
    # Call C interrupt handler
    call machine_timer_handler
    
    # Restore context
    lw ra, 0(sp)
    lw t0, 4(sp)
    lw t1, 8(sp)
    lw t2, 12(sp)
    lw a0, 16(sp)
    lw a1, 20(sp)
    addi sp, sp, 64
    
    # Return from interrupt
    mret

.size _start, . - _start
.size trap_handler, . - trap_handler

link_inter.ld

/*
 * Linker Script for Timer Interrupt - RV32IMC
 * Places .text at 0x00000000 (Flash/ROM)
 * Places .data at 0x10000000 (SRAM)
 */

ENTRY(_start)

MEMORY
{
    FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 256K
    SRAM  (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}

SECTIONS
{
    /* Text section in Flash at 0x00000000 */
    .text 0x00000000 : {
        *(.text.start)    /* Entry point first */
        *(.text*)         /* All other text */
        *(.rodata*)       /* Read-only data */
    } > FLASH

    /* Data section in SRAM at 0x10000000 */
    .data 0x10000000 : {
        _data_start = .;
        *(.data*)         /* Initialized data */
        _data_end = .;
    } > SRAM

    /* BSS section in SRAM */
    .bss : {
        _bss_start = .;
        *(.bss*)          /* Uninitialized data */
        _bss_end = .;
    } > SRAM

    /* Stack at end of SRAM */
    _stack_top = ORIGIN(SRAM) + LENGTH(SRAM);
}

Compile Timer Interrupt Program Compile the timer interrupt program with CSR support

riscv32-unknown-elf-gcc -march=rv32imac_zicsr -c start_inter.s -o start_inter.o
riscv32-unknown-elf-gcc -march=rv32imac_zicsr -c timer_inter.c -o timer_inter.o
riscv32-unknown-elf-ld -T link_inter.ld start_inter.o timer_inter.o -o timer_inter.elf

complete working build script build_timer_inter.sh

#!/bin/bash
echo "=== Task 13: Timer Interrupt Implementation ==="

# Compile everything with zicsr extension
echo "1. Compiling timer interrupt program..."
riscv32-unknown-elf-gcc -march=rv32imac_zicsr -c start_inter.s -o start_inter.o
riscv32-unknown-elf-gcc -march=rv32imac_zicsr -c timer_inter.c -o timer_inter.o
riscv32-unknown-elf-ld -T link_inter.ld start_inter.o timer_inter.o -o timer_inter.elf

echo "βœ“ Compilation successful!"

# Verify results
echo -e "\n2. Verifying timer interrupt program:"
file timer_inter.elf

echo -e "\n3. Checking interrupt-related symbols:"
riscv32-unknown-elf-nm timer_inter.elf | grep -E "(interrupt|timer|handler)"

echo -e "\n4. CSR operations in disassembly:"
riscv32-unknown-elf-objdump -d timer_inter.elf | grep -A 3 -B 1 "csr"

echo -e "\nβœ“ Timer interrupt program ready!"
chmod +x build_timer_inter.sh
./build_timer_inter.sh

Screenshot from 2025-06-08 20-22-24 Screenshot from 2025-06-08 20-24-04 Screenshot from 2025-06-08 20-27-28 Screenshot from 2025-06-08 20-44-13 Screenshot from 2025-06-08 20-49-55 Screenshot from 2025-06-08 20-50-27 Screenshot from 2025-06-08 20-50-45

πŸ” Task 14: Circular Queue - With & Without Atomic Operations

🎯 Objective

Implement and compare two circular queue designs:

  1. βœ… With atomic operations (__atomic_exchange_n)
  2. ❌ Without atomic operations (single-core safe only)

πŸ“‚ Files

File Description
task14_queue_atomic.c Thread-safe queue using spinlocks
task14_queue_non_atomic.c Basic queue for single-core use
start.s Minimal startup assembly
linker.ld Bare-metal linker script

atomic_queue.c

#include <stdint.h>
#include <stdbool.h>

#define QUEUE_SIZE 8

volatile uint32_t queue[QUEUE_SIZE];
volatile uint32_t head = 0;
volatile uint32_t tail = 0;
volatile uint32_t lock = 0;

static inline void lock_acquire(volatile uint32_t *lock) {
    while (__atomic_exchange_n(lock, 1, __ATOMIC_ACQUIRE) != 0) {}
}
static inline void lock_release(volatile uint32_t *lock) {
    __atomic_store_n(lock, 0, __ATOMIC_RELEASE);
}

bool enqueue(uint32_t value) {
    lock_acquire(&lock);
    uint32_t next_tail = (tail + 1) % QUEUE_SIZE;
    if (next_tail == head) {
        lock_release(&lock);
        return false;
    }
    queue[tail] = value;
    tail = next_tail;
    lock_release(&lock);
    return true;
}

bool dequeue(uint32_t *value) {
    lock_acquire(&lock);
    if (head == tail) {
        lock_release(&lock);
        return false;
    }
    *value = queue[head];
    head = (head + 1) % QUEUE_SIZE;
    lock_release(&lock);
    return true;
}

void test_queue(void) {
    uint32_t val;
    enqueue(10); enqueue(20); enqueue(30);
    dequeue(&val); dequeue(&val);
    enqueue(40); enqueue(50);
    while (dequeue(&val)) { (void)val; }
}

void main(void) {
    test_queue();
    while (1);
}

no_atomic_queue.c

#include <stdint.h>
#include <stdbool.h>

#define QUEUE_SIZE 8

volatile uint32_t queue[QUEUE_SIZE];
volatile uint32_t head = 0;
volatile uint32_t tail = 0;

bool enqueue(uint32_t value) {
    uint32_t next_tail = (tail + 1) % QUEUE_SIZE;
    if (next_tail == head) return false;
    queue[tail] = value;
    tail = next_tail;
    return true;
}

bool dequeue(uint32_t *value) {
    if (head == tail) return false;
    *value = queue[head];
    head = (head + 1) % QUEUE_SIZE;
    return true;
}

void test_queue(void) {
    uint32_t val;
    enqueue(10); enqueue(20); enqueue(30);
    dequeue(&val); dequeue(&val);
    enqueue(40); enqueue(50);
    while (dequeue(&val)) { (void)val; }
}

void main(void) {
    test_queue();
    while (1);
}

atomic_start.s

.section .text.start
.globl _start

_start:
    lui sp, %hi(_stack_top)
    addi sp, sp, %lo(_stack_top)
    call main

hang:
    j hang
.size _start, . - _start

atomic_link.ld

ENTRY(_start)

MEMORY {
    FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 256K
    SRAM  (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}

SECTIONS {
    .text : { *(.text.start) *(.text*) *(.rodata*) } > FLASH
    .data : { _data_start = .; *(.data*) _data_end = .; } > SRAM
    .bss  : { _bss_start = .; *(.bss*) _bss_end = .; } > SRAM
    _stack_top = ORIGIN(SRAM) + LENGTH(SRAM);
}

Compile the atomic operations program with RV32IMAC

riscv32-unknown-elf-gcc -march=rv32imac -c atomic_start.s -o atomic_start.o
riscv32-unknown-elf-gcc -march=rv32imac -c atomic_queue.c -o atomic_queue.o

Link with custom linker script

riscv32-unknown-elf-ld -T atomic_link.ld atomic_start.o atomic_queue.o -o atomic_queue.elf

Also compile non-atomic version for comparison

riscv32-unknown-elf-gcc -march=rv32imc -c no_atomic_queue.c -o no_atomic_queue.o
riscv32-unknown-elf-ld -T atomic_link.ld atomic_start.o no_atomic_queue.o -o no_atomic_queue.elf

Complete working build script

#!/bin/bash
echo "=== Task 14: Atomic Extension Demonstration ==="

# Compile with RV32IMAC (includes atomic extension)
echo "1. Compiling with atomic extension (RV32IMAC)..."
riscv32-unknown-elf-gcc -march=rv32imac -c atomic_start.s -o atomic_start.o
riscv32-unknown-elf-gcc -march=rv32imac -c atomic_queue.c -o atomic_queue.o
riscv32-unknown-elf-ld -T atomic.ld atomic_start.o atomic_queue.o -o atomic_queue.elf

echo "2. Compiling without atomic extension (RV32IMC)..."
riscv32-unknown-elf-gcc -march=rv32imc -c no_atomic_queue.c -o no_atomic_queue.o
riscv32-unknown-elf-ld -T atomic_link.ld atomic_start.o no_atomic_queue.o -o no_atomic_queue.elf

echo "βœ“ Compilation successful!"

# Verify results
echo -e "\n3. Verifying atomic operations program:"
file atomic_queue.elf

echo -e "\n4. Checking for atomic instructions:"
riscv32-unknown-elf-gcc -march=rv32imac -S atomic_queue.c
grep -E "(lr\.w|sc\.w|amoadd|amoswap|amoand|amoor)" atomic_queue.s

echo -e "\n5. Disassembly showing atomic instructions:"
riscv32-unknown-elf-objdump -d atomic_queue.elf | grep -A 2 -B 2 "lr\.w\|sc\.w\|amo"

echo -e "\nβœ“ Atomic extension demonstration ready!"
chmod +x build_atomic.sh
./build_atomic.sh

Screenshot from 2025-06-08 21-28-41 Screenshot from 2025-06-08 21-35-40 Screenshot from 2025-06-08 21-36-41 Screenshot from 2025-06-08 22-06-47 Screenshot from 2025-06-08 22-18-34

πŸ”’ Task 15: Mutex Using Spinlock with LR/SC

🎯 Objective

Demonstrate how to implement a mutex (mutual exclusion) in bare-metal RISC-V using a software spinlock based on load-reserved / store-conditional (lr.w / sc.w) instructions.

The goal is to protect a shared counter from concurrent access by two simulated "threads".


🧠 Core Concepts

Component Purpose
lr.w Load-reserved word β€” marks address for atomic use
sc.w Store-conditional β€” only stores if reservation is valid
sw zero Releases the lock (writes 0 to lock variable)
Spinlock Loops until lock is acquired (no preemption)

πŸ“ Files Used

File Description
task15_complete_mutex.c C code simulating two threads using mutex
start.s Assembly _start to call main
mutex.ld Custom linker script

full_mutex.c

#include <stdint.h>

/* =======================================
   Shared Resources
   ======================================= */
volatile int mutex_lock = 0;
volatile int shared_counter = 0;
volatile int thread1_count = 0;
volatile int thread2_count = 0;

/* =======================================
   Spinlock Acquire using LR/SC
   ======================================= */
void lock_acquire(volatile int *lock) {
    int temp;

    __asm__ volatile (
        "1:\n"
        "   lr.w    %0, (%1)\n"      // Load-reserved
        "   bnez    %0, 1b\n"        // Retry if already locked
        "   li      %0, 1\n"         // Prepare value to store
        "   sc.w    %0, %0, (%1)\n"  // Attempt store-conditional
        "   bnez    %0, 1b\n"        // Retry if SC failed
        : "=&r"(temp)
        : "r"(lock)
        : "memory"
    );
}

/* =======================================
   Spinlock Release
   ======================================= */
void lock_release(volatile int *lock) {
    __asm__ volatile (
        "sw zero, 0(%0)\n"   // Store 0 to release lock
        :
        : "r"(lock)
        : "memory"
    );
}

/* =======================================
   Critical Section (Mutex Protected)
   ======================================= */
void safe_increment(int thread_id, int iterations) {
    for (int i = 0; i < iterations; i++) {
        lock_acquire(&mutex_lock);

        // Begin critical section
        shared_counter++;
        if (thread_id == 1)
            thread1_count++;
        else
            thread2_count++;
        // End critical section

        lock_release(&mutex_lock);
    }
}

/* =======================================
   Pseudo Threads
   ======================================= */
void thread1(void) {
    safe_increment(1, 50000);
}

void thread2(void) {
    safe_increment(2, 50000);
}

/* =======================================
   Fake Delay to Simulate Overlap
   ======================================= */
void delay(volatile int count) {
    while (count--) {
        __asm__ volatile ("nop");
    }
}

/* =======================================
   Main Function
   ======================================= */
int main() {
    // Reset all shared variables
    mutex_lock = 0;
    shared_counter = 0;
    thread1_count = 0;
    thread2_count = 0;

    // Simulated concurrent thread execution
    thread1();
    delay(1000);
    thread2();

    return 0;
}

mutex.ld

/* Linker script for Task 15: Mutex Demo */

ENTRY(_start)

MEMORY {
    FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 256K
    SRAM  (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}

SECTIONS {
    .text : {
        *(.text.start)     /* Entry point */
        *(.text*)          /* Application code */
        *(.rodata*)        /* Read-only constants */
    } > FLASH

    .data : {
        _data_start = .;
        *(.data*)
        _data_end = .;
    } > SRAM

    .bss : {
        _bss_start = .;
        *(.bss*)
        _bss_end = .;
    } > SRAM

    _stack_top = ORIGIN(SRAM) + LENGTH(SRAM);
}

Compile the complete single file version

riscv32-unknown-elf-gcc -march=rv32imac -c full_mutex.c -o full_mutex.o
riscv32-unknown-elf-ld -T mutex.ld start.o full_mutex.o -o full_mutex.elf

Verify programs compile successfully

file full_mutex.elf

Check LR/SC instructions are generated

cho -e "\n=== LR/SC Instructions Found ==="
riscv32-unknown-elf-gcc -march=rv32imac -S full_mutex.c
grep -E "(lr\.w|sc\.w)" full_mutex.s

Screenshot from 2025-06-08 22-49-23 Screenshot from 2025-06-08 22-54-11 Screenshot from 2025-06-08 22-57-09

πŸ–¨οΈ Task 16: Retarget printf() to UART without OS

🎯 Objective

Implement a working printf() in a bare-metal RISC-V environment by:

  • Overriding the _write() syscall from Newlib
  • Redirecting its output to a memory-mapped UART
  • Running without any operating system

πŸ“‚ Files Overview

File Description
no_os.c C source file with custom _write() and main()
start.s Startup assembly to initialize stack and call main()
linker.ld Linker script to define memory layout and entry point
build_no_os.sh Script to compile and verify the full ELF

πŸ”§ Core Features

  • βœ… Uses UART0_BASE at 0x10000000 (QEMU virt default)
  • βœ… Retargets Newlib’s printf() through _write() syscall
  • βœ… Sends characters byte-by-byte using uart_putchar()
  • βœ… Includes stubbed syscalls to prevent linker errors

πŸ“„ Code Highlights

#define UART0_BASE 0x10000000
#define UART0_TX (*(volatile uint8_t *)UART0_BASE)

void uart_putchar(char c) {
    UART0_TX = c;
}

ssize_t _write(int fd, const void *buf, size_t count) {
    const char *str = (const char *)buf;
    for (size_t i = 0; i < count; i++) {
        uart_putchar(str[i]);
    }
    return count;
}
int main() {
    printf("Hello, UART!\n");
    printf("Decimal: %d, Hex: 0x%x\n", 123, 123);
    return 0;
}

Compilation Command (from script)

riscv32-unknown-elf-gcc -march=rv32imac -mabi=ilp32 \
  -nostartfiles -T linker.ld -o no_os.elf \
  start.s no_os.c -lc -lgcc

Run in QEMU (virt machine)

qemu-system-riscv32 -nographic -machine virt \
  -bios fw_jump.bin -kernel no_os.elf

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors