跳到主要内容

示例项目

ZStudio 中内置了部分示例项目,用户可以直接从主菜单导入并跟随示例项目熟悉 ZStudio 基本使用流程及各项功能。

matrix_cmplx_mult

本节将用 matrix_cmplx_mult 示例项目演示如何用 ZIGen 创建自定义指令集,并应用于虚拟 SoC 模型,使用 ZProf 进行性能分析与设计优化。

打开 matrix_cmplx_mult 解决方案

用户可以遵循以下步骤在 ZSolution 中打开示例项目:

  1. 在菜单栏选择"文件" > "从示例解决方案开始",在下拉框中选择 matrix_cmplx_mult 并打开。

  2. base 项目设置为当前活动项目,选择 Release 配置和 RISC-V 架构,点击"构建",并在构建控制台中确认构建输出。

    构建 *base* 项目
  3. 成功构建后,需要指定执行性能分析的目标虚拟开发板,将 base 项目设置为当前活动项目,点击菜单栏 "性能分析" > "配置"。

    • 选择开发板:在下拉框中选择"内置"
    • 仿真器虚拟开发板:在下拉框中选择"RocketChip"作为目标虚拟开发板。
    更改性能分析配置
  4. base 项目设置为当前活动项目,点击菜单栏 "性能分析" > "启动性能分析"生成该项目的性能分析数据,性能分析完成之前不要重新执行该行为,以免生成错误数据。性能分析完成后,结果将在编辑区打开。

    性能分析结果

接下来,我们将创建自定义指令并将其应用于 base 项目的源码中从而提升其在 RocketChip 虚拟开发板上的表现。

创建自定义指令集

用户可以从下拉菜单中选择 ZExplorer 切换模式。

进入 ZIGen 视图

用户可以使用我们准备好的文件进行一键导入,单击 more-action-button (更多操作..) > import-extension-button (导入指令集) 打开导入指令集弹窗,在文件系统中找到位于 /home/{user_name}/ZStudio_Workspace/Solutions/matrix_cnmplx_mult/Extension 文件夹下的 .dspmult.zsext 文件并打开。

此外,也可以遵循以下步骤熟悉指令创建过程:

点击 new-extension-button (新建指令集) 按钮创建 dspmult 自定义指令集。

添加 zkmxda 自定义指令

  1. 右键 dspmult 指令集节点,在上下文菜单中选择"新建指令",为新建指令命名为 zkmxda 然后点击"创建"。

  2. 进入指令编辑页面,并为自定义指令指定配置选项。

    • 功能单元:从下拉框中选择 Simd 功能单元,参考功能单元支持表

    • 长度:从下拉框中选择 32 位指令长度。

    • 架构:从下拉框中选择 RV32&RV64 处理器架构。

    • 汇编语言: 自定义指令的汇编语言表示,即自定义指令的助记符,将根据用户定义的指令格式自动生成。

    指定 <em>zkmxda</em> 配置
  3. 添加自定义指令格式

    通过编辑编码字段定义指令 zkmxda 的格式,在弹窗中分配可用的编码空间。

    为自定义指令 *zkmxda* 添加编码字段
    • 起始索引 & 字段位宽: 控制编码具体位数和位置。
    • 字段类型 & 字段值: 选择字段类型并填入字段值。

    用户可以在编码表中检查自定义指令的格式,右键编码字段中的行,可以编辑或删除该行的字段,直到确定指令格式。

    <em>zkmxda</em> 的指令编码
  4. 在指令行为代码编辑区中描述自定义指令的执行流程。

    for (uint32_t i = 0; i < XLen / 32; ++i) {
    int16_t r1 = getHalf(rs1, 2 * i);
    int16_t r2 = getHalf(rs2, 2 * i);
    int32_t tmp = 0x80008000;
    if (getWord(rs1, i) != tmp || getWord(rs2, i) != tmp) {
    int32_t res = sext<16>(getHalf(rs1, 1 + 2 * i)) * sext<16>(r2) +
    sext<16>(r1) * sext<16>(getHalf(rs2, 1 + 2 * i));
    setWord(rd, i, res);
    } else {
    setWord(rd, i, 0x7fffffff);
    setVxsat(DI);
    }
    }

自定义指令 zkmxda 的编码及行为如下图所示:

自定义指令 <em>zkmxda</em>

添加 zsmdrs 自定义指令

  1. 右键 dspmult 指令集节点,创建另一条自定义指令 zsmdrs

  2. 进入指令编辑页面,为自定义指令 zsmdrs 指定与 zkmxda 完全一致的配置。

  3. 定义 zsmdrs 的编码格式并描述其执行流程。

    for (uint32_t i = 0; i < XLen / 32; ++i) {
    int64_t Tmp = (int16_t)getHalf(rs1, 2 * i) * (int16_t)getHalf(rs2, 2 * i) -
    (int16_t)getHalf(rs1, 1 + 2 * i) * (int16_t)getHalf(rs2, 1 + 2 * i);
    setWord(rd, i, Tmp);
    }

自定义指令 zsmdrs 的编码及行为如下图所示:

自定义指令 <em>zsmdrs</em>

生成动态库

右键 dspmult 指令集并选择"生成动态库",在右下角查看动态库生成进度并等待"指令扩展 dspmult 已成功生成动态库。" 的消息提示。生成的动态库将显示在动态库导航下。

生成动态库

编译器和仿真器需要调用的动态库将保存在文件系统中的 Home/{user_name}/ZStudio_Workspace/lib 目录下;程序所需包含的宏定义将保存在文件系统中的 Home/{user_name}/ZStudio_Workspace/include 目录下。

使用自定义指令集

尝试将自定义指令集应用于内置虚拟开发板和项目中。

使用自定义指令集编写程序

在下拉菜单中选择 ZSolution 切换模式,并尝试将自定义指令集 dspmult 用于新建项目 custom 中。这里我们已经在 matrix_cmplx_mult 解决方案中准备好了 dspmult 项目。

进入 ZSolution 并创建新项目 *custom*
  1. custom 项目中使用自定义指令集。

    • custom_mat_cmplx_mult_q15.c 中包含自定义指令集生成的头文件。

      包含头文件
    • base_mat_cmplx_mult_q15.c 的基础上使用自定义指令编写程序 custom_mat_cmplx_mult_q15.c

      #include <stddef.h>
      #include <stdint.h>
      #include <stdio.h>
      #include <sys/_stdint.h>

      #include "input1_q15.h"
      #include "input2_q15.h"
      #include "xdspmult.h"

      #define TEST_M 10
      #define TEST_K 10
      #define TEST_N 10

      typedef int16_t q15_t;
      typedef int64_t q33_30_t;

      __attribute__((noinline)) static int32_t __SSAT(int32_t aValue, uint32_t aBits)
      {
      if (aBits >= 1U && aBits <= 32U)
      {
      int32_t max = (1 << (aBits - 1U)) - 1;
      int32_t min = -1 - max;

      if (aValue > max)
      return max;
      // aValue = max;
      else if (aValue < min)
      return min;
      // aValue = min;
      }

      return aValue;
      }

      __attribute__((noinline)) void tpt_mat_cmplx_mult_q15(q15_t *aOutMat, const q15_t *aInMatA,
      const q15_t *aInMatB, uint16_t aM, uint16_t aK,
      uint16_t aN)
      {
      for (uint16_t row = 0; row < aM; ++row)
      {
      for (uint16_t col = 0; col < aN; ++col)
      {
      q33_30_t sum_r = 0;
      q33_30_t sum_i = 0;
      const q15_t *p_in1 = &aInMatA[2 * ((uint32_t)row * aK)];
      const q15_t *p_in2 = &aInMatB[2 * col];

      for (uint16_t i = 0; i < aK; ++i)
      {
      int32_t a = ((int32_t *)p_in1)[0];
      int32_t b = ((int32_t *)p_in2)[0];
      int32_t tmp;

      _ZSMDRS(tmp, a, b);
      sum_r += tmp;
      _ZKMXDA(tmp, a, b);
      sum_i += tmp;

      p_in1 += 2;
      p_in2 += 2 * aN;
      }

      *aOutMat++ = __SSAT(sum_r >> 15U, 16);
      *aOutMat++ = __SSAT(sum_i >> 15U, 16);
      }
      }
      }

      #ifdef DUMP
      #define dump(data, n) \
      for (int i = 0; i < (n); ++i) \
      printf("%x \n", (data)[i]);
      #else
      #define dump(data, n) do {} while (0)
      #endif // DUMP

      static q15_t output_q15[TEST_M * TEST_N * 2];

      #define test_mat_cmplx_mult_q15(out, in1, in2, rows, k, cols) \
      tpt_mat_cmplx_mult_q15(out, in1, in2, rows, k, cols)

      int main(void)
      {
      test_mat_cmplx_mult_q15(output_q15, input1_q15, input2_q15, TEST_M, TEST_K,
      TEST_N);
      dump(output_q15, 20);
      return 0;
      }
      使用自定义指令编写程序
  2. 更改 custom 项目的构建配置

    点击 gear-button(构建配置)按钮,在构建配置中指定 Arch, Arch 格式为 {standard_feature}_x{custom_extension}, 其中 x 是添加自定义指令集的前缀,RISC-V 架构支持的 multilibs 参考 ZIGen 使用指南。如下图所示,在此示例中,请指定 Arch 参数为 rv32imafdc_xdspmult

    在构建配置中指定 Arch

    当用户在构建配置中指定架构(Arch)后构建项目,无需单独指定头文件路径,头文件将被包含在代码中,自定义指令集生成的动态库将链接到可执行文件中。

  3. 构建 cutom 项目并检查构建结果

    点击项目快捷控制中的 build-button 按钮并在构建控制台中检查构建输出。

    构建 <em>custom</em> 项目
  4. 反汇编 custom 以检查程序中的自定义指令

    文件导航中右键可执行文件 custom 并选择"反汇编",打开反汇编结果以检查程序中的自定义指令是否正确执行。

    反汇编 <em>custom</em> 检查自定义指令
  5. 运行项目并检查运行结果

    点击项目快捷控制中的 run-button 按钮并在运行控制台中检查运行输出。

    执行 *custom* 项目

将自定义指令应用于虚拟 SoC 模型

从下拉菜单中选择并返回 ZExplorer 模式,然后从活动栏中切换至 ZVB 视图。

进入 ZVB 视图

在虚拟开发板导航中右键点击内置 RocketChip 虚拟开发板选择"拷贝",并另存为 CusRocketChip。用户需要定义 CPU 内部执行单元运行的指令类型,ZVB 支持使用用户在 ZIGen 中自定义的指令集。

  1. 右键单击 CPU 类型的组件 CusRocketChip 然后选择"打开下层",双击 EXU 组件以打开其属性编辑面板。

    进入 CPU 层级
  2. 在属性栏点击add-button 增加 Op List 1 表单。

  3. 从下拉框中选择"Op Desc"并为 EXU 组件指定"Builtin"功能单元。

    Builtin 包含除 Cus0/1/2/3/4 以外所有功能单元类型,参考功能单元支持表

    提示

    如果为自定义指令指定的功能单元为 Cus0/Cus1/Cus2/Cus3/Cus4,则需要重复上述步骤增加 Op List 表单并指定为 Cus0/Cus1/Cus2/Cus3/Cus4 功能单元。即给自定义指令指定的功能单元和为虚拟开发板 CPU 层级 EXU 组件指定的功能单元需要对应。

    为 EXU 组件指定执行单元
  4. 在 CusRocketChip 虚拟开发板上运行 ELF 文件

    右键单击空白区域,然后选择"关闭"。在虚拟开发板导航中右键点击"CusRocketChip",然后选择"运行 ELF 文件"。在弹窗中,指定可执行文件的路径 /ZStudio_Workspace/Solutions/matrix_cmplx_mult/custom/output/zcc/risc-v/release/build-output/custom ,并给仿真器 zemu 传递自定义指令生成的动态库参数 --zigen-plugin-path=/home/{user_name}/ZStudio_Workspace/lib

    指定可执行文件 *custom* 路径

    运行控制台 中检查运行输出。

    检查运行输出

性能分析及对比

用户可以通过性能分析对比查看自定义指令及对源码所做的更改如何影响 RocketChip 虚拟开发板上程序的性能。

custom 项目执行性能分析

用户可以遵循以下步骤为 custom 项目执行性能分析:

  1. custom 项目设置为当前活动项目,,点击菜单栏 "性能分析" > "配置"。

    • 选择开发板:在下拉框中选择"自定义"
    • 开发板配置文件:指定 CusRocketChip.zvb 文件路径,虚拟开发板文件一般存放于 home\ ZStudio_Workspace\VirtualBoards 目录下。
    指定 *custom* 项目的性能分析配置
  2. custom 项目设置为当前活动项目,点击菜单栏 "性能分析" > "启动性能分析"生成该项目的性能分析数据,性能分析完成之前不要重新执行该行为,以免生成错误数据。完成后右下角会生成提示信息。

    对 <em>custom</em> 项目执行性能分析

性能分析对比

用户可以遵循以下步骤利用对比工具直观比较性能分析结果:

  1. 在主菜单中,点击"视图" > "性能分析对比" 调出对比工具。

  2. 在"性能分析对比"视图的左侧栏中,右键 base 项目的性能分析结果,并点击"选为对比基准"。

    /docs-images/zstudio/images/image-set-base-project-as-base-data.png
    base 项目设置为对比基准
  3. 点击 comparison-configuration-button 按钮打开设置窗口,调整完设置后,点击 "确定" 关闭窗口。

    性能分析设置
    • 选择"程序性能指标对比"的数据可视化形式:至少选择图或表格中的一种形式。
    • 选择对比项:默认对比项为"指令总数","周期总数"以及"单位周期执行的指令数",用户可自行选择对比项。
  4. 用户可以按住 ShiftCtrl 键同时选择两个结果,右击其中一个,然后选择"对比"。

    选择*base*和*custom*性能分析结果进行对比
  5. 性能分析对比视图将在编辑器中打开。

    性能分析对比视图

程序性能表根据基准数据显示指标的变化;颜色代表变化方向,绿色表示相对于基准数据减少,而红色表示增加。custom 项目指令数和周期数的减少以及 IPC 的增加表明自定义指令提高了整体性能和效率。

add

本节将用 add 示例项目演示如何用 ZIGen 创建自定义指令集,并使用自定义指令编写程序。

创建自定义指令集

从下拉菜单中选择并返回 ZExplorer 模式,然后从活动栏中切换至 ZIGen 视图。

进入 ZIGen 视图

用户可以使用我们准备好的文件进行一键导入,单击 more-action-button (更多操作..) > import-extension-button (导入指令集) 打开导入指令集弹窗,在文件系统中找到 .zsext 文件并打开。singularadd.zsext 位于 /home/{user_name}/ZStudio_Workspace/Solutions/add/Extension 文件夹下。

此外,也可以遵循以下步骤熟悉指令创建过程:

  1. 点击 new-extension-button (新建指令集) 创建指令集 singularadd

  2. 右键指令集节点,在上下文菜单中选择"新建指令",为新建指令命名为 singular_add 然后点击"创建"。

  3. 进入指令编辑页面,并为 singular_add 指定配置选项。

    • 功能单元:从下拉框中选择整数运算单元 IntAlu
    • 长度:从下拉框中选择指令的长为 32 位。
    • 架构:从下拉框中选择 RV32/RV64 处理器架构。
    • 汇编语言: 自定义指令的汇编语言表示,即自定义指令的助记符,将根据用户定义的指令格式自动生成。
  4. 通过编辑编码字段定义指令的格式,在弹窗中分配可用的编码空间。

    定义指令*singular_add*格式
    • 起始索引 & 字段位宽: 控制编码具体位数和位置。
    • 字段类型 & 字段值: 参考下方表格,选择字段类型并填入字段值。

    自定义指令 singular_add 的编码格式将如下图所示。用户可以在编码表中检查自定义指令的格式,右键编码字段中的行,可以编辑或删除该行的字段,直到确定指令格式。

    自定义指令*singular_add*的编码格式
  5. 在指令行为代码编辑区中描述自定义指令的执行流程等。

    rd = rs1 + rs2;
    定义 *singular_add* 指令的行为

生成动态库

右键指令集 singularadd 并选择"生成动态库",在右下角查看动态库生成进度并等待"指令扩展singularadd已成功生成动态库。" 的消息提示。生成的动态库将显示在动态库导航下。

生成动态库

编译器和仿真器需要调用的动态库将保存在文件系统中的 Home/{user_name}/ZStudio_Workspace/lib 目录下;程序所需包含的宏定义将保存在文件系统中的 Home/{user_name}/ZStudio_Workspace/include 目录下。

使用自定义指令集

在下拉菜单中选择 ZSolution 切换模式,新建项目并尝试将自定义指令集 singularadd 用于新建项目中。用户可以使用我们准备好的项目进行一键导入,单击"文件">"从示例项目开始"弹窗,在下拉框中选择 add 项目并打开。

进入 ZSolution 并创建新项目

此外,也可以遵循以下步骤自己创建项目和解决方案:

  1. 在解决方案和项目中使用自定义指令集

    • main.c 中包含自定义指令集的头文件。
    • main.c 中使用需要的自定义指令。
    #include "xsingularadd.h"

    #include <stdio.h>

    int main() {
    int Num0 = 2;
    int Num1 = 5;
    int Res;

    _SINGULAR_ADD(Res, Num0, Num1);
    printf("singular_add res: %d\n", Res);

    return 0;
    }
    包含头文件并使用自定义指令*singular_add*编写程序
  2. 更改构建配置

    点击 gear-button(构建配置)按钮,在构建配置中指定 Arch 。Arch 格式为 {standard_feature}_x{custom_extension},其中 x 是添加自定义指令集的前缀,RISC-V 架构支持的 multilibs 参考 ZIGen 使用指南。如下图所示,在此示例中,请指定 Arch 参数为 rv64imafdc_xsingularadd

    在构建配置中指定 Arch

    当用户在构建配置中指定架构(Arch)后构建项目,无需单独指定头文件路径,头文件将被包含在代码中,自定义指令集生成的动态库将链接到可执行文件中。

  3. 点击项目快捷控制中的 build-button 按钮并在构建控制台中检查构建输出。

    构建 *add* 项目
  4. 点击项目快捷控制中的 run-button 按钮并在运行控制台中检查运行输出。

    运行 *add* 项目