查看: 1557|回复: 0

[评测分享] 【Telink B91通用开发套件】(五)RISC-V 存储加载指令学习

[复制链接]
  • TA的每日心情
    奋斗
    3 天前
  • 签到天数: 128 天

    连续签到: 1 天

    [LV.7]常住居民III

    发表于 2023-10-29 07:27:00 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 andeyqi 于 2023-10-29 21:13 编辑

    简介:

        一直想学习下risc-v 的指令集,虽然可以通过各种模拟器来学习了解,不过还是喜欢在开发板上学习验证,适此开发板评测机会正好可以实际硬件环境上学习验证下。


    1.RISC-V 通用寄存器


    RISC-V 体系结构提供32个通用整型寄存器(X0~X31),寄存器的长度则根据处理器的位数相关,64位处理器寄存器长度位64位,32位处理器则是32位,长度位XLENG根据处理器的长度一致,浮点数运行RISC-V处理器也提供32个浮点数通用寄存器f0~f31,寄存器描述如下图1.1和图1.2。

    图1.1.png

    图1.1


    图1.2.png

    图1.2

    • 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,在函数调用时传递参数和返回值


    2 RISC-V 指令集类型

       图 2.2 显示了六种基本指令格式,分别是:用于寄存器-寄存器操作的 R 类型指令,用于短立即数和访存 load 操作的 I 型指令,用于访存 store 操作的 S 型指令,用于条件跳转操作的B 类型指令,用于长立即数的U 型指令和用于无条件跳转的J 型指令。图 2.3 使用图2.2 的指令格式列出了图 2.1 中出现的所有RV32I 指令的操作码。

    图2.1.png
    图 2.1

    图2.2.png
    图2.2



    图2.3_.png

    图 2.3
    • 0~6 位为opcode字段,用于指令分类
    • rs1:表示源寄存器1,可以从通用寄存器x0~x31中选择
    • rs2:表示源寄存器2,可以从通用寄存器x0~x31中选择
    • rd:表示目标寄存器,可以从通用寄存器x0~x31中选择
    • imm:表示有符号立即数
    • func:表示功能码,有些指令的opcode 是相同的但是,但是他们的func是不同的从而来解析命令,参见图2.4



    图2.4.png
    图 2.4

    3.存储和加载指令
        RISC-V 的指令集架构是一种存储预加载的架构,存储与加载是基本的指令,RISC-V除了提供 32 位字(lw, sw)的加载和存储外,图 2.1 中说明, RV32I 支持加载有符号和无符号字节和半字(lb, lbu, lh, lhu)和存储字节和半字(sb, sh)。有符号字节和半字符号扩展为 32 位再写入目的寄存器。即使是自然数据类型更窄,低位宽数据也是被扩展后再处理,这使得**的整数计算指令能正确处理所有的 32 位。在文本和无符号整数中常用的无符号字节和半字,在写入目标寄存器之前都被无符号扩展到 32 位。


    3.1 加载指令

    加载指令格式如下:
    l{d|w|h|b} {u} rd, offset(rs1)
    其中相关的定义选项如下:
    • {d|w|h|b} 表示加载的数据宽度 d-64bit w-32bit h-16bit b-8bit
    • {u} 可选项,表示加载的数据为无符号数,即采用零扩展的方式,如果没有这个选项表示加载的数为有符号数,即采用有符号扩展
    • rd 表示目标寄存器
    • rs1 表示源寄存器1
    • (rs1) 表示以rs1寄存器的值为基地址进行寻址
    • offset 表示以源地址的寄存器的值为基地址的偏移量,offset 是12位有符号数,取值范围为[-2048,2047]


    如下为load 指令的支持的指令:


    load1.png

    图3.1.1

    3.2 存储指令

    存储指令格式如下:
    s {d|w|h|b} rs2,offset(rs1)
    其中相关的定义选项如下:
    • {d|w|h|b} 表示加载的数据宽度 d-64bit w-32bit h-16bit b-8bit
    • rs1 表示源寄存器1
    • (rs1) 表示以rs1寄存器的值为基地址进行寻址
    • rs2 表示源操作寄存器,用来表示源操作数
    • offset 表示以源地址的寄存器的值为基地址的偏移量,offset 是12位有符号数,取值范围为[-2048,2047]


    load 相关指令如下:
    sd1.png

    sd2.png

    sd3.png


    图3.2.1

    4 B91开发板验证

    4.1 lw 和 sw 指令的验证

      根据上面的描述可知,lw 可以加载32bit数据至寄存器,sw 可以存储32bit数据至目标地址,因为板子的jtag 反正没有搞定无法单步查看寄存器的状态,此实验会依赖shell 编写hexdump1/hexdump2 命令来dump 内存信息,hexwrite 写内存数据,此代码就不展开说明了,我们的B91 开发板上有128K的DRAM(0X00080000~0X000A0000),从map 可知大部分内存是没有被程序使用的,我们在0x00090000 为基地址验证,创建asm_test.S文件加入如下代码段验证。

    1. .align 3

    2. .global load_store_test
    3. load_store_test:
    4.         li t0, 0x90000
    5.         lw t1,(t0)
    6.         sw t1,4(t0)

    7.         ret
    复制代码
    如上代码会把0x90000 地址的数据复制到0x90004处,我们通过hexwrite 命令将0x90000处修改为0xdeadbeef的魔数复制到0x90004处。

    通过asm_test 命令执行上述汇编函数

    1. unsigned int asm_test(char argc,char ** argv)
    2. {
    3.         int cmd = 0;
    4.         cmd = atoi(argv[0]);
    5.         uint8_t data[20] = {0x00};

    6.         switch(cmd)
    7.         {
    8.         case 0:
    9.                 load_store_test();
    10.                 break;
    11.         default:
    12.                 break;
    13.         }

    14.         return 0;
    15. };
    复制代码
    验证结果如下跟我们预期的而一致更新了0x90004内存地址的内容为0xdeadbeef.


    sw_lw.png


    图4.1.1

    4.2 offset 取值范围编译验证

    按照load/store 命令的格式说明offset 的范围是取值范围为[-2048,2047],我们修改上面的汇编代码,验证下如果offset > 2047 || < -2048
    编译器是否会报错.

    1. .align 3

    2. .global load_store_test
    3. load_store_test:
    4.         li t0, 0x90000
    5.         lw t1,(t0)
    6.         sw t1,2048(t0)

    7.         ret
    复制代码
    offset 修改为2048 及 -2049 超过取值范围编译器会报错,在取值范围内则不会。
    2048.png

    图 4.2.1

    2049.png

    图4.2.2


    4.3 lb 和 sb 指令的验证lb/sb 指令分别是加载和存储一个字节操作,我们编写如下测试代码期待复制0x9000 的一个byte至0x9004地址,代码如下执行asmtest 1指令会触发执行如下代码:


    1. .global load_store_byte_test
    2. load_store_byte_test:
    3.         li t0, 0x90000
    4.         lb t1,(t0)
    5.         sb t1,4(t0)

    6.         ret
    复制代码

    从如下执行结果可知,已经复制了一个字节至0x90004,跟预期的一致,lh/sh的也是一样在此就不做验证了。

    load_byte.png

    图 4.3.1

    4.4 ld 和 sd 指令的验证

    文档上ld/sd 指令RV32和RV64都是支持的不过在B91开发板上会提示失败不支持的config。


    LD.png

    图4.4.1


    ld_failed.png

    图 4.4.2

    该问题后来查了下RISC-V官方的文档SD/LD指令是RV64才有的我们的RV32是不支持的,并不是编译器的问题,图4.4.1 应该是有问题的,如下是官方文档说明,该问题也算结案。
    ld_sd.png

    图 4.4.3


    4.5 load 指令的无符号扩展

    上面的load 指令描述时有个u选项代表了时否都加载的数进行无符号扩展,默认是进行有符号扩展,如下的指令1执行时会将t0对应的byte数据按照有符号的方式进行扩展至32bit,以数据为0x8a为例,最高位为1,因此在做符号扩展的时候会将高字节填充0xff,指令1 t1加载道的数据即为0xffffff8a,而指令2是不进行符号扩展,会对高字节的数据填充0x00,t1加载的数值为0x0000008a

    指令1:lb t1,(t0)
    指令2:lbu t1,(t0)

    我们编写如下测试代码按照上面的0x8a数值进行验证。
    1. .global load_store_byte_with_u_test
    2. load_store_byte_with_u_test:
    3.         li t0, 0x90000
    4.         lb t1,(t0)
    5.         sw t1,4(t0)

    6.         ret
    复制代码

    此时如果设置0x9000地址的内容为0x8a,0x90004的地址将被更新为0xffffff8a,验证结果如下,跟预期的一致。
    lb.png

    图4.5.1


    更改测试代码如下:

    1. .global load_store_byte_with_u_test
    2. load_store_byte_with_u_test:
    3. li t0, 0x90000
    4. lbu t1,(t0)
    5. sw t1,4(t0)

    6. ret
    复制代码
    此时如果设置0x9000地址的内容为0x8a,0x90004的地址将被更新为0x0000008a,验证结果如下,跟预期的一致。
    lbu.png

    图4.5.2


    基本的load 和 stroe 指令就先学习到这,后续有时间在逐步熟悉RISC-V指令集的其他指令。


    ==================资料分割线==================
    代码和官方下载的资料已上传至如下git路径



























    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-5-20 18:22 , Processed in 0.113945 second(s), 17 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.