前言
本文是为数电课编写的实验指导,介绍了如何为 Wujian100 添加一个简单的外设。本文主要参考了视频,添加了一个三色灯控制器。
- 项目代码:wujian100_basys3
 - 指导视频:在Basys3上开发Wujian100 RISC-V SoC
 - wujian100 相关文章:哈猪猪的博客
 
实验目的
- 熟悉基本的软硬件开发流程。
 - 学习 Block Design,给 wujian100 SoC 添加外设。
 
实验器材
Basys3, Vivado 2022.2
实验原理
wujian100 SoC 概述
Wujian100 是阿里平头哥自主研发的 RISC-V SOC。其系统结构层次如下图所示。(其中有删除线的代表在移植过程中被裁剪的模块,虚线框的代表我们将会在实验一二中添加的模块)

Wujian100 SoC 基于玄铁 E902,是一块极低功耗、极低成本的嵌入式 CPU 核,非常适用于边缘智能场景。E902 采用 RISC-V RV32E[M]C 指令集架构,并支持部分平头哥自研拓展指令。
Wujian100 SoC 采用哈佛架构,拥有一块 64KB 的指令 SRAM 以及三块大小均为 64KB 的数据 SRAM。如果软件程序不是很大,在实现时我们可以通过删去部分 DRAM 来节省 FPGA 资源。
Wujian100 SoC 的系统总线支持 AMBA3.0 AHB-Lite 协议。AHB 总线协议主要用于高性能、高时钟频率的系统结构中。E902 实现了 AHB-Lite 协议中的部分内容。系统总线连接了 CPU,DMA 以及 SRAM 等核心部件,还连接了很多没有内部逻辑的 Dummy 模块。SoC 预留了这些 Dummy 模块的接口以及地址,方便用户自己设计支持 AHB 协议的模块替换 Dummy 以实现功能拓展。由各个 Dummy 模块名可知,用户可以设计主设备、从设备以及指令、数据存储器,连接到系统总线上。
系统总线通过同步桥连接到低速 AHB 总线上,后者再转接到低速 APB 总线并连接各个 APB 外设上:脉宽调制器(PWM)、定时器(TIM)、串行接口模块(USI)等。其中 USI 兼容支持 USART,SPI 以及 I2C 三种串行总线协议。在 SoC 文档 doc\wujian100_open Userguide v1.0.docx 中我们可以了解到 USI 的复用方式和对应信号名。此外,在低速总线上也预留了很多 Dummy 模块以供拓展。
wujian100 soc 的源码可以通过以下方法获取:
git clone https://github.com/T-head-Semi/wujian100_open
为了方便同学们实验,我们已经帮大家完成了 wujian100 到 Basys3 的移植,并且裁剪了部分模块以适应 Basys3 的硬件资源。同学们可以通过北大网盘链接下载得到工程文件,工程结构详见 readme。
我们主要完成了以下工作,需要同学们注意:
- 删除了 DMA 模块。删除了第三个 DATA SRAM,其地址空间为:0x2002_0000~0x2002_FFFF。因此,修改了 CDK 项目链接脚本 
sdk\board\wujian100_open_evb\gcc_csky.ld的地址空间。 - 修改了约束文件:映射了 GPIO,USART,JTAG,RST_N 等信号。请仔细阅读约束文件。其中系统的低有效复位键映射到了拨码开关 SW15 上,SW15 断开时系统复位。
 - 添加了 pll 分频器,将板载 100MHz 时钟分频为 20MHz,以适用于 wujian100 SoC。
 - 编写了 tcl 脚本,用于忽略综合和比特流生成时的警告。
 
外设添加
Wujian100 SoC 为我们预留了很多 dummy 模块,观察它们的源码可以看到它们只有基础的 AHB-Lite 总线端口定义,没有内部逻辑。我们可以设计自己的模块,再通过替换这些 dummy 来接入 SoC。
在本实验中,我们将借助 Vivado 的 Block Design 工具设计一个三色灯控制器。
三色灯及其电路图如下所示:


当 R、G、B 引脚为高电平(3.3V-5V)时,三色灯发出对应颜色的灯光。
我们要设计的三色灯控制器结构如下图所示:

三色灯控制器主要分为三个模块:AHB-Lite to AXI Bridge,AXI Interconnect 以及 MYIO。
- MYIO:我们将在 Vivido 中创建的三色灯驱动 IP,采用 AXI-Lite 协议,受总线控制输出 32 位的信号 myio。由于三色灯只需要 3bits 信号控制,因此实现时我们只使用输出信号的低三位。
 - AHB-Lite to AXI Bridge:wujian100 SoC 的系统总线采用 AHB-Lite 协议,而我们创建的 IP MYIO 是 AXI-Lite 总线,因此我们需要转接桥来实现总线协议的转换。AHB-Lite to AXI Bridge 能将 AHB-Lite 转换为 AXI。
 - AXI Interconnect:能够将 AXI 协议进一步转换为 AXI-Lite,最终连接到 MYIO 上。
 
调试原理
Wujian100 SoC 支持平头哥自定义的两线调试接口协议,通过两线接口与调试器 CKLink 通信。调试器通过 USB 与 PC 机相连,并与 Debug Server 通信。最后,借助平头哥的集成开发环境 CDK,我们可以实现对 wujian100 的调试。
调试时,首先我们将 CKLink 的 TCK,TMS,GND 连接至 Basys3 映射好的两线调试接口以及 GND 上,连接 CKLink 至 PC。在 CDK 中,默认设置下 Debug Server 即可识别到 CPU E902。在编译好程序后,我们就可以利用调试模式全速运行程序或者进行调试。
最后,Basys3 的管脚映射图可以参阅附录图。
实验步骤
移植运行wujian100
进入 Vivado 工程目录 wujian100_basys3_base\,解压打开工程。
综合、实现、生成比特流。
为提高运行速度,可以在 tcl console 中输入以下命令以多线程运行。$max_threads 是你电脑支持的最高线程数。
set_param general.maxThreads $max_threads
下载比特流到basys3上。
在 IMPLEMENTATION -> Open Implemented Design -> Report Utilization 中可以看到详细的资源使用情况:

测试 GPIO
连接 CKLink 的 TCK,TMS,GND 至 basys3。basys3 的对应 pin 请参阅约束文件。
在本例中,PAD_JTAG_TCLK 连接 J1,即 Pmod JA1;PAD_JTAG_TMS 连接 L2,即 Pmod JA2。连线结果如下图:

gpio cdk 工程位于 sdk\projects\Labs\Lab1\gpio,打开项目文件 CDK\wujian100_open-gpio.cdkproj,编译。
打开 Project Settings -> Debug -> Connector Configurations -> Use ICE Settings,保持默认参数如下:

点击右侧 Connected Debug Target -> Update,出现如下信息表示 CKLink 成功识别到了 wujian100 的 CPU E902M:
Running Debug Server, auto to check the target at first...
T-HEAD: CKLink_Lite_V2, App_ver 2.36, Bit_ver null, Clock 2526.316KHz,
       2-wire, With DDC, Cache Flush On, SN CKLink_Lite_V2-0307F39794.
+--  Debug Arch is CKHAD.  --+
+--  CPU 0  --+
T-HEAD Xuan Tie CPU Info:
    WORD[0]: 0x0804000c
    WORD[1]: 0x10000000
    WORD[2]: 0x24204038
    MISA   : 0x40001014
Target Chip Info:
    CPU Type is E902M, Endian=Little, ISA Patch: 0x0, Revision: 0x0.
    HWBKPT number is 5, HWWP number is 2.
    MISA: (RV32MCE, Imp M-mode)
GDB connection command for CPUs(CPU0):
    target remote 172.29.64.1:1025
    target remote 192.168.1.5:1025
否则,请排查以下问题:
- bitstream 下载是否正确。
 - CKLink 连线是否正确。
 - 是否正确安装了 CKLink 的驱动,连接上后在设备管理器中能否找到 CKLink。
 - 作为复位键的拨码开关 SW15 是否处于闭合状态。
 - 是否打开了多个 CDK 工程的 Debug Server。
 
编译成功,且 CKLink 连接成功后,打开调式模式,点击运行,即可运行程序。
可以观察到 led 灯组正在展示一个二进制累加器:

测试 USART
仔细阅读 SoC 文档了解 USI 的复用方式和对应信号名,并在约束文件查看已经映射好的对应 basys3 pin。
在本例中,PAD_USI0_SCLK 为 RXD,映射到 A14,即 Pmod JB1;PAD_USI0_SD0 为 TXD,映射到 A16,即 Pmod JB2。
USART 的 CDK 项目位于 sdk\projects\Labs\Lab1\usart,打开项目文件并编译。
提前在 pc 上安装好 USB 转 TTL 模块(如 CH340)的驱动程序,连接模块的 GND,TXD,RXD 到 basys3 对应 pin。打开 pc 的串口调试程序,初始化波特率 115200,8N1。CDK 利用调试模式全速运行程序,即可进行串口测试。
为 wujian100 添加外设
我们将在 Vivado 中通过创建 block design 来为 SoC 添加外设——三色灯控制器,替代 main_dummy_top0。
首先我们点击左侧:IP INTEGRATOR->Create Block Design,命名为 ahb_axi。
添加 AHB-Lite 转 AXI 桥
我们需要将 AXI-Lite 的 MYIO 与 AHB-Lite 系统总线桥接起来。
先添加 AHB-Lite 转 AXI 桥。右键 Add IP,搜索并添加 AHB-Lite to AXI Bridge。选中新加入的 IP,点击上方工具栏的 Make External 或直接按下 Ctrl+T,增加外部接口。
创建 IP
点击 Vivado 顶部 tools->create and package new ip->Create AXI4 Peripheral,命名为 myio。设置保持如下默认即可:

在 IP Catalog 中找到新建的 IP,右键点击 Edit in ip package。修改 module hdl 代码:myio_v1_0_S00_AXI.v。观察代码可知,CPU 能通过总线读写 slv_reg0/1/2/3。因此:
我们在用户接口定义处添加:
        // Users to add ports here
        output [31:0] myio,
        // User ports ends
在最后的用户逻辑中添加:
    // Add user logic here
    assign myio=slv_reg0;
    // User logic ends
这样,我们将第一个 slave 寄存器的输出连接到 myio 上,再取 myio 的任意3个口(如低3位)输出到三色灯上。这样就能通过软件让 cpu 控制三色灯了。
再修改顶层代码:myio_v1_0.v
添加接口:
output [31:0] myio,
再在模块 myio_v1_0_S00_AXI 的实例化中添加接口:
myio_v1_0_S00_AXI_inst (
        .myio (myio)
        ...
        );
最后,回到 Vivado Package IP 中,一步步完成左侧的 Packaging Steps,最终 Re-Package IP 完成我们 IP 的创建和打包。
互联
打包后就可以在 Block Design 中添加刚刚创建的 IP 了。
由于我们创建的 IP myio 使用的是轻量级 AXI 总线,因此还需要加入 AXI Interconnect。加入后右键 Customize IP,改为输入输出各1个接口。
接着,Run connection automation。为简化工程,删掉自动生成的复位模块,直接将三个模块的复位端口相连,并连接到 s_ahb_hresetn_0 上,后者最后将和 soc 中 AHB 总线相连。
为 myio Make External,引出 myio[31:0]。
在 Address Editor 中修改 myio 的地址。我们要将它分配到 main_dummy_top0 的位置上,因此地址范围为:0x4001_0000~0x4001_FFFF。
最后,在 Diagram 上方工具栏中点击 Validate Design,完成设计。最终的结构应和原理中的相同。
系统整合
完成 Block Design 后,我们需要将 ahb_axi 整合进 SoC 中。
在 Source->Design Sources 中找到我们设计的 ahb_axi。右键 Generate Output Product,接着再 Create HDL Wrapper。打开源码 ahb_axi_wrapper.v 可以看到,ports 的最后三个就是我们的 myio,时钟和复位,其余的都是 AHB-Lite 的。
在 Sources 中新建一个 myio_top.v,作为顶层模块用来替代 main_dummy_top0。后者是模块 ahb_dummy 的实例化。
因此首先,我们定义 myio_top 的输入输出接口和 ahb_dummy 一致。
接着,我们再实例化我们的模块 ahb_axi_wrapper。将它的接口与 myio_top 的接口匹配相连。注意以下几个问题:
- 将同一信号的 port 相连。wrapper 中的端口名基本上只是多了开头的 
AHB_INTERFACE_0_,除了以下几个 port。 hready。ahb_axi_wrapper里有hready_in,hready_out。但是ahb_dummy只有hready。处理方法是将hready_out和hready直接相连,hready_in则:
   wire hready_in;
   assign hready_in=hready;
原因请自行查阅 wujian100 和 Xilinx 手册。
ahb_axi_wrapper多了一个hburst,直接加到myio_top作为 input。事实上 dummy0 的顶层模块是有这根线的。
   input  hburst; 
   wire [2:0] hburst; 
hresp。hresp是 2bits 信号,而ahb_axi_wrapper中的AHB_INTERFACE_0_hresp只需要hresp最低位,处理方法:
   wire [1:0] hresp;
   assign hresp[1]=1'b0;
   ahb_axi_wrapper  u_ahb_axi_wrapper (
   ...
   .AHB_INTERFACE_0_hresp       ( hresp[0]       ),
   ...
   );
myio,引为myio_top的 output port。
完成 myio_top.v 后,我们到 ahb_matrix_top.v 中替代 x_main_dummy_top0。注意 hburst 和 myio。
myio_top  x_main_dummy_top0 (  //ahb_dummy_top
    ...
    .hburst                  (hmain0_dummy0_s7_hburst),
    .myio                    (myio)
};
将模块的输出 myio 一直引到系统顶层:wujian100_open_fpga_top.v。注意要在每层同时添加 port、wire 和实例化的连线。
最后,在约束文件中添加:
set_property -dict {PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { myio[0] }]; #R
set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { myio[1] }]; #G
set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { myio[2] }]; #B
映射到了 Basys3 JC 的三个口。
综合实现生成比特流,下载到 Basys3 上。
测试外设
连接三色灯到 Basys3 JC 对应 pin。
在本例中,R 对应 M18,即 Pmod JC2,G 对应 N17,即 Pmod JC3,B 对应 P18,即 Pmod JC4。
测试三色灯的 CDK 项目位于 sdk\projects\Labs\Lab1\myio,编译,调试模式运行。可以观察到三色灯的颜色变化:

附录
