格雷码可以避免冒险与竞争,抗干扰能力更强
离线
1、格雷码主要是为了解决跨时钟域异步问题。GTC寄存器自然数->格雷码->CPU寄存器自然数,格雷码两端对自然数的转换都是由硬件进行转换,不需要软件处理。两头寄存器看到的值,都是咱们直接可读的自然数值。
2、GTC自然数的读取有两种方式,一种是读取GTC寄存器GTC_CNTVL GTC_CNTVH的值,一种是读取RISC-V CPU寄存器的值,通过汇编指令(csrr a0, time)(csrr a1, timeh) 获取;第一种方式由于CPU访问GTC寄存器的访问进行各级总线的同步,会占有百纳秒级别的开销,实际不推荐这么使用;建议使用第二种方式,访问CPU寄存器自然数的值,其开销取决于CPU指令的时间。
3、GTC时基250ns,通过读取CPU寄存器的值判断实现1us延迟是可行的。这里还需要特别注意延迟后执行动作耗费的时间,函数调用和寄存器读写都会占用一些时间。
离线
@xdlkliang
感谢解答。我上面的需求说法有误,我要的是分辨率为1us,精度是us级(10us左右)的时基计时。
我测试了第二种方式,发现读取timeh寄存器时会再现cpu异常。寄存器读取函数如下:
static inline uint32_t _read_csr_time_lo(void)
{
uint32_t value;
__asm__ __volatile__ ("csrr %0, time\n\t"
: "=r" (value) :
: "memory");
return value;
}
static inline uint32_t _read_csr_time_hi(void)
{
uint32_t value;
__asm__ __volatile__ ("csrr %0, timeh\n\t"
: "=r" (value) :
: "memory");
return value;
}
RTOS SDK 1.0.5下经测试读寄存器time没有问题,读寄存器timeh时出现cpu异常:
CPU Exception: NO.2
x1(ra) : 00000000400338b8 x2(sp) : 000000004022d2c8 x3(gp) : 00000000401ba318 x4(tp) : 00000000deadbeef
x5(t0) : 00000000401bbef8 x6(t1) : 0000000000000001 x7(t2) : 00000000deadbeef x8(s0/fp): 0000000000000006
x9(s1) : 00000000400bb7e0 x10(a0) : 0000000000000001 x11(a1) : 000000004022d2e8 x12(a2) : ffffffff00000000
x13(a3) : 0000000040223228 x14(a4) : 0000000000000000 x15(a5) : 0000000000000000 x16(a7) : 0000000000000009
x17(a7) : 0000000040223228 x18(s2) : 0000000040223222 x19(s3) : 0000000040150570 x20(s4) : 00000000401829f8
x21(s5) : 0000000040150900 x22(s6) : 000000004015bd28 x23(s7) : 00000000401c2400 x24(s8) : 000000000000000d
x25(s9) : 0000000040223222 x26(s10) : 0000000040222dd0 x27(s11) : 00000000deadbeef x28(t3) : 0000000000000022
x29(t4) : 000000000000005c x30(t5) : 000000000000000a x31(t6) : 00000000deadbeef
mcause : 0000000000000002
mtval : 00000000c8102773
mepc : 00000000400bb7f0
mstatus : 8000000a00007880
什么问题?
离线
发现RTOS SDK里有这个API:aic_get_time_us,其实现就是读time寄存器,于是找到上面问题的原因了。
我用的芯片是D21x,其CSR寄存器time是64位的,所以此芯片应该没有timeh寄存器,只需读time寄存器即可。
那么,基本上只需两条指令就能获取分辨率为1us的时基,不错!
最后D21x上读取内核时基的函数如下,D13x等32位MCU就没那么便利了:要读取寄存器2次且需处理读取过程中的进位情况
static inline uint64_t _read_csr_time(void)
{
uint64_t value;
__asm__ __volatile__ ("csrr %0, time\n\t"
: "=r" (value) :
: "memory");
return value;
}
PS:RTOS SDK里d13x、d12x等读取系统时基的函数 aic_get_ticks,没有处理读取过程中可能出现的进位情况(读取时低32位寄存器发生32位进位),是否是一个BUG?!
u64 aic_get_ticks(void)
{
return (((u64)csi_coret_get_valueh() << 32U) | csi_coret_get_value());
}
最近编辑记录 海石生风 (2024-08-11 15:48:30)
离线
感谢感谢,这个的确是个bug来着,需要把左移操作放到运算符右边,下一版SDK修改。
离线
"把左移操作放到运算符右边"还不行吧,参照Linux SDK那边(linux-5.10/arch/riscv/include/asm/timex.h)应该改成如下才行吧:
u64 aic_get_ticks(void)
{
u32 hi, lo;
do {
hi = csi_coret_get_valueh();
lo = csi_coret_get_value();
} while (hi != csi_coret_get_valueh());
return ((u64)hi << 32) | lo;
}
最近编辑记录 海石生风 (2024-08-13 12:07:26)
离线
展开来看的话,代码是这样
{
u64 tick_high_64;
u32 tick_low_32;
/* 1.先获取高32位,但是在执行这段的时候,低32位的tick实际还是在增加的 */
tick_high_64 = csi_coret_get_valueh() << 32;
/* 2.后获取低32位,如果在执行"1"之前,低32位已经是满量程0xFFFFFFFF。则这里就有可能溢出成0的概率 */
/* 所以这一段需要在1之前执行 */
tick_low_32 = csi_coret_get_value();
tick_high_64 |= tick_low_32;
return tick_high_64;
}
离线
您上面的修改应该也是对的
离线
@xdlkliang
不行呀,在读取过程中产生进位,即是高低两个寄存器都已经变化了呀,那么两个寄存器都要重新读取。
所以要用do while流程来或以下的if流程来处理:
hi = csi_coret_get_valueh();
lo = csi_coret_get_value();
// 产生进位的时间间隔是很长的,不可能在短时间内产生两次进位
if(hi != csi_coret_get_valueh()) {
hi = csi_coret_get_valueh();
lo = csi_coret_get_value();
}
最近编辑记录 海石生风 (2024-08-13 14:37:40)
离线