目录
往期文章传送门
一、硬件定时器
硬件实现
软件实现
二、上板测试
往期文章传送门
开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(二)—— 系统引导程序(Bootloader)_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(三)—— 串口驱动程序(UART)_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(四)—— 内存管理_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(五)—— 协作式多任务_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(六)—— 中断(interrupt)和异常(exception)_Patarw_Li的博客-CSDN博客
本节的代码在仓库的 05_HW_TIMER 目录下,仓库链接:riscv_os: 一个RISC-V上的简易操作系统
本文代码的运行调试会在前面开发的RISC-V处理器上进行,仓库链接:cpu_prj: 一个基于RISC-V指令集的CPU实现
一、硬件定时器
生活离不开对时间的管理,操作系统也是一样。
时钟节拍(Tick)
- 操作系统中最小的时间单位。
- Tick的单位(周期)由硬件定时器的周期决定(通常为1~100ms)。
- Tick周期越小,系统精度越高,但开销越大。
系统时钟
- 操作系统维护一个整形计数值,记录着系统启动直到当前发生的Tick总数。
硬件实现
在本项目中,timer作为一个外设挂载在总线rib上,rtl文件为 cpu_prj\FPGA\rtl\perips\timer.v :
五个读写信号用于读写timer模块中的寄存器,信号 timer_int_flag_o 用于给 clint 中断模块发出中断信号,verilog 代码如下:
// 32bit 定时器
module timer(
input wire clk ,
input wire rst_n ,
// 读写信号
input wire wr_en_i , // write enable
input wire[`INST_ADDR_BUS] wr_addr_i , // write address
input wire[`INST_REG_DATA] wr_data_i , // write data
input wire[`INST_ADDR_BUS] rd_addr_i , // read address
output reg [`INST_REG_DATA] rd_data_o , // read data
// 中断信号
output wire timer_int_flag_o
);
localparam TIMER_CTRL = 4'h0;
localparam TIMER_COUNT = 4'h4;
localparam TIMER_EVALUE = 4'h8;
// [0]: timer enable
// [1]: timer int enable
// [2]: timer int pending, software write 0 to clear it
// addr offset: 0x00
reg[31:0] timer_ctrl;
// timer current count, read only
// addr offset: 0x04
reg[31:0] timer_count;
// timer expired value
// addr offset: 0x08
reg[31:0] timer_evalue;
assign timer_int_flag_o = ((timer_ctrl[2] == 1'b1) && (timer_ctrl[1] == 1'b1))? 1'b1 : 1'b0;
// 读写寄存器,write before read
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
timer_ctrl <= `ZERO_WORD;
timer_evalue <= `ZERO_WORD;
end
else begin
if (wr_en_i == 1'b1) begin
case (wr_addr_i[3:0])
TIMER_CTRL: begin
// 这里代表软件只能把 timer_ctrl[2]置0,无法将其置1
timer_ctrl = {wr_data_i[31:3], (timer_ctrl[2] & wr_data_i[2]), wr_data_i[1:0]};
end
TIMER_EVALUE: begin
timer_evalue = wr_data_i;
end
endcase
end
if(timer_ctrl[0] == 1'b1 && timer_count >= timer_evalue) begin
timer_ctrl[0] = 1'b0;
timer_ctrl[2] = 1'b1;
end
case (rd_addr_i[3:0])
TIMER_CTRL: begin
rd_data_o = timer_ctrl;
end
TIMER_COUNT: begin
rd_data_o = timer_count;
end
TIMER_EVALUE: begin
rd_data_o = timer_evalue;
end
default: begin
rd_data_o = `ZERO_WORD;
end
endcase
end
end
// 计数器 timer_count
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
timer_count <= `ZERO_WORD;
end
else begin
if (timer_ctrl[0] != 1'b1 || timer_count >= timer_evalue) begin
timer_count <= `ZERO_WORD;
end
else begin
timer_count <= timer_count + 1'b1;
end
end
end
endmodule
其中:
timer_ctrl 为控制寄存器,低三位有效,分别是第0位 timer enable ,置1则 timer_count 开始计时;第1位 timer int enable,置1则允许发出中断信号,反之则不允许;第2位 timer int pending,当 timer_count >= timer_evalue 时,就把该位置1,表示有中断信号要发出,需要软件置0。
timer_count 为计数寄存器(只读)。
timer_evalue 存放过期值,用来与 timer_count 寄存器比较,当 timer_count >= timer_evalue 时则发出中断信号。
软件实现
代码实现为 riscv_os/05_HW_TIMER/timer.c :
// 1s
#define TIMER_INTERVAL 50000000
/*
* The TIMER control registers are memory-mapped at address TIMER (defined in inc/platform.h).
* This macro returns the address of one of the registers.
*/
#define TIMER_REG_ADDRESS(reg) ((volatile uint32_t *) (TIMER + reg))
/*
* TIMER registers map
* timer_count is a read-only reg
*/
#define TIMER_CTRL 0
#define TIMER_COUNT 4
#define TIMER_EVALUE 8
#define timer_read_reg(reg) (*(TIMER_REG_ADDRESS(reg)))
#define timer_write_reg(reg, data) (*(TIMER_REG_ADDRESS(reg)) = (data))
#define TIMER_EN 1 << 0
#define TIMER_INT_EN 1 << 1
#define TIMER_INT_PENDING 1 << 2
static uint32_t _tick = 0;
void timer_load(uint32_t interval)
{
timer_write_reg(TIMER_EVALUE, interval);
timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) | (TIMER_EN)));
}
/*
* enable timer interrupt
*/
void timer_init()
{
timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) | (TIMER_INT_EN)));
timer_load(TIMER_INTERVAL);
}
void timer_handler()
{
timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) & ~(TIMER_INT_PENDING)));
_tick++;
printf("tick: %d\n", _tick);
timer_load(TIMER_INTERVAL);
}
其中:
_tick 为该模块维护的全局时间节拍。
timer_load(uint32_t interval) 函数用于给定时器模块寄存器赋值,interval 个硬件时钟周期后发出定时器中断(如果 interval = 板子系统时钟频率,相当于1s)。
timer_init() 函数用于给定时器模块寄存器初始化。
timer_handler() 函数用于执行定时器中断处理,当定时器中断发生的时候,执行这个函数的内容。该函数会将 _tick 值加一后,执行 timer_load(uint32_t interval) 函数,从而达到持续计数的功能。
二、上板测试
烧录到板子上后,打开串口调试程序,可以看到tick值一直在计数,从而实现系统时钟的功能:
遇到问题欢迎加群 892873718 交流~