一、硬件定时器
生活离不开对时间的管理,操作系统也是一样。

时钟节拍(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值一直在计数,从而实现系统时钟的功能: