当前位置: 首页 > news >正文

1.6 基于NICE接口的存储器访问通道扩展自定义指令的简单示例

一、指令扩展单元

我们的上一篇文章基于NICE接口扩展自定义指令的简化版示例简单介绍了基于蜂鸟E203进行自定义指令扩展的示例,该示例仅虽然足够简单易懂,但不够完备,因为NICE接口除了指令请求与反馈通道,还包括存储器访问通道,本文将提供一个新的示例。

我们的示例仅有不到200行代码,实现了将两个数组逐元素相乘,并保存回第一个数组的地址中。数组的读写均基于NICE接口的存储器访问通道进行连续访问,具有较高的访问效率。具体的代码如下,代码的核心内容可以分为状态控制、存储器访问请求和存储器访问响应三个部分。

`include "e203_defines.v"`ifdef E203_HAS_NICE  //{
module e203_subsys_nice_core (input  nice_clk,input  nice_rst_n,output nice_active,output nice_mem_holdup,input                   nice_req_valid,output                  nice_req_ready,input  [`E203_XLEN-1:0] nice_req_inst,input  [`E203_XLEN-1:0] nice_req_rs1,input  [`E203_XLEN-1:0] nice_req_rs2,output                  nice_rsp_valid,input                   nice_rsp_ready,output [`E203_XLEN-1:0] nice_rsp_rdat,output                  nice_rsp_err,output                       nice_icb_cmd_valid,input                        nice_icb_cmd_ready,output [`E203_ADDR_SIZE-1:0] nice_icb_cmd_addr,output                       nice_icb_cmd_read,output [     `E203_XLEN-1:0] nice_icb_cmd_wdata,output [                1:0] nice_icb_cmd_size,input                        nice_icb_rsp_valid,output                       nice_icb_rsp_ready,input  [     `E203_XLEN-1:0] nice_icb_rsp_rdata,input                        nice_icb_rsp_err
);localparam IDLE = 0;localparam READ_ARRAY1 = 1;localparam READ_ARRAY2 = 2;localparam WRITE_ARRAY1 = 3;localparam RESPONSE = 4;localparam WAIT = 5;localparam ARRAY_LEN = 10;integer i;reg [2:0] state, state_icb_cmd, state_icb_rsp;reg [`E203_ADDR_SIZE-1:0] array1_addr;reg [`E203_ADDR_SIZE-1:0] array2_addr;reg [`E203_XLEN-1:0] data_buf[0:ARRAY_LEN-1];reg [3:0] counter_icb_cmd, counter_icb_rsp;wire [`E203_ADDR_SIZE-1:0] base_addr = (state_icb_cmd==READ_ARRAY2) ? array2_addr : array1_addr;wire [6:0] opcode = {7{nice_req_valid}} & nice_req_inst[6:0];wire [2:0] rv32_func3 = {3{nice_req_valid}} & nice_req_inst[14:12];wire [6:0] rv32_func7 = {7{nice_req_valid}} & nice_req_inst[31:25];wire opcode_custom0 = (opcode == 7'b0001011);wire rv32_func3_011 = (rv32_func3 == 3'b011);wire rv32_func7_0000000 = (rv32_func7 == 7'b0000000);wire ins_is_multiply = opcode_custom0 && rv32_func3_011 && rv32_func7_0000000;always @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) state <= IDLE;else begincase (state)IDLE: if (ins_is_multiply && nice_req_ready) state <= WAIT;WAIT: if (state_icb_rsp == IDLE) state <= RESPONSE;RESPONSE: if (nice_rsp_valid && nice_rsp_ready) state <= IDLE;default: state <= IDLE;endcaseendendalways @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) state_icb_cmd <= IDLE;else begincase (state_icb_cmd)IDLE: if (ins_is_multiply && nice_req_ready) state_icb_cmd <= READ_ARRAY1;READ_ARRAY1: beginif (nice_icb_cmd_valid && nice_icb_cmd_ready) beginif (counter_icb_cmd == ARRAY_LEN - 1) state_icb_cmd <= READ_ARRAY2;endendREAD_ARRAY2: beginif (nice_icb_cmd_valid && nice_icb_cmd_ready) beginif (counter_icb_cmd == ARRAY_LEN - 1) state_icb_cmd <= WAIT;endendWAIT: if (state_icb_rsp == WRITE_ARRAY1) state_icb_cmd <= WRITE_ARRAY1;WRITE_ARRAY1: beginif (nice_icb_cmd_valid && nice_icb_cmd_ready) beginif (counter_icb_cmd == ARRAY_LEN - 1) state_icb_cmd <= IDLE;endenddefault: state_icb_cmd <= IDLE;endcaseendendalways @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) counter_icb_cmd <= 4'd0;else beginif (state_icb_cmd == IDLE) counter_icb_cmd <= 4'd0;else if (nice_icb_cmd_valid && nice_icb_cmd_ready) begincounter_icb_cmd <= counter_icb_cmd + 1'b1;if (counter_icb_cmd == ARRAY_LEN - 1) counter_icb_cmd <= 4'd0;endendendalways @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) beginarray1_addr <= {`E203_ADDR_SIZE{1'b0}};array2_addr <= {`E203_ADDR_SIZE{1'b0}};end else if (state == IDLE && ins_is_multiply) beginarray1_addr <= nice_req_rs1;array2_addr <= nice_req_rs2;endendalways @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) beginstate_icb_rsp <= IDLE;end else begincase (state_icb_rsp)IDLE: if (ins_is_multiply && nice_req_ready) state_icb_rsp <= READ_ARRAY1;READ_ARRAY1: beginif (nice_icb_rsp_valid && nice_icb_rsp_ready) beginif (counter_icb_rsp == ARRAY_LEN - 1) state_icb_rsp <= READ_ARRAY2;endendREAD_ARRAY2: beginif (nice_icb_rsp_valid && nice_icb_rsp_ready) beginif (counter_icb_rsp == ARRAY_LEN - 1) state_icb_rsp <= WRITE_ARRAY1;endendWRITE_ARRAY1: beginif (nice_icb_rsp_valid && nice_icb_rsp_ready) beginif (counter_icb_rsp == ARRAY_LEN - 1) state_icb_rsp <= IDLE;endenddefault: state_icb_rsp <= IDLE;endcaseendendalways @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) counter_icb_rsp <= 4'd0;else beginif (state_icb_rsp == IDLE) counter_icb_rsp <= 4'd0;else if (nice_icb_rsp_valid && nice_icb_rsp_ready) begincounter_icb_rsp <= counter_icb_rsp + 1'b1;if (counter_icb_rsp == ARRAY_LEN - 1) counter_icb_rsp <= 4'd0;endendendalways @(posedge nice_clk, negedge nice_rst_n) beginif (!nice_rst_n) beginfor (i = 0; i < ARRAY_LEN; i = i + 1) begindata_buf[i] <= {`E203_XLEN{1'b0}};endend else beginif (state_icb_rsp == READ_ARRAY1) beginif (nice_icb_rsp_valid && nice_icb_rsp_ready) begindata_buf[ARRAY_LEN-1] <= nice_icb_rsp_rdata;for (i = 0; i < ARRAY_LEN - 1; i = i + 1) begindata_buf[i] <= data_buf[i+1];endendend else if (state_icb_rsp == READ_ARRAY2) beginif (nice_icb_rsp_valid && nice_icb_rsp_ready) begindata_buf[ARRAY_LEN-1] <= data_buf[0] * nice_icb_rsp_rdata;for (i = 0; i < ARRAY_LEN - 1; i = i + 1) begindata_buf[i] <= data_buf[i+1];endendend else if (state_icb_cmd == WRITE_ARRAY1) beginif (nice_icb_cmd_valid && nice_icb_cmd_ready) beginfor (i = 0; i < ARRAY_LEN - 1; i = i + 1) begindata_buf[i] <= data_buf[i+1];endendendendendassign nice_req_ready = (state == IDLE);assign nice_rsp_valid = (state == RESPONSE);assign nice_rsp_rdat = {`E203_XLEN{1'b0}};assign nice_rsp_err = 1'b0;assign nice_icb_cmd_valid = ((state_icb_cmd != IDLE) && (state_icb_cmd != WAIT));assign nice_icb_cmd_addr = base_addr + {counter_icb_cmd, 2'b00};assign nice_icb_cmd_read = (state_icb_cmd != WRITE_ARRAY1);assign nice_icb_cmd_wdata = data_buf[0];assign nice_icb_cmd_size = 2'b10;assign nice_icb_rsp_ready = 1'b1;assign nice_mem_holdup = ins_is_multiply || (state != IDLE);assign nice_active = ins_is_multiply || (state != IDLE);
endmodule
`endif  //}

1. 状态控制

我们设计的指令扩展单元采用多状态机协同工作的架构设计,通过精细的状态转换实现对数组乘法运算的全流程控制。该设计包含三个主要状态机:主状态机(state)、存储器访问请求状态机(state_icb_cmd)和存储器访问响应状态机(state_icb_rsp),它们分别负责总体流程控制、存储器访问请求生成和响应处理。这种分离设计提高了模块的并行处理能力,使存储器访问与数据处理可以重叠进行,显著提升了系统性能。

主状态机仅包括IDLE、WAIT和RESPONSE状态,主状态机不设置太复杂的控制,可以提高系统的可靠性和稳定性。系统复位后主状态机进入IDLE状态,IDLE状态等待有效指令到达,当检测到自定义乘法指令时进入WAIT状态。这种设计确保了指令识别的实时性,同时通过WAIT状态实现了与子状态机的同步协调。状态转换完全遵循握手协议,仅在nice_req_ready信号有效时才会离开IDLE状态,保证了与处理器核心的协同工作可靠性。

存储器访问请求状态机负责管理存储器访问的请求过程,其状态转换与存储器访问阶段严格对应。该状态机采用流水线式设计,在READ_ARRAY1状态下完成第一个数组的读取请求发送后,立即进入READ_ARRAY2状态处理第二个数组,最后转入WRITE_ARRAY1状态回写结果。每个状态的转换都通过counter_icb_cmd计数器精确控制,确保处理完ARRAY_LEN个元素后才切换状态,这种设计完美适配了批量数据处理的特性。

响应状态机与请求状态机保持松耦合的同步关系,通过独立的控制逻辑处理存储器返回数据。该状态机同样包含READ_ARRAY1、READ_ARRAY2和WRITE_ARRAY1三个工作状态,但专注于响应主处理器返回的数据而非发起数据访问请求。当收到nice_icb_rsp_valid信号时,状态机根据当前状态将数据存入缓冲区或进行乘法运算。这种分离式设计使得存储器访问延迟不会阻塞新的访问请求发出,有效隐藏了存储器延迟,提升了整体吞吐量。

2. 存储器访问请求

存储器访问请求部分硬件电路是NICE扩展单元与系统存储子系统交互的关键接口,它通过精心设计的控制逻辑和地址生成机制实现高效的数据搬运。该部分电路根据state_icb_cmd状态机的当前状态,动态生成符合ICB总线协议的存储器访问请求,包括地址、读写控制、数据大小和写入数据等信号。nice_icb_cmd_valid信号的生成逻辑确保了只有在非IDLE和非WAIT状态才会发起有效请求,这种设计避免了无效请求对总线资源的占用。

地址生成机制采用基地址加偏移量的灵活设计,base_addr信号根据当前处理阶段在array1_addr和array2_addr之间自动切换。偏移量由counter_icb_cmd计数器左移两位产生,巧妙地实现了以字为单位的地址递增。这种设计不仅支持任意对齐的数组起始地址,还能通过简单修改计数器位宽来适配不同规模的数组处理需求。地址计算逻辑完全组合实现,确保了每个时钟周期都能生成最新地址,满足高性能处理的要求。

数据写入控制采用先进先出的缓冲区管理策略,data_buf数组作为核心数据暂存区,在READ_ARRAY2状态自动完成元素级乘法运算。当处于WRITE_ARRAY1状态时,缓冲区头部数据通过nice_icb_cmd_wdata端口输出,同时触发缓冲区数据前移操作。这种设计实现了读写操作的流水线处理,写入阶段可以连续发出请求而无需等待前次写入完成,最大限度地利用了存储器带宽。整个请求生成逻辑采用寄存器输出设计,所有控制信号都在时钟上升沿同步更新,确保了与总线时钟域的完美同步,避免了亚稳态问题的发生。

3. 存储器访问响应

存储器访问响应部分硬件电路负责接收、解析和利用ICB总线返回的数据,其设计重点在于数据通路的高效性和与命令阶段的并行性。该模块通过nice_icb_rsp_ready常高设计简化了流控逻辑,表明其始终准备接收响应数据,这种设计权衡了复杂度与性能,在保证功能正确的前提下最大化总线利用率。实际系统中,这种设计需要确保后端处理能力足以消化最大可能的数据返回速率。

数据缓冲区的管理采用移位寄存器式的设计,在READ_ARRAY1状态将返回数据存入缓冲区尾部,同时所有已有数据向前移动。这种设计巧妙地避免了复杂的地址计算,仅通过物理位移就实现了逻辑上的队列管理。当处理READ_ARRAY2状态时,模块自动将新到达数据与缓冲区头部数据相乘,实现了流水线式的元素级乘法运算。整个数据通路仅使用单端口RAM等效资源,却实现了类似双端口RAM的并发访问效果。

状态同步机制是响应模块的关键点,state_icb_rsp状态机与state_icb_cmd状态机保持松耦合但协调的工作节奏。两个状态机通过共同的counter_icb_rsp/counter_icb_cmd计数器保持阶段同步,但又各自独立响应总线事务的请求和响应侧。这种设计使得存储器访问延迟被有效隐藏,命令阶段可以持续发出新请求而不必等待先前请求的响应返回,极大地提高了总线利用率和整体处理吞吐量。

二、在Nuclei Studio IDE中运行软件代码

类似于前面的章节,我们需要先定义一个自定义指令接口函数,作为C语言与汇编语言的桥梁,具体如下面的代码所示。相比于上一个简单示例,该函数的输入为两个数组的地址,且无需返回数据。自定义指令中的xd位置为0,意味着不需要依靠指令反馈通道进行写回,因为我们直接使用存储器访问通过进行数据中具体数据的读写。代码中zero变量仅用于占位,没有实际意义。

#ifndef __CO_UNIT_INSN_H__
#define __CO_UNIT_INSN_H__#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>
#include <hbird_sdk_soc.h>__STATIC_FORCEINLINE void array_multiply(int* addr1, int* addr2)
{int zero = 0;asm volatile(".insn r 0x0b, 3, 0, %0, %1, %2" : "=r"(zero) : "r"(addr1), "r"(addr2));
}#ifdef __cplusplus
}
#endif#endif

之后我们在main.c源文件中调用这个函数进行测试,测试代码如下:

#include <stdio.h>
#include "co_unit_func.h"int main(void)
{int array1[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};int array2[] = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5};printf("Array1: ");for(int i = 0; i < 10; i++){printf("%d ", array1[i]);}printf("\nArray2: ");for(int i = 0; i < 10; i++){printf("%d ", array2[i]);}printf("\n");array_multiply(array1, array2);printf("Product: ");for(int i = 0; i < 10; i++){printf("%d ", array1[i]);}printf("\n");return 0;
}

之后我们点击Nuclie Studio的运行按钮,可以看到下面的打印结果,我们的自定义指令正常执行。

为了进一步查看性能提升效果,我们可以用下面的代码进行测试,该代码可以得到通过执行普通指令的数组乘法和基于自定义指令的数组乘法花费的时钟周期,并将其打印输出。从结果可以看到,我们的自定义指令花费的时钟周期数不足普通普通指令的十分之一。

#include <stdio.h>
#include "co_unit_func.h"int main(void)
{__RV_CSR_WRITE(CSR_MSTATUS, MSTATUS_XS);__enable_minstret_counter();int begin_cycle, end_cycle;int cycle_normal, cycle_nice;int array1[] = {100, 800, 700, 600, 500, 400, 300, 200, 100, 100};int array2[] = {100, 100, 200, 200, 300, 300, 400, 400, 500, 500};printf("Array1: ");for (int i = 0; i < 10; i++){printf("%d ", array1[i]);}printf("\nArray2: ");for (int i = 0; i < 10; i++){printf("%d ", array2[i]);}printf("\n");begin_cycle = __get_rv_cycle();for (int i = 0; i < 10; i++){array1[i] *= array2[i];}end_cycle = __get_rv_cycle();cycle_normal = end_cycle - begin_cycle;printf("Product: ");for (int i = 0; i < 10; i++){printf("%d ", array1[i]);}printf("\n");begin_cycle = __get_rv_cycle();array_multiply(array1, array2);end_cycle = __get_rv_cycle();cycle_nice = end_cycle - begin_cycle;printf("Product: ");for (int i = 0; i < 10; i++){printf("%d ", array1[i]);}printf("\n");printf("Normal multiply used %d cycles.\n", cycle_normal);printf("NICE multiply used %d cycles.\n", cycle_nice);return 0;
}

 需要注意的是,我们需要先关闭乘法指令和编译器优化才能得到上面的结果,如下图所示。

不过即使完全打开我们的设计也存在优势,只是优势有所下降,从十分之一变成了四分之一,如下图所示。

三、总结

 本文介绍了基于蜂鸟E203处理器的NICE接口扩展自定义指令的实现方法。通过多状态机协同设计,实现了两个数组的逐元素相乘运算,并利用存储器访问通道进行高效数据传输。示例代码不足200行,包含状态控制、存储器访问请求和响应三个核心模块。测试结果显示,该自定义指令的性能比常规指令提升十倍以上。文章详细阐述了硬件设计思路和软件调用方法,并提供了性能对比测试代码。该方案为RISC-V处理器自定义指令扩展提供了实用参考。

http://www.lqws.cn/news/577369.html

相关文章:

  • 大语言模型LLM在训练/推理时的padding
  • SQL参数化查询:防注入与计划缓存的双重优势
  • 衡石科技chatbot分析手册--钉钉数据问答机器人配置
  • 设计模式之外观模式
  • 【微服务】.Net中使用Consul实现服务高可用
  • 大语言模型微调的效能控制与评估策略
  • 提示技术系列——链式提示
  • 跨主机用 Docker Compose 部署 PostgreSQL + PostGIS 主从
  • 对象池模式:减少GC的Kotlin实战指南
  • 基于 SpringBoot+Vue.js+ElementUI 的 Cosplay 论坛设计与实现7000字论文
  • LeetCode 1456. 定长子串中元音的最大数目
  • MapReduce
  • EtherCAT主站教程4--IGH主站代码详解
  • 云手机的用途都有哪些?
  • Deep Mean-Shift Priors for Image Restoration论文阅读
  • mysql mvcc
  • Hadoop WordCount 程序实现与执行指南
  • Java 案例 6 - 数组篇(基础)
  • 第 89 场周赛:山脉数组的峰值索引、车队、考场就坐、相似度为 K 的字符串
  • 大语言模型(LLM)笔记
  • UE5 一台电脑+双显示器 配置nDisplay裸眼3D效果
  • 东芝TC78S600FNG在打印机中的应用:静音、防卡纸与能效
  • Python 数据分析与机器学习入门 (八):用 Scikit-Learn 跑通第一个机器学习模型
  • 智慧畜牧-猪场猪只行为状态检测数据集VOC+YOLO格式3790张15类别
  • Java中for与foreach
  • python+uniapp基于微信小程序的生鲜订购系统nodejs+java
  • 基于uniapp的老年皮肤健康管理微信小程序平台(源码+论文+部署+安装+售后)
  • JAVA八股文:异常有哪些种类,可以举几个例子吗?Throwable类有哪些常见方法?
  • HTML5 实现的圣诞主题网站源码,使用了 HTML5 和 CSS3 技术,界面美观、节日氛围浓厚。
  • 湖北理元理律师事务所债务解法:从法律技术到生活重建