RV32I/RV32E的寄存器是32位的,而mtime和mtimecmp總是64位的,RV32I/RV32E讀寫mtime和mtimecmp就需要分兩次Load/Store,而且mtime不停地在變化,這就需要一些技巧處理這兩個寄存器的通路。
mingdu.zheng at gmail dot com
64位的mtime和mtimecmp
mtime和mtimecmp總是64位的,無論是RV32I/RV32E或者是RV64I還是RV128I。對用作計數的這兩個寄存器而言,64位已經是天文數字,在産品的整個生命周期内都不會産生溢出。假設驅動mtime的時鐘頻率是10GHz(目前還沒有哪個CPU主頻能到10GHz的吧),那麼要讓64位的mtime溢出,那麼需要
0x10000000000000000 / 10000000000 / 60 / 60 / 24 / 365
年,也就是58年。HiFive1開發闆驅動mtime的時鐘頻率是32768Hz,那麼需要17851025年mtime才會溢出。
RV64I或RV128I通路mtime或mtimecmp隻需要一條指令,不會有什麼問題。RV32I/RV32E通路mtime或mtimecmp需要把這兩個寄存器拆成兩個32位寄存器來通路,這需要兩條指令,如果真好碰上低32位要進位的情況,那麼就會産生問題,通路的結果會産生巨大的偏差。
讀mtime
讀mtime的代碼如下:
static uint32_t mtime_lo(void)
{
return *(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIME);
}
static uint32_t mtime_hi(void)
{
return *(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIME + 4);
}
uint64_t get_timer_value()
{
uint32_t hi = mtime_hi();
uint32_t lo = mtime_lo();
return ((uint64_t)hi << 32) | lo;
}
假設進入
get_timer_value
函數時,mtime目前值為0x00000050_FFFFFFFF,假如在
hi = mtime_hi();
和
lo = mtime_lo();
兩條語句之間正好産生進位,那麼 hi = 0x00000050,lo = 0x00000000。 傳回結果是0x00000050_00000000,預期結果應該是0x00000051_00000000,不僅産生極大的偏差,而且時光倒流了。
改進的
get_timer_value
:
// 出自 HiFive1 示例程式 demo_gpio/bsp/env/freedom-e300-hifive1/init.c
uint64_t get_timer_value()
{
while (1) {
uint32_t hi = mtime_hi();
uint32_t lo = mtime_lo();
if (hi == mtime_hi())
return ((uint64_t)hi << 32) | lo;
}
}
改進後的
get_timer_value
在讀取了mtime的兩部分後再次讀取高32位,并判斷有沒有進位産生,如果産生進位那麼就丢棄本次錯誤的結果重新讀一次,如果沒有進位那麼結果是可靠的,傳回讀到的mtime值。
寫mtimecmp
如何正确地寫mtimecmp的值在 The RISC-V Instruction Set Manual Volume II: Privileged Architecture 3.1.15 Machine Timer Registers (mtime and mtimecmp) 中有明确的說明:
In RV32, memory-mapped writes to mtimecmp modify only one 32-bit part of the register. The following code
sequence sets a 64-bit mtimecmp value without spuriously generating a timer interrupt due to the intermediate
# New comparand is in a1:a0.
li t0, -1
sw t0, mtimecmp # No smaller than old value.
sw a1, mtimecmp+4 # No smaller than new value.
sw a0, mtimecmp # New value.
static void mtimecmp_lo(uint32_t v)
{
*(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIMECMP) = v;
}
static void mtimecmp_hi(uint32_t v)
{
*(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIMECMP + 4) = v;
}
void set_timecmp_value(uint64_t v)
{
uint32_t hi = (v >> 32) & 0xffffffff;
uint32_t lo = v & 0xffffffff;
mtimecmp_lo(0xffffffff); // No smaller than old value.
mtimecmp_hi(hi); // No smaller than new value.
mtimecmp_lo(lo); // New value.
}