硬件准备

一、实现一个mtime外设作为RISC-V标准机器定时器中断源

src/rtl/peripheral/mtime_mmio.sv

`include "../lib/soc_pkg.sv"
`include "../lib/bus_if.sv"

module mtime_mmio
    (
        input  logic clk,
        input  logic rst_n,
        output logic timer_irq,
        simple_bus_if.slave bus
    );
    import soc_pkg::*;

    logic sel_mtime_lo, sel_mtime_hi, sel_mtimecmp_lo, sel_mtimecmp_hi;
    always_comb begin
        sel_mtime_lo    = (align_word(bus.addr) == IO_MTIME_LO_ADDR);
        sel_mtime_hi    = (align_word(bus.addr) == IO_MTIME_HI_ADDR);
        sel_mtimecmp_lo = (align_word(bus.addr) == IO_MTIMECMP_LO_ADDR);
        sel_mtimecmp_hi = (align_word(bus.addr) == IO_MTIMECMP_HI_ADDR);
    end

    logic [63:0] mtime;
    logic [63:0] mtimecmp;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            mtime <= 64'd0;
            mtimecmp <= 64'hFFFF_FFFF_FFFF_FFFF;
        end
        else begin
            mtime <= mtime + 64'd1;

            if (bus.wen && (|bus.wstrb)) begin
                if (sel_mtimecmp_lo) begin
                    mtimecmp[31:0] <= bus.wdata;
                end
                if (sel_mtimecmp_hi) begin
                    mtimecmp[63:32] <= bus.wdata;
                end
            end
        end
    end

    always_comb begin
        bus.rdata = 32'b0;
        if (sel_mtime_lo) begin
            bus.rdata = mtime[31:0];
        end
        else if (sel_mtime_hi) begin
            bus.rdata = mtime[63:32];
        end
        else if (sel_mtimecmp_lo) begin
            bus.rdata = mtimecmp[31:0];
        end
        else if (sel_mtimecmp_hi) begin
            bus.rdata = mtimecmp[63:32];
        end
    end

    assign timer_irq = (mtime >= mtimecmp);

endmodule

src/rtl/lib/soc_pkg.sv

`ifndef MELONSOC_SOC_PKG_SV
`define MELONSOC_SOC_PKG_SV

        package soc_pkg;
            ...
            // MTIME
            localparam logic [31:0] IO_MTIME_LO_ADDR    = IO_BASE_ADDR + 32'h0000_0060;
            localparam logic [31:0] IO_MTIME_HI_ADDR    = IO_BASE_ADDR + 32'h0000_0064;
            localparam logic [31:0] IO_MTIMECMP_LO_ADDR = IO_BASE_ADDR + 32'h0000_0068;
            localparam logic [31:0] IO_MTIMECMP_HI_ADDR = IO_BASE_ADDR + 32'h0000_006C;
            ...
        endpackage

`endif

mtime是只增不减的,依靠软件设置mtimecmp来触发新的中断。

二、实现ECALL软件Trap

src/rtl/core/cpu.sv:370

always_comb begin
    trap_req = 1'b0;
    trap_cause = 32'b0;

    // 硬件中断
    if (mstatus_mie && id_ex_valid && !halt_now) begin
        if (active_irq[11]) begin // 外部中断 (MEIP)
            trap_req = 1'b1;
            trap_cause = 32'h8000_000B; // 最高位为1表示中断,异常码 11
        end else if (active_irq[3]) begin // 软件中断 (MSIP)
            trap_req = 1'b1;
            trap_cause = 32'h8000_0003; 
        end else if (active_irq[7]) begin // 定时器中断 (MTIP)
            trap_req = 1'b1;
            trap_cause = 32'h8000_0007;
        end
    end

    // 软件trap
    if (!trap_req && id_ex_valid) begin
        if (id_ex_isECALL) begin
            trap_req = 1'b1;
            trap_cause = 32'd11; // Machine ECALL
        end else if (id_ex_isEBREAK) begin
            trap_req = 1'b1;
            trap_cause = 32'd3;  // Breakpoint
        end
    end
end

移植

源码下载与FreeRTOSConfig.h

首先

git clone https://github.com/FreeRTOS/FreeRTOS-Kernel.git

删除其他文件,保留以下文件/文件夹:

  • tasks.c, queue.c, timers.c, list.c
  • include
  • portable/GCC/RISC-V
  • portable/MemMang/heap_4.c

heap_x.c的区别可在这里找到

然后新增FreeRTOSConfig.h

src/program/FreeRTOS/FreeRTOSConfig.h

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#define configUSE_PREEMPTION                     1
#define configUSE_TIME_SLICING                   1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION  0
#define configUSE_TICKLESS_IDLE                  0

#define configCPU_CLOCK_HZ                       ( 27000000UL )
#define configTICK_RATE_HZ                       ( ( TickType_t ) 100 )

#define configMAX_PRIORITIES                     ( 4 )
#define configMINIMAL_STACK_SIZE                 ( ( unsigned short ) 256 )
#define configMAX_TASK_NAME_LEN                  ( 8 )
#define configTOTAL_HEAP_SIZE                    ( ( size_t ) ( 4 * 1024 ) )
#define configUSE_16_BIT_TICKS                   0
#define configIDLE_SHOULD_YIELD                  1

#define configISR_STACK_SIZE_WORDS               128

/*
 * !重要
 * MelonSoc MTIME/MTIMECMP MMIO layout (mtime_mmio.sv):
 *   0x400060 = mtime_lo,    0x400064 = mtime_hi
 *   0x400068 = mtimecmp_lo, 0x40006C = mtimecmp_hi
 * port.c reads +0/+4 for the 64-bit mtime value and writes +0/+4 for mtimecmp.
 */
#define configMTIME_BASE_ADDRESS                 ( 0x400060UL )
#define configMTIMECMP_BASE_ADDRESS              ( 0x400068UL )

#define configUSE_IDLE_HOOK                      0
#define configUSE_TICK_HOOK                      0
#define configCHECK_FOR_STACK_OVERFLOW           0
#define configUSE_MALLOC_FAILED_HOOK             0
#define configUSE_DAEMON_TASK_STARTUP_HOOK       0

#define configUSE_MUTEXES                        0
#define configUSE_RECURSIVE_MUTEXES              0
#define configUSE_COUNTING_SEMAPHORES            0
#define configUSE_TASK_NOTIFICATIONS             0
#define configUSE_QUEUE_SETS                     0
#define configQUEUE_REGISTRY_SIZE                0
#define configUSE_TIMERS                         0
#define configGENERATE_RUN_TIME_STATS            0
#define configUSE_TRACE_FACILITY                 0
#define configUSE_STATS_FORMATTING_FUNCTIONS     0
#define configUSE_NEWLIB_REENTRANT               0

#define configSUPPORT_DYNAMIC_ALLOCATION         1
#define configSUPPORT_STATIC_ALLOCATION          0

#define configTASK_RETURN_ADDRESS                0

#define configASSERT( x )   if( ( x ) == 0 ) { for( ;; ); }

#define INCLUDE_vTaskDelay                       1
#define INCLUDE_vTaskDelete                      0
#define INCLUDE_vTaskSuspend                     0
#define INCLUDE_vTaskPrioritySet                 0
#define INCLUDE_uxTaskPriorityGet                0
#define INCLUDE_vTaskDelayUntil                  0
#define INCLUDE_xTaskGetSchedulerState           0
#define INCLUDE_xTaskGetCurrentTaskHandle        0
#define INCLUDE_uxTaskGetStackHighWaterMark      0
#define INCLUDE_xTaskGetIdleTaskHandle           0
#define INCLUDE_eTaskGetState                    0
#define INCLUDE_xResumeFromISR                   0
#define INCLUDE_xTaskAbortDelay                  0
#define INCLUDE_xTaskGetHandle                   0
#define INCLUDE_xTaskResumeFromISR               0

#endif

当前只是最基础的移植,很多FreeRTOS特性都还没有支持。

应用层入口

src/program/app/main.c

int main(void) {
    uart_init();
    gpio_init();
    i2c_init(27000000u, 100000u);
    spi_init(27000000u, 200000u);
    timer_init_1mhz(27000000u);

    puts("Simple shell ready. Type 'help' for commands.");

    char line[128];
    while (1) {
        printf("> ");
        int len = read_line(line, sizeof(line));
        if (len <= 0) continue;
        char *cmd = line;
        while (*cmd == ' ') ++cmd;
        if (*cmd == 0) continue;

        char *arg = cmd;
        while (*arg && *arg != ' ') ++arg;
        if (*arg) { *arg++ = 0; while (*arg == ' ') ++arg; }

        if (str_equals(cmd, "help")) {
            shell_help();
		...
        } else if (str_equals(cmd, "test-rtos")) {
            shell_test_rtos(arg);
		...
    }

    return 0;
}

src/program/app/test_rtos.c

#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"
#include "test_rtos.h"

extern int printf(const char *fmt, ...);
extern int puts(const char *s);

static void task_a(void *param)
{
    (void)param;
    for (;;) {
        puts("[TaskA] ping");
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

static void task_b(void *param)
{
    (void)param;
    for (;;) {
        puts("[TaskB] pong");
        vTaskDelay(pdMS_TO_TICKS(300));
    }
}

void shell_test_rtos(const char *arg)
{
    (void)arg;

    extern void freertos_risc_v_trap_handler(void);
    uint32_t trap_addr = (uint32_t)freertos_risc_v_trap_handler;
    asm volatile("csrw mtvec, %0" :: "r"(trap_addr));

    puts("--- FreeRTOS Dual-Task Test ---");
    puts("TaskA prints every 200ms, TaskB every 300ms.");
    puts("Scheduler running (sim exits at max-cycles)...");

    xTaskCreate(task_a, "A", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
    xTaskCreate(task_b, "B", configMINIMAL_STACK_SIZE, NULL, 2, NULL);

    vTaskStartScheduler();

    puts("[FAIL] Scheduler returned!");
}

其中freertos_risc_v_trap_handlersrc/program/FreeRTOS/portable/GCC/RISC-V/portASM.s中定义。

运行效果

屏幕截图-2026-03-25-175317

关于Trap和中断

freertos_risc_v_trap_handler

它是FreeRTOS接管mtevc之后的trap总入口。

src/program/FreeRTOS/portable/GCC/RISC-V/portASM.s:329-384

.section .text.freertos_risc_v_trap_handler
.align 8
freertos_risc_v_trap_handler:
    portcontextSAVE_CONTEXT_INTERNAL /* 保存trap上下文 */

    csrr a0, mcause
    csrr a1, mepc
	/* `mcause`最高位为零表示同步异常,为一表示异步中断* /
    bge a0, x0, synchronous_exception

asynchronous_interrupt:
    store_x a1, 0( sp )                 /* Asynchronous interrupt so save unmodified exception return address. */
    load_x sp, xISRStackTop             /* Switch to ISR stack. */
    j handle_interrupt

synchronous_exception:
    addi a1, a1, 4                      /* Synchronous so update exception return address to the instruction after the instruction that generated the exeption. */
    store_x a1, 0( sp )                 /* Save updated exception return address. */
    load_x sp, xISRStackTop             /* Switch to ISR stack. */
    j handle_exception

handle_interrupt:
#if( portasmHAS_MTIME != 0 )

    test_if_mtimer:                     /* If there is a CLINT then the mtimer is used to generate the tick interrupt. */
        addi t0, x0, 1
        slli t0, t0, __riscv_xlen - 1   /* LSB is already set, shift into MSB.  Shift 31 on 32-bit or 63 on 64-bit cores. */
        addi t1, t0, 7                  /* 0x8000[]0007 == machine timer interrupt. */
        bne a0, t1, application_interrupt_handler
		/* 如果是标准机器定时器中断,则更新`mtimecpm` */
        portUPDATE_MTIMER_COMPARE_REGISTER
        call xTaskIncrementTick
        beqz a0, processed_source       /* Don't switch context if incrementing tick didn't unblock a task. */
        call vTaskSwitchContext
        j processed_source

#endif /* portasmHAS_MTIME */

application_interrupt_handler:
    call freertos_risc_v_application_interrupt_handler
    j processed_source

handle_exception:
    /* a0 contains mcause. */
    li t0, 11                                   /* 11 == environment call. */
    bne a0, t0, application_exception_handler   /* Not an M environment call, so some other exception. */
    call vTaskSwitchContext
    j processed_source

application_exception_handler:
    call freertos_risc_v_application_exception_handler
    j processed_source                  /* No other exceptions handled yet. */

processed_source:
    portcontextRESTORE_CONTEXT    /* 恢复的上下文受`vTaskSwitchContext`影响 */
/*-----------------------------------------------------------*/

ISR栈

ISR 栈就是“中断服务程序专用栈”。

ISR = Interrupt Service Routine,中断服务例程。

  • 任务栈:每个任务各自有一份,保存该任务函数调用链、局部变量、上下文
  • ISR 栈:所有中断/异常处理共享的一份专用栈

当前程序使用静态ISR栈。

中断相关寄存器

  • mtvec trap 入口地址。trap 一发生,PC 跳这里。
  • mepc trap 返回地址。异常/中断处理完后,mret 回到这里。
  • mcause trap 原因。
  • mstatus 关键看:
    • MIE:全局中断使能
    • MPIE:trap 前的 MIE 备份
  • mie 分类型中断使能。关键位:
    • bit 11:MEIE
    • bit 7:MTIE
    • bit 3:MSIE
  • mip 分类型 pending。关键位:
    • bit 11:MEIP
    • bit 7:MTIP
    • bit 3:MSIP
  • mtime
  • mtimecmp

参考资料

FreeRTOS 文档