- 2024-12-17
-
回复了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V GCC 内嵌汇编
Jacktang 发表于 2024-12-15 09:52
都是汇编代码,厉害
涉及的汇编指令也不多,基本就是常用的存储加载和加法运算,RISC- V指令编码比较简洁易懂
- 2024-12-14
-
发表了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V GCC 内嵌汇编
本帖最后由 andeyqi 于 2024-12-13 11:20 编辑
## 简介
内嵌汇编代码是指在C语言中嵌入汇编代码(Inline Assembly Language in C Code),可以在对代码时间要求比较高,及在C语言中访问某些汇编指令来实现特殊功能,内嵌汇编代码主要有两种形式:
- 基础内嵌汇编代码(basic asm):不带任何参数
- 扩展内嵌汇编代码(extended asm):可以带输入/输出参数。
扩展内嵌汇编的模板如下
```c
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
```
asm 修饰词 (
指令部分
:输出部分
:输入部分
:损害部分
)
常见的约束体条件如下:
- = 被修饰的操作数具有只写属性
- “+” 被修饰的操作数具有可读可写属性
- & 用于输出限定符,这个操作数在输入参数指令执行完成后才能写入。
- r 通用寄存器
- p 内存地址
- m 内存变量
- i 立即数
### 内嵌汇编代码验证1
学习内嵌汇编代码,我们将之前汇编实现的my_memcpy 代码按照函数调用规划a0 传递数据源地址,a1 传递目的地址,a2 传递拷贝长度,返回拷贝长度通过a0 返回,以下是 rars 验证通过的汇编代码。
```c
.global my_memcpy_test
.text
li t0, 0x10010000
li t1, 0x11111111
sw t1, (t0)
li t1, 0x22222222
sw t1, 4(t0)
li t1, 0x33333333
sw t1, 8(t0)
li t1, 0x44444444
sw t1, 12(t0)
li t1, 0x55555555
sw t1, 16(t0)
li t1, 0x66666666
sw t1, 20(t0)
li t1, 0x77777777
sw t1, 24(t0)
li t1, 0x88888888
sw t1, 28(t0)
li t1, 0x99999999
sw t1, 32(t0)
li t1, 0xaaaaaaaa
sw t1, 36(t0)
li t1, 0xbbbbbbbb
sw t1, 40(t0)
li a0, 0x10010000
li a1, 0x100100c0
li a2, 32
my_memcpy_test:
mv t0, a0
mv t1, a1
add t2, t0, a2
.loop:
lw t3, (t0)
sw t3, 0(t1)
addi t0, t0, 4
addi t1, t1, 4
blt t0, t2, .loop
mv a0,a2
ret
```
将上述代码按照转化以下内嵌汇编代码:
```c
int my_memcpy(void * src,void * dest ,int len)
{
asm volatile (
"mv t0, a0 \n"
"mv t1, a1 \n"
"add t2, t0, a2 \n"
".loop: \n"
"lw t3, (t0) \n"
"sw t3, 0(t1) \n"
"addi t0, t0, 4 \n"
"addi t1, t1, 4 \n"
"blt t0, t2, .loop\n"
"mv a0,a2 \n"
"ret \n"
:::
);
}
```
在main函数内添加测试代码调用my_mempy 函数,验证函数工作状态。
```c
int main(void)
{
int src[8] = {0x11111111,0x22222222,0x33333333,0x44444444,
0x55555555,0x66666666,0x7777777,0x88888888};
int dest[8] = {0x00};
int ret = my_memcpy((void*) src,(void *)dest,32);
for(int i = 0;i < 8;i++)
printf("dest[%d] = %x,",i,dest);
printf("ret = %d\r\n",ret);
}
```
运行后数据已经按照预期的进行了拷贝,函数返回值也已经按照预期的打印输出32.
```c
dest[0] = 11111111,dest[1] = 22222222,dest[2] = 33333333,dest[3] = 44444444,dest[4] = 55555555,dest[5] = 66666666,dest[6] = 7777777,dest[7] = 88888888,ret = 32
```
反汇编 产看编译器产生的汇编代码如下:
```c
30079462 :
30079462: 82aa mv t0,a0
30079464: 832e mv t1,a1
30079466: 00c283b3 add t2,t0,a2
3007946a :
3007946a: 0002ae03 lw t3,0(t0)
3007946e: 01c32023 sw t3,0(t1)
30079472: 0291 addi t0,t0,4
30079474: 0311 addi t1,t1,4
30079476: fe72cae3 blt t0,t2,3007946a
3007947a: 8532 mv a0,a2
3007947c: 8082 ret
3007947e: 8082 ret
30079480 :
30079480: 1101 addi sp,sp,-32
30079482: 02000613 li a2,32
30079486: 0ff89597 auipc a1,0xff89
3007948a: c3258593 addi a1,a1,-974 # 400020b8
3007948e: 00043517 auipc a0,0x43
30079492: 4aa50513 addi a0,a0,1194 # 300bc938
30079496: cc22 sw s0,24(sp)
30079498: ca26 sw s1,20(sp)
3007949a: c84a sw s2,16(sp)
3007949c: c64e sw s3,12(sp)
3007949e: c452 sw s4,8(sp)
300794a0: ce06 sw ra,28(sp)
300794a2: fc1ff0ef jal ra,30079462
300794a6: 8a2a mv s4,a0
300794a8: 4401 li s0,0
```
### 内嵌汇编代码验证2
上面的内嵌汇编的代码直接使用的汇编代码实现的,并没有使用变量,我们再次基础上修改内嵌汇编的代码使用变量的方式实现上述功能,修改代码如下。
```c
static int my_memcpy1(void * src,void * dst ,int len)
{
unsigned long tmp = 0;
unsigned long end = (char *)src + len;
asm volatile (
"1: lw %1, (%2)\n" //tmp = *src
"sw %1, (%0)\n" //*dst = tmp
"addi %0, %0, 4\n" //dst += 4
"addi %2, %2, 4\n" //src += 4
"blt %2, %3, 1b" //if(src < end) goto 1
: "+r" (dst), "+r" (tmp), "+r" (src)
: "r" (end)
: "memory");
return len;
}
```
添加测试代码在main 函数中调用my_memcpy1 函数
```asp
int main(void)
{
static int src[8] = {0x11111111,0x22222222,0x33333333,0x44444444,
0x55555555,0x66666666,0x7777777,0x88888888};
static int dest[8] = {0x00};
int ret = my_memcpy1((void*) src,(void *)dest,32);
for(int i = 0;i < 8;i++)
printf("dest[%d] = %x,",i,dest);
printf("ret = %d\r\n",ret);
}
```
上述代码运行结果跟之前也是一致说明功能已经ok,查看反汇编代码如下
```asp
40034284 :
40034284: 1101 addi sp,sp,-32
40034286: ca26 sw s1,20(sp)
40034288: 0007c497 auipc s1,0x7c
4003428c: 98048493 addi s1,s1,-1664 # 400afc08
40034290: ce06 sw ra,28(sp)
40034292: cc22 sw s0,24(sp)
40034294: c84a sw s2,16(sp)
40034296: c64e sw s3,12(sp)
40034298: 87a6 mv a5,s1
4003429a: 4701 li a4,0
4003429c: 00078697 auipc a3,0x78
400342a0: 1c868693 addi a3,a3,456 # 400ac464
400342a4: 00078617 auipc a2,0x78
400342a8: 1e060613 addi a2,a2,480 # 400ac484
400342ac: 4298 lw a4,0(a3)
400342ae: c398 sw a4,0(a5)
400342b0: 0791 addi a5,a5,4
400342b2: 0691 addi a3,a3,4
400342b4: fec6cce3 blt a3,a2,400342ac
```
发现编译器将 my_memcpy1 函数进行了编译优化,进行了inline 处理,修改my_memcpy1 函数的编译属性__attribute__((noinline))查看反汇编代码如下。
```c
40034264 :
40034264: 00c506b3 add a3,a0,a2 //end = src + len
40034268: 4701 li a4,0 //tmp = 0
4003426a: 87aa mv a5,a0 //a5 = src
4003426c: 4398 lw a4,0(a5) //tmp = *src
4003426e: c198 sw a4,0(a1) //*dst = tmp
40034270: 0591 addi a1,a1,4 //dst += 4
40034272: 0791 addi a5,a5,4 //src += 4
40034274: fed7cce3 blt a5,a3,4003426c if(src < end) goto 4003426c
40034278: 8532 mv a0,a2
4003427a: 8082 ret
```
从上述反汇编代码可以看出
src 通过 a0/a5 传递
dst 通过 a1 传递
len 通过 a2 传递
将end 变量分配了a3寄存器
tmp 变量分配了a4 寄存器
返回值通过 a0 返回
- 2024-12-08
-
回复了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V 函数调用规范
电子烂人 发表于 2024-12-8 18:01
这个函数调用规范是在RISC-V才新提出的吗?
这个是RISC-V架构 定义的ABI接口调用规范
-
发表了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V 函数调用规范
本帖最后由 andeyqi 于 2024-12-8 16:02 编辑
## 简介
函数调用规范(calling convention)是用来描述父/子函数是如何编译和链接的,特别是父函数和子函数之间的调用约定,如栈的布局,参数传递等。
RISC-V 整型通用寄存器在函数调用规范中使用情况如下:
从上述表格可以看出通用寄存器,只有**sp,s0-s11**在子函数中使用了需要保存到栈中,使用完之后再从栈中恢复到寄存器中,以下是riscv-abi.pdf 文档中的描述信息。
> In the standard ABI, procedures should not modify the integer registers tp and gp, because signal handlers may
rely upon their values.
The presence of a frame pointer is optional. If a frame pointer exists it must reside in x8 (s0), the register
remains callee-saved.
**函数的调用规范如下:**
- 函数的前八个参数使用a0-a7寄存器来传递
- 如果函数参数大于8个,前面8个参数使用寄存器来传递,后面的传递使用栈来传递。
- 如果传递的参数小于寄存器宽度(64位),那么先按照符号扩展到32位,再按照符号扩展到64位,如果传递的数据是128位,则使用一对寄存器来传递该值,官方文规则说明如下。
> Scalars that are at most XLEN bits wide are passed in a single argument register, or on the stack by value if
none is available. When passed in registers or on the stack, integer scalars narrower than XLEN bits are widened
according to the sign of their type up to 32 bits, then sign-extended to XLEN bits. When passed in registers or
on the stack, floating-point types narrower than XLEN bits are widened to XLEN bits, with the upper bits
undefined.
> Scalars that are 2×XLEN bits wide are passed in a pair of argument registers, with the low-order XLEN bits in
the lower-numbered register and the high-order XLEN bits in the higher-numbered register. If no argument
registers are available, the scalar is passed on the stack by value. If exactly one register is available, the loworder XLEN bits are passed in the register and the high-order XLEN bits are passed on the stack.
- 函数的返回值保存在a0 a1 寄存器中
- 函数的返回值保存在ra 寄存器中
- 如果子函数中使用了s0-s11 寄存器,那么子函数在使用这些寄存器前需要先保存到栈中,函数退出时再将寄存器从栈中恢复
- 栈向下生长,sp 寄存器在进入子函数时sp 要对齐到16字节的边界上,传递的第一个参数位于sp 寄存器的0偏移量处,后续的参数则位于高地址处
- 如果GCC 使用-fno-omit-frame-pointer 编译选项,那么编译器将使用s0 寄存器作为栈帧指针(Frame Point,FP)
### 参数传递验证
在编写如下代码传递多个参数C代码。
```c
#include
int main(void)
{
int a = 1,b = 2,c = 3,d = 4,e = 5,f = 6,g = 7,h = 8,i = 9,j = -1;
printf("data %d %d %d %d %d %d %d %d %d %d \n",
a,b,c,d,e,f,g,h,i,j);
}
```
上述代码会被编译成如下的汇编代码:
```c
30078e2a :
30078e2a: 1101 addi sp,sp,-32
30078e2c: 557d li a0,-1
30078e2e: c42a sw a0,8(sp)
30078e30: 4525 li a0,9
30078e32: c22a sw a0,4(sp)
30078e34: 4521 li a0,8
30078e36: c02a sw a0,0(sp)
30078e38: 489d li a7,7
30078e3a: 4819 li a6,6
30078e3c: 4795 li a5,5
30078e3e: 4711 li a4,4
30078e40: 468d li a3,3
30078e42: 4609 li a2,2
30078e44: 4585 li a1,1
30078e46: 00037517 auipc a0,0x37
30078e4a: 71250513 addi a0,a0,1810 # 300b0558
30078e4e: ce06 sw ra,28(sp)
30078e50: d53c80ef jal ra,30041ba2
```
从以上汇编代码调用printf 函数时传递参数的过程可以看出,对应流程如下
1. sp = sp - 32 将SP对齐到32字节边界上,书中描述是16 字节,这个猜测是跟编译器的实现有关
2. sp + 8 处保存参数-1
3. sp + 4 处保存参数9
4. sp + 0 处保存参数8
5. a7 保存参数7
6. a6 保存参数6
7. a5 保存参数5
8. a4 保存参数4
9. a3 保存参数3
10. a2 保存参数2
11. a1 保存参数1
12. a0 保存格式化字符串 data %d %d %d %d %d %d %d %d %d %d 这里面的寻址方式正是之前文章说的PC相对寻址
调用printf 函数前栈帧及通用寄存器a0-a7的布局如下
从上述过程也可以看出,参数的保存顺序是从左至右先使用a0-a7 ,参数超过8个后使用栈空间,栈空间内靠右侧的参数分配在栈的高地址也就是先压栈的效果,上述参数8,9,-1 的压栈的顺序为 -1.9,8
- 2024-12-07
-
回复了主题帖:
FREERTOS编译错误的问题
chenbingjy 发表于 2024-12-6 11:59
查了,就是这样写的
是不是这里面的 --far 或者 --interrupt 没有被正常识别编译器不认识报错了
-
发表了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V memcpy 实验
本帖最后由 andeyqi 于 2024-12-7 12:10 编辑
## 简介
书中有很多上机试验的参考代码,书中使用的是QEMU的实验环境,本地使用RARS 模拟器环境来继续验证学习RISC-V 体系知识,实验代码可以从以下路径获取下载。
> 《RISC-V体系结构编程与实践》一书的实验参考代码由git来管理,
读者可以通过如下地址来下载。
Github站点:
https://github.com/runninglinuxkernel/riscv_programming_practice
国内镜像站点:
https://gitee.com/benshushu/riscv_programming_practice
书中有如下memcpy 的试验代码
```c
.global my_memcpy_test
my_memcpy_test:
li t0, 0x80200000
li t1, 0x80210000
addi t2, t0, 32
.loop:
ld t3, (t0)
sd t3, (t1)
addi t0, t0, 8
addi t1, t1, 8
blt t0, t2, .loop
ret
```
上述试验代码,跟memcpy 的参数分布类似,t0 存储数据源地址0x80200000,t1 存储目的地址 0x80210000 ,t2 代表拷贝结束地址 拷贝32字节的数据,参照上述代码在RARS 环境下运行该代码进行试验。
拷贝上述代码至RARS 模拟运行修改数据源地址和目标地址,发现运行结果跟预期的一致,从0x10010000 拷贝了 32字节至 0x100100c0地址
```c
.global my_memcpy_test
.text
my_memcpy_test:
li t0, 0x10010000
li t1, 0x100100c0
addi t2, t0, 32
.loop:
ld t3, (t0)
sd t3, 0(t1)
addi t0, t0, 8
addi t1, t1, 8
blt t0, t2, .loop
ret
```
- 2024-12-03
-
回复了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V 存储加载指令集学习
学学学学学学学 发表于 2024-12-3 15:08
rars 这个软件有下载链接吗大佬
https://github.com/TheThirdOne/rars
- 2024-11-29
-
回复了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V CSR 指令集学习
Jacktang 发表于 2024-11-29 07:26
好书,RISC-V 体系结构该讲的都讲到了
嗯 很适合座位学习RISC-V 的入门书籍
- 2024-11-28
-
发表了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V CSR 指令集学习
本帖最后由 andeyqi 于 2024-11-28 23:47 编辑
## 简介
RISC-V 除了通用寄存器外,还定义了CSR 系统控制与状态寄存器,来实现中断控制及系统状态获取等功能,这些特殊的寄存器的访问需要使用特定的 CSR 指令来读取,设置。对于CSR 指令的验证依然在 RARS 模拟器上进行验证,RARS 定义了 U Mode 下的CSR 寄存器,模拟器的寄存器定义如下。
以下书中对U mode 下的CSR 寄存器说明
以下是书中对CSR 指令的说明
### CSRRW 指令测试验证
指令格式:csrrw rd,csr,rs1
指令描述:原子的交换CSR 和 rs1 寄存器的值,读取CSR旧值,写入到rd寄存器中,与此同时rs1 寄存器的旧值被写入到CSR中,对应伪代码如下
rd = csr,csr = rs1
以下是CSRW 指令说明。
上述当rd 为 x0 时,不进行读操作,编写如下代码验证csrrw 指令运行效果。
```c
li x2,0x55
csrrw x1,fcsr,x2
nop
```
上述代码执行后会将fcsr 寄存器的值(0xaa)更新到x1,将x2 的值0x55 更新到fcsr 寄存器
**csrrw x1,fcsr,x2** 执行后x1 被更新为0xaa
同时fcsr 寄存器更新为x2 的值0x55
### CSRRS 指令测试验证
指令格式:csrrs rd,csr,rs1
指令说明:原子的读取CSR 的值并且设置CSR中相应的位。对应伪代码如下
rd = csr,csr |= rs1
CSRRS 指令说明如下
> The CSRRS (Atomic Read and Set Bits in CSR) instruction reads the value of the CSR, zeroextends the value to XLEN bits, and writes it to integer register rd. The initial value in integer
register rs1 is treated as a bit mask that specifies bit positions to be set in the CSR. Any bit that
is high in rs1 will cause the corresponding bit to be set in the CSR, if that CSR bit is writable.
Other bits in the CSR are unaffected (though CSRs might have side effects when written).
添加如下测试代码,fcsr 初始值为0xaa, 设置rs1 = 0x55 掩码跟新fcsr bit0-3 ,按照上述说明执行后fcsr 更新为0xff.
```c
li x2,0x55
csrrs x1,fcsr,x2
nop
```
另外的csrrc/csrrwi/csrrsi/csrrci 指令跟上述处理也是类似,再此就不额外赘述了。
CSR寄存器 指令还可以通过以下伪指令来访问,伪指令会被被转化成相应的CSR指令来更新寄存器
- 2024-11-26
-
回复了主题帖:
《RISC-V 体系结构编程与实践(第2版)》互动:接龙解答作者笨叔出的思考题
本帖最后由 andeyqi 于 2024-11-26 17:55 编辑
第3章:问题回答
1.RISC-V指令集有什么特点?
==》
RISC-V 指令集采用了模块化的设计方法,设计了一个最小集合的基础指令集,这个最小指令集可以完整的是实现一个软件栈,其他功能可以在此基础上通过模块化的方式叠加扩展,用于支持浮点,乘法,除法,矢量运算等。
2.RISC-V指令编码格式分成几类?
==》
指令格式可以分为6种
R类型:寄存器与寄存器算术指令。
I类型:寄存器与立即数算术指令或者加载指令。
S类型:存储指令。
B类型:条件跳转指令。
U类型:长立即数操作指令。
J类型:无条件跳转指令。
3.什么是零扩展和符号扩展?
==》
符号扩展:对于一个需要扩展的立即数的最高位作为符号位进行扩展,最高位的符号位为1 前面的数据全部填充为1,0则填充为0
零扩展:扩展时不考虑符号位,直接在前面填充为0
4.什么是PC相对寻址?
==》
使用PC + offset 的形式进行寻址方式,RISC-V 提供了AUIPC 指令根据当前PC的值 算出 要寻址的偏移的大小,但是AUIPC 指令编码格式的限制只能寻址到4KB边界的位置,随影通常配合ADDI 指令联合使用来实现32位地址空间的PC 相对寻址。
-
回复了主题帖:
《RISC-V 体系结构编程与实践(第2版)》互动:接龙解答作者笨叔出的思考题
第1章:问题回答
1.RISC-V体系结构有什么特点?
==>
对学术界和工业界完全开放,开源的架构
属于一个通用指令集体系结构
真正适合硬件实现的指令集体系结构
实现最小的整数指令集作为基础指令集,在此基础上实现众多众多可选扩展
支持IEEE 754浮点标准
支持众多扩展指令集
支持多核异构体系结构
支持可选的压缩指令集
支持虚拟化扩展
支持可伸缩矢量指令扩展
2.RISC-V体系结构处理器包含多少个通用寄存器?
==>
包括32个整形通用寄存器x0-x31,32个浮点通用寄存器 f0~f31
3.RISC-V体系结构包含几种处理器模式?它们分别有什么作用
==>
U 模式用于运行应用程序
S模式用于运行操作系统,为应用程序提供服务
M模式用于运行SBI固件,为操作系统提供服务
- 2024-11-24
-
回复了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V 架构寄存器学习
lemonboard 发表于 2024-11-24 10:33
RISC-V在国内的份量越来越重了
嗯 国内市面上的RISC-V 的芯片也越来越多了
-
发表了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V 存储加载指令集学习
本帖最后由 andeyqi 于 2024-11-24 22:35 编辑
## 简介
RISC-V 指令按照类型分为6种,对应指令编码格式如下。
- R类型:寄存器与寄存器算术指令。
- I类型:寄存器与立即数算术指令或者加载指令。
- S类型:存储指令。
- B类型:条件跳转指令。
- U类型:长立即数操作指令。
- J类型:无条件跳转指令。
以下是 RV32I 基础指令集所包含的指令
RV64I 指令相对RV32I 增加了部分指令,这也体现了RISC-V 指令集模块化的设计方式
### 存储与加载指令
RISC-V 架构是基于存储加载的设计方式,CPU 访问内存不可以直接从将数据写入内存,需要通过过寄存器将要写的数据先写入寄存器,让后将寄存器数据加载到内存,所以存储与加载指令只是最基本的指令。
#### 加载指令
以下是加载指令编码格式
对应ld 指令说明如下
以下是书中对ld 指令的测试代码
上述代码使用LD指令load 0x8000000 + offset 的memory 书籍至寄存器,书中使用的验证环境为QEMU ,本地使用 rars 软件验证上述ld 指令运行效果。
添加如下测试代码
```c
li t0,0x10010010
lb t1,(t0)
lb t2,4(t0)
lbu t3,4(t0)
lb t4,-4(t0)
ld t5,(t0)
ld t6,16(t0)
```
运行后结果如下,发现按照预期的方式将内存的数据加载到t1-t6 寄存器中。
#### 存储指令
以下是store 指令的编码说明
我们在之前的代码基础上添加代码将t1-t6 的寄存器内容写入到内存中
```c
li t0,0x10010010
lb t1,(t0)
lb t2,4(t0)
lbu t3,4(t0)
lb t4,-4(t0)
ld t5,(t0)
ld t6,16(t0)
li a0,0x10010040
sb t1,(a0)
sb t2,4(a0)
sw t3,8(a0)
sw t4,12(a0)
sd t5,16(a0)
sd t6,24(a0)
```
运行后发现t1-t6 寄存器的数值已经通过a0寄存器写入到内存中了。
- 2024-11-23
-
发表了主题帖:
【读书】《RISC-V 体系结构编程与实践(第2版)》-- RISC-V 架构寄存器学习
本帖最后由 andeyqi 于 2024-11-24 16:42 编辑
## 简介
很高兴能够与参加论坛的《RISC-V 体系结构编程与实践(第2版)》的读书活动,借此读书活动学习了解RISC-V架构的知识,收到了论坛邮寄的书籍,先来张书籍崭新的封面,查看目录发现内容还是很丰富的,熟悉RISC-V 先从架构的基本的寄存器开始学习。
### RISC-V 寄存器
#### 通用寄存器
RISC-V 体系结构提供32个通用整型寄存器(X0-X31),寄存器的长度则根据处理器的位数相关,64位处理器寄存器长度位64位,32位处理器则是32位,长度位XLENG根据处理器的长度一致,浮点数运行RISC-V处理器也提供32个浮点数通用寄存器f0-f31。书中对寄存器也有详细描述,对应内容如下。
RISC-V 对寄存器划分了特定的功能用途对应如下。
- x0 寄存器别名为zer0,寄存器内容全是0,可以作为源寄存器,也可以作为目标寄存器
- x1 寄存器别名为ra 链接寄存器,用于保存函数返回地址
- x2 寄存器别名为sp 栈指针寄存器,指向栈地址
- x3 寄存器别名为gp 全局寄存器,用于链接器松弛优化
- x4 寄存器别名为tp 线程寄存器,通常在操作系统中保存指向进程控制块 task_struct 指针
- x5-x7 以及 x28-x31 寄存器为临时寄存器,它们别名分别是t0~t6
- x8-x9 以及 x18-x27 寄存器的别名分别为s0-s11,称为Callee-saved registers 寄存器用户在程序中使用需要保存到栈中
- x10-x17 寄存器别名为a0-a7,在函数调用时传递参数和返回值
以下是**通用寄存器**的number 和 别名的对应关系
以下是**浮点寄存器**的number 和 别名的对应关系
#### 系统寄存器
除了上述系统必须的通用寄存器外,RISC-V 还定义了其他的系统控制和状态寄存器(Control and Status Registers,CSRs),通过特定的CSR 指令去访问这些寄存器。CSR 指令编码中预留了12bit 的编码空间(csr[11:0])用来作为索引访问特定的系统寄存器,对应的CSR 指令编码格式如下。
其中12bit 的编码空间可以用来索引4096 个寄存器,其中12bit 编码管理方式如下:
1. CSR[11:10] 用来表示系统寄存器的读写属性,0b11 表示只读,其余表示可读可写
2. CSR[9:8] 表示允许访问该系统处理器的模式,0b00 表示U模式,0b11 S模式,0b11 M模式 ,0b10 HS/VS 模式
以下是RISC-V 规范(riscv-privileged-20211203.pdf)中对CSR 寄存器管理的说明
**U模式下的CSR寄存器定义说明**
**S模式的CSR寄存器定义说明**
**M模式下的CSR寄存器定义说明**
##### GD32VF103 MCU CSR 寄存器的定义说明
对于CSR 寄存器我们查看GD32VF103基于RISC-V 32bit 的 Bumblebee 内核的处理器芯片的手册查看其中定义了如下的CSR寄存器
从上述CSR 寄存器定义可以看出GD32VF103 的芯片只实现支持了M模式和U模式,支持了部分的RISC-V 标准的CSR寄存器外还自定义了部分的CSR 寄存器。
CSR 寄存器访问索引中定义了寄存器访问的模式及读写属性,该特性可以用于芯片的访问权限的管理,以下是GD32VF103基于RISC-V 32bit 的 Bumblebee 内核的 的管理方法说明。
- 2024-11-21
-
回复了主题帖:
STM32全球线上峰会,STM32N6重磅发布啦!
- 2024-11-20
-
回复了主题帖:
读书入围名单:《RISC-V 体系结构编程与实践(第2版)》
个人信息无误,确认可以完成阅读计划和打卡任务
- 2024-09-29
-
回复了主题帖:
【匠芯创D133CBS】--5.使用GuiGuider 配置LVGL
wangerxian 发表于 2024-9-29 17:41
GuiGuider做复杂的界面可以吗?
可以, guiguider 的示例里有比较复杂的姐买你像数字仪表的demo
- 2024-09-28
-
发表了主题帖:
【匠芯创D133CBS】--5.使用GuiGuider 配置LVGL
本帖最后由 andeyqi 于 2024-9-28 08:28 编辑
## 简介
开发板已经一致适配了LVGL,SDK里也已经集成一些写好的demo,本次使用GuiGuider 来年生成显示界面并在办卡上显示。
## GuiGuider 界面配置
在GuiGuider 下配置screen 分辨率为1024*600 及screen 颜色添加switch 和 led 控件,通过switch 控件来控制led 空间的点亮熄灭,模拟点灯场景。
设置switch 开关的click event,用于控制led 的点亮熄灭。
配置好上诉event 后,会在event_init.c 中生成如下代码我们只要在里面添加对应的处理即可。
```c
#include "events_init.h"
#include
#include "lvgl.h"
#if LV_USE_FREEMASTER
#include "freemaster_client.h"
#endif
static void screen_sw_1_event_handler (lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
switch (code) {
case LV_EVENT_CLICKED:
{
lv_obj_t * status_obj = lv_event_get_target(e);
int status = lv_obj_has_state(status_obj, LV_STATE_CHECKED) ? 1 : 0;
switch(status) {
case 0:
{
break;
}
case 1:
{
break;
}
default:
break;
}
break;
}
default:
break;
}
}
```
至此Guiguider 已经配置好了,我们继续修改SDK 添加GuiGuider 的demo 支持。
## GuiGuider 代码添加至SDK
```c
LVGL 启动流程如下
lvgl_thread_init
==》rt_thread_init //创建LVGL thread
====》lvgl_thread_entry
======》lv_port_disp_init();//初始化配置LCD显示
======》lv_port_indev_init();//配置触摸屏输入响应
======》lv_user_gui_init();//配置显示界面
========》aic_ui_init()
```
最终会调用到aic_ui_init()函数这个函数里会配置LVGL界面配置,我们GuiGuider 代码调用也要放到此处,,该函数内根据不同的宏配置运行不同的demo,对应代码如下:
```c
void aic_ui_init()
{
/* qc test demo is only for aic internal qc testing, please ignore it. */
#ifdef LPKG_USING_LVGL_VSCODE
extern void vscode_ui_init();
vscode_ui_init();
return;
#endif
#ifdef AIC_LVGL_BASE_DEMO
#include "base_ui.h"
base_ui_init();
#endif
#ifdef AIC_LVGL_METER_DEMO
#include "meter_ui.h"
meter_ui_init();
#endif
#ifdef AIC_LVGL_LAUNCHER_DEMO
extern void launcher_ui_init();
launcher_ui_init();
#endif
#ifdef AIC_LVGL_MUSIC_DEMO
extern void lv_demo_music(void);
lv_demo_music();
#endif
#ifdef AIC_LVGL_DASHBOARD_DEMO
extern void dashboard_ui_init(void);
dashboard_ui_init();
#endif
#ifdef AIC_LVGL_SHOWCASE_DEMO
extern void showcase_demo_init(void);
showcase_demo_init();
#endif
#ifdef AIC_LVGL_ELEVATOR_DEMO
#include "elevator_ui.h"
elevator_ui_init();
#endif
#ifdef AIC_LVGL_SLIDE_DEMO
extern void slide_ui_init(void);
slide_ui_init();
#endif
#ifdef AIC_LVGL_SIMPLE_PLAYER_DEMO
extern void simple_player_ui_init(void);
simple_player_ui_init();
#endif
return;
}
```
根据上述的代码结构我们添加AIC_LVGL_GUI_GUIDER 配置项目。
在menuconfig 配置界面内选择该demo
在aic_ui_init() 函数内添加Guiguider 初始化代码,并将GuiGuider 生成的代码加入工程编译。
配置好后在switch 的event 响应中添加LED 点亮熄灭的控制代码,对应switch case 0/1 的内容部分。
```c
extern lv_ui guider_ui;
static void screen_sw_1_event_handler (lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_color_t led_color;
switch (code) {
case LV_EVENT_CLICKED:
{
lv_obj_t * status_obj = lv_event_get_target(e);
int status = lv_obj_has_state(status_obj, LV_STATE_CHECKED) ? 1 : 0;
switch(status) {
case 0:
{
led_color.ch.green = 0x3f;
led_color.ch.blue = 0x00;
led_color.ch.red = 0x00;
lv_led_set_color(guider_ui.screen_led_1,led_color);
lv_led_set_brightness(guider_ui.screen_led_1,0);
break;
}
case 1:
{
led_color.ch.green = 0x3f;
led_color.ch.blue = 0x00;
led_color.ch.red = 0x00;
lv_led_set_color(guider_ui.screen_led_1,led_color);
lv_led_set_brightness(guider_ui.screen_led_1,255);
break;
}
default:
break;
}
break;
}
default:
break;
}
}
```
## 编译运行
将上述代码编译通过后运行效果如下,LVGL已经按照设计的方式运行。
[localvideo]2f5f2a13e6268400be7518adc7187778[/localvideo]
- 2024-09-02
-
发表了主题帖:
【匠芯创D133CBS】--4.eFuse 读写使用测试
本帖最后由 andeyqi 于 2024-9-2 22:32 编辑
## eFuse 简介
>芯片电路通常是生产时物理固化的电路,生产完成之后不可改变。实际应用中有一些重要信息,需要固化在芯片之中, 使得上电即可读取使用,比如一些硬件的性能参数信息。但是这些参数可能与芯片的生产工艺、所用的材料等有关系, 无法在设计时确定,因此需要有一种存储技术,即可以固化信息,又能够在生产之后进行修改。
> eFuse 就是一种允许芯片上的电路在运行时进行修改的技术。它类似 EEPROM,属于一次性可编程存储器。eFuse ROM 里初始存储的内容都是 0,当需要修改某个比特位的值时,通过片上电压(通常为2.5V)提供一个持续一定时长的直流脉冲, 将代表该比特位的单根熔丝烧断,使得该比特位变为 1。熔丝被烧断之后,无法再恢复连接,因此这样的修改是一次性不可逆的。
> SID(Secure ID)模块主要用于读写eFuse,包括每颗芯片独立ID,每颗芯片量产校准, 以及安全管理用到的KEY,HASH码等信息,eFuse 的控制可以使用 SID 来进行读写访问,对应的block 图如下。
对应的特性如下,结合block 可以了解efuse 的功能。
- 4Kbit eFuse双备份实现2Kbit空间,软件读写按照4Kbit空间,以32bit为单位
- 2Kbit SRAM缓存,上电复位放开后自动读取eFuse值到SRAM
- 2Kbit空间,以32bit为单位,可烧写禁止读操作或写操作
- 提供CE安全密钥功能(SSK/HUK/PNK/PSK0/PSK1/PSK2/PSK3)
- 具备 BROM 特权保护功能,固件防回滚计数区域仅在 BROM 中可烧
- 支持通过烧写关闭JTAG功能,并可通过安全认证再打开JTAG
从上述信息可以知道芯片内集成了4K bit即512byte fuse memory 空间,访问读写按照32bit 为单位即128 word 的操作空间,上述的128 word 按照双分区的模式被加载到SRAM空间,SRAM 空间大小为64 word,对应eFuse 的 和SRAM 的映射关系如下:
对应的fuse 64 word 的空间byte0~0xff 其中0x00~0xbf 芯片设计时已经定义好了使用方法,留给用户自定义使用的空间为0xc0~0xff 64 byte 的空间(word 48~63),我们本次实验也基于这个空间进行测试efuse 功能。
## eFuse 读写验证
env环境下 menuconfig 使能SDI 驱动测试接口
开启上述配置后系统年内汇集成efuse 驱动及efuse 读取写入测试接口,对应命令使用说明如下:
```
aic /> efuse help
efuse command usage:
efuse help : Get this help.
efuse dump offset len : Dump data from eFuse offset.
efuse read addr offset len : Read eFuse data to RAM addr.
efuse write addr offset len : Write data to eFuse from RAM addr.
efuse writehex offset data : Write data to eFuse from input hex string.
efuse writestr offset data : Write data to eFuse from input string.
```
我们先使用 efuse dump 命令dump 2Kbit efuse 数据
```c
aic /> efuse dump 0 0x100
0x00000000: 00 00 00 0F 00 00 00 00 00 00 00 0F 00 00 00 00 |................|
0x00000010: C8 81 C6 B8 2C 3E 20 20 03 0C 03 45 02 00 30 04 |....,> ...E..0.|
0x00000020: 00 00 00 00 21 00 00 00 0D 02 00 3F A8 65 5A 00 |....!......?.eZ.|
0x00000030: 00 00 00 00 D6 17 7D 2E 00 00 00 00 00 00 00 00 |......}.........|
0x00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x000000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x000000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x000000c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x000000d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x000000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
```
从上面的efuse 功能介绍可知,SID 模块在启动后会将efuse 的数据加载到内部的SRAM 中,对应的寄存器为efuse_data
我们来添加命令来dump 该段寄存器的数值和fuse 的数据比较看是否一致,对应的寄存器地址为sid_base + offfset = 0x19010000 + 0x200 = 0x19010200
```c
aic /> hexdump1 0x19010200 256 1
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000:00 00 00 0F 00 00 00 00 00 00 00 0F 00 00 00 00 ................
00000010:C8 81 C6 B8 2C 3E 20 20 03 0C 03 45 02 00 30 04 ....,> ...E..0.
00000020:00 00 00 00 21 00 00 00 0D 02 00 3F A8 65 5A 00 ....!......?.eZ.
00000030:00 00 00 00 D6 17 7D 2E 00 00 00 00 00 00 00 00 ......}.........
00000040:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000a0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000b0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000c0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000d0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000e0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000f0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
```
我们compare 下这两组数据,比较结果也是一致的符合预期值。
efuse 的读写控制流程如下:
写流程:
> 1.检查eFuse控制寄存器(EFUSE_CTL)状态位(EFUSE_WRITE_START/EFUSE_READ_START/EFUSE_AUTO_STATUS),若为非空闲则等待,若都为空闲则继续步骤2;
> 2.配置要烧写eFuse地址到eFuse地址寄存器(EFUSE_ADDR);
3.配置要烧写eFuse数据到eFuse写数据寄存器(EFUSE_WDATA);
4.配置eFuse控制寄存器(EFUSE_CTL)启动烧写流程,EFUSE_WRITE_START和EFUSE_OP_CODE都需要配置;
5.等待EFUSE_WRITE_START自动清零,硬件自动关闭LDO。
读取流程:
> 1.检查eFuse控制寄存器(EFUSE_CTL)状态位(EFUSE_WRITE_START/EFUSE_READ_START/EFUSE_AUTO_STATUS),若为非空闲则等待,若都为空闲则继续步骤2;
2.配置要读取eFuse地址到eFuse地址寄存器(EFUSE_ADDR);
3.配置eFuse控制寄存器(EFUSE_CTL)启动读取流程,EFUSE_READ_START和EFUSE_OP_CODE都需要配置;
4.等待EFUSE_READ_START自动清零,读取eFuse读数据寄存器(EFUSE_RDATA)获得eFuse的值。
我们对用户自定义的0xc0~0xff 区间的0xc0 地址空间写入01测试 写入功能,然后dump数据发现0x01 已经被写入
写入01后我们再次写入00 发现dump 回来的数据还是1,因为fuse 是只能写入一次的。
对写入的数据还是需要谨慎的,一旦被写入后是无法再次被修改的。
从以下efuse 的初始化流程说明可知,修改完fuse 后SID的SRAM 的数据和efuse 的数据是不一致的,触发读取操作后会更先SRAM数据
> 每次系统上电复位后,SID会自动读取所有eFuse值到SRAM,软件只需要读取对应地址的值即可。 注意第一次烧写后,SRAM值不会自动更新,此时可通过软件读操作将对应地址的值读到寄存器。
我们对c0 地址更新为010310 ,然后读取SRAM 数据和efuse 数据进行比较查看是否是不一致的,对0xc0 更新后,读取SRAM数据发现并没有更新为写入数据跟上述说明一致。再此基础上我们使用dump 命令读取efuse 数据在dump SRAM数据,理论上此时0xc0会被更新为03,按照此流程进行验证运行结果如下,和上述说明保持一致。
- 2024-08-27
-
回复了主题帖:
RISC-V 手册(中文,一本开源指令集的指南)
下来看看