天天看點

RV32I/RV32E在通路mtime和mtimecmp的注意事項

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.
}