硬件准备
一、实现一个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.cincludeportable/GCC/RISC-Vportable/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_handler在src/program/FreeRTOS/portable/GCC/RISC-V/portASM.s中定义。
运行效果

关于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栈。
中断相关寄存器
mtvectrap 入口地址。trap 一发生,PC 跳这里。mepctrap 返回地址。异常/中断处理完后,mret 回到这里。mcausetrap 原因。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
mtimemtimecmp