前言

蜂鸟E203是由芯来科技推出的开源RISC-V处理器核,它遵循RV32IMC指令集架构,专为嵌入式场景设计,具有低功耗、小面积​(2级流水线,核心面积仅0.02mm²@28nm)、高可配置性等特点。

蜂鸟v2 E203 RISC-V 处理器内核和 SoC 由中国大陆领先的 RISC-V IP 和解决方案公司 Nuclei System Technology 开发和开源。它是在 SI-RISCV/e200_opensource 中维护的蜂鸟 E203 项目的升级版,因此我们称之为 Hummingbirdv2 E203 (HBirdv2 E203)。Hummingbirdv2 E203 的系统架构如下图所示。

开发环境

  • 硬件工具:ZYNQ Z7-020开发板(XC7Z020CLG400-2)、蜂鸟官方JTAG调试器、相关连线

  • 软件工具:Vivado 2018.3、Nuclei Studio任意版本

  • 操作系统:Windows11

  • 工程源码E203_ZYNQ_FPGA


移植思路

通过对比原Nuclei开发板(Artix-7)与ZYNQ7020的硬件差异,重点解决以下关键问题:

  1. 时钟架构重构:利用ZYNQ PS端PLL生成系统时钟

  2. 管脚约束设置:需要根据自己的开发板管脚定义重新设置约束文件

  3. JTAG调试:需要自行连接riscv调试器进行调试


1. 准备工作

1.1 E203工程获取

E203官方项目地址:e203_hbirdv2,在Github下载官方项目文件,其中,我们只需要用到的是rtl/e203下的RTL源码,以及tb下的仿真文件。

当热,也可以直接进入E203_ZYNQ_FPGA ,其中已经上传了这个移植项目的所有RTL代码、约束文件、仿真文件以及Vivado工程文件。

然后,安装好该项目需要用到的必要工具,即 Vivado、Nuclei Studio,具体教程如下:

Vivado18.3的安装 安装教程_vivado安装教程2018.3-CSDN博客

Nuclei Studio安装教程

NucleiStudio的快速上手 - RISC-V MCU文档中心

1.2 E203_ZYNQ_FPGA项目源码目录结构

├── E203_RTL/ - Core RTL & Top-level Integration / 核心RTL与顶层集成
│ ├── core/ - E203 Processor Core (Pipeline, ALU, CSR) / 处理器核源码(流水线、ALU、CSR等)
│ ├── system.v - Top-level System Module / 系统顶层模块
│ └── clkdivider.v - ZYNQ Clock Divider / ZYNQ时钟分频模块

├── E203_SIM/ - Simulation Environment / 仿真环境
│ └── e203_tb_top.v - System Testbench / 系统测试平台

├── E203_Zynq_7020/ - Vivado Hardware Project / Vivado硬件工程

├── SDK/ - Nuclei Studio SDK Projects / Nuclei Studio软件开发工程

└── XDC/ - Physical Constraints / 物理约束
   └── e203_zynq_7020.xdc - ZYNQ7020 Pin Assignment / 引脚分配文件

1.3 创建vivado工程并导入E203核的RTL代码

新建vivado项目,根据自己的开发板型号选择部件,然后将E203_RTL目录下的全部RTL代码都导入到源文件(采用 Add Directories 的方式引入整个文件夹)

导入代码后vivado可以自动识别顶层代码system.v,也可以手动设置:

2. 系统顶层文件及相关设置

导入RTL代码后,还需要进行一些设置,具体如下:

2.1 时钟设计

E203 MCU SoC 的两个输入时钟定义如下:

input wire CLK100MHZ,//GCLK-W19
input wire CLK32768KHZ,//RTC_CLK-Y18

而ZYNQ7020开发板是50MHz的时钟源,所以要先用一个锁相环(ZYNQ自带的IP)将50MHz时钟源降频为16MHz ,但是32.768KHz由于频率过低,无法通过此IP实现,所以得自己写分频器实现(分频代码在E203_RTL/clkdivider.v)。

实现方案

  1. 通过一个PLL锁相环(ZYNQ自带的IP)将50MHz时钟源降频为 16MHz
  2. 设计分频模块将16MHz分频生成 32.768KHz

顶层代码中具体的实例化如下:

// 实例化PLL
PLL sys_clk_gen
  (
    .resetn      (ck_rst    ), // ck_rst is active low
    .CLK_I_50M   (CLK50MHZ  ),

    .CLK_O_16M   (clk_16M   ), // 16 MHz, this clock we set to 16MHz
    // .CLK_O_8M388608 (clk_8M388608  ),
    .locked      (mmcm_locked)
  );


// 实例化clkdivider 
clkdivider rtc_clk_gen(
    .clk         (clk_16M   ),//generate 32.768KHz
    .resetn       (ck_rst),
    .clk_out     (CLK32768KHZ)
  );

手写的分频器是将16MHz分频生成 32.768KHz,具体如下:

module clkdivider
(
  input wire clk,
  input wire resetn,
  output reg clk_out
);

  reg [7:0] counter;

  always @(posedge clk)
  begin
    if (!resetn)
    begin
      counter <= 8'd0;
      clk_out <= 1'b0;
    end
    //else if (counter == 8'hff)
    else if (counter == 8'd243)
    begin
      counter <= 8'd0;
      clk_out <= ~clk_out;
    end
    else
    begin
      counter <= counter+1;
    end
  end
endmodule

vivado中需要通过IP Catalog设置相应的IP(Clocking Wizard),修改name以及输入输出端口,设置低电平复位,具体如下:

然后在管脚约束中将 CLK50MHZ 绑定到开发板的PL时钟源管脚 PL_GCLK 即可。

2.2 复位设置

E203 MCU SoC 的复位信号定义如下,即FPGA开发板自身的复位和为MCU设置的复位,这两个复位通过一个与操作生成一个为SoC使用的复位信号,即SoC复位的条件是这两个复位信号至少有一个生效。

input wire fpga_rst,//FPGA_RESET-T6
input wire mcu_rst,//MCU_RESET-P20

由于这两个复位信号都是低电平有效,故分别绑定到开发板自身的 RESET 和 KEY0 按键即可,其中RESET 是FPGA自身的复位键,而KEY0 是为MCU设置的复位键(注意开发板上的按键要满足按下去为0,松开为1,即低电平有效的要求。

system.v中的 reset_sys ip_reset_sys 实例化代码无需修改,同样在vivado中设置IP,注意修改name:

2.3 QSPI Flash接口映射

蜂鸟E203 有三个QSPI接口,分别为QSPI0、QSPI1、QSPI2,其中QSPI0专门是为外部FLASH准备的,而其余两个都是通过GPIO口复用进行使用。所以rtRTL顶层需要绑定的是专为外部FLASH准备的QSPI接口。但是ZYNQ只有一个FLASH,没有外部的接口,所以先注释掉QSPI,不采用FLASH启动而是ILM(ROM)方式。

  // Dedicated QSPI interface
  // output wire qspi0_cs,
  // output wire qspi0_sck,
  // inout wire [3:0] qspi0_dq,

相应的,由于蜂鸟 E203 MCU SoC芯片顶层引脚中 io_pads_bootrom_n_i_ival 是用来配置上电地址选择的,即上电复位后处理器核从哪个地址开始上电执行,此信号为1时,处理器核从外部FLASH地址(0x2000_0000)开始执行,这也是默认的上电流程配置;而当此信号为0时,处理器核从内部 ROM 地址(0x0000_1000)开始执行,而 ROM 中存放的代码执行完后会跳转至ITCM(0x8000_0000)中继续执行。由于目前开发板采用ROM方式启动,所以需要将此信号配置为 0.

  //==================model select===================
  // 0:internal ROM    (0x0000_1000)~0x0000_1FFFF (ITCM 0x8000_0000)
  // 1:from QSPI_FLASH (0x2000_0000)
  assign dut_io_pads_bootrom_n_i_ival  = 1'b0;   // set 0 to make it rom start either flash start
  assign dut_io_pads_dbgmode0_n_i_ival = 1'b1;
  assign dut_io_pads_dbgmode1_n_i_ival = 1'b1;
  assign dut_io_pads_dbgmode2_n_i_ival = 1'b1;

2.4 PMU管脚即其他

蜂鸟E203的 PMU 用于控制主域电源,可以使主域被置于断电状态以节省功耗,或者重新唤醒。pmu_paden 用于MCU的电源指示,pmu_padrst 指示MCU的复位,这两个可以接开发板上的LED,而mcu_wakeup 用于唤醒,低电平有效,可以接开发板的按键。而32个GPIO和JTAG管脚绑定到开发板上的空闲IO口上即可,具体可以参考项目XDC / e203_zynq_7020.xdc 的引脚分配文件。

  //pmu_wakeup
  inout wire pmu_paden,  //PMU_VDDPADEN   M14
  inout wire pmu_padrst, //PMU_VADDPARST  M15
  inout wire mcu_wakeup, //MCU_WAKE       N16

  // JD (used for JTAG connection)
  // inout wire mcu_TDO, //MCU_TDO-W13
  output wire mcu_TDO, //MCU_TDO-W13
  inout wire mcu_TCK, //MCU_TCK-U13 
  inout wire mcu_TDI, //MCU_TDI-T11
  inout wire mcu_TMS, //MCU_TMS-T12

2.5 设置全局文件

导入项目RTL代码、设置完相应IP后,vivado的Sources目录依旧报错,这时需要设置e203_defines.v为Global Include以及文件类型为Verilog Header:

 同时,需要在该文件中加上`define FPGA_SOURCE宏定义:

3. 行为级仿真

E203_SIM / e203_tb_top.v 中已经写好了仿真的测试用例,在Sources中点击“+”号导入该仿真文件(注意选择的是 “Add or create simulation sources”):

仿真前需要在Nuclei Studio应用中新建工程,然后编译生成.verilog文件,在tb中进行调用,具体步骤如下:

3.1 Nuclei Studio新建工程

在1.1中已经完成了Nuclei Studio的安装,此时,进入应用中,新建一个helloworld工程:

然后设置编译的指令,在project -> properties -> c/c++ Bulid -> Settings -> Build Steps -> Post-bulid steps -> command 中添加命令:

riscv-nuclei-elf-objcopy -O verilog "${BuildArtifactFileBaseName}.elf" "${BuildArtifactFileBaseName}.verilog";sed -i 's/@800/@000/g' "${BuildArtifactFileBaseName}.verilog"; sed -i 's/@00002FB8/@00002000/g' "${BuildArtifactFileBaseName}.verilog";

默认的main主程序比较复杂,为了方便测试,可以修改简单一些:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include "hbird_sdk_soc.h"

int main(void)
{

    for (int i = 0; i < 10; i ++) {
        //printf("%d: Hello World From My risc-v\r\n",i);

        printf("%d: Hello World In zynq 7020!!!\r\n",i);
    }
    return 0;
}

点击锤子按钮编译生成 .verilog文件:

 然后将文件地址复制到e203_tb_top.v指定位置,tb中进行调用的具体代码是这里:

//省略前面的部分......

reg [7:0] itcm_mem [0:(`E203_ITCM_RAM_DP*8)-1];
    initial begin
      $readmemh({"C:/user/projects/hello_test.verilog"}, itcm_mem);
      //$readmemh({""}, itcm_mem);

3.2 vivado仿真

在vivado中点击左侧菜单栏的 Run Simulation 进行行为级仿真,在 Tcl Console 可以观察到刚才的main函数的printf输出内容:

4. 上板验证

4.1 管脚约束

在Sources栏中添加约束文件,导入项目 XDC / e203_zynq_7020.xdc 的约束文件:

约束文件具体内容如下,可以自行参考开发板的说明文档进行设置:

set_property CFGBVS VCCO [current_design];
set_property CONFIG_VOLTAGE 3.3 [current_design];

#####               create clock              #####
set_property -dict { PACKAGE_PIN U18    IOSTANDARD LVCMOS33 } [get_ports { CLK50MHZ }];  # PL_GCLK
create_clock -add -name sys_clk_pin -period 20.00 -waveform {0 5} [get_ports {CLK50MHZ}];


#=========================== CLK32768KHZ ======================================#
#set_property -dict { PACKAGE_PIN Y18    IOSTANDARD LVCMOS33 } [get_ports { CLK32768KHZ }]; 
#create_clock -add -name sys_clk_pin -period 30517.58 -waveform {0 15258.79} [get_ports {CLK32768KHZ}];

set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets dut_io_pads_jtag_TCK_i_ival];
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets IOBUF_jtag_TCK/O];


#####            rst define           #####
# set_property -dict { PACKAGE_PIN C7   IOSTANDARD LVCMOS33 } [get_ports { fpga_rst }]; # RESET    PS_POR_B PS_POR_B_500 C7 (all keys: pull down is 0, low active)
set_property -dict { PACKAGE_PIN N15  IOSTANDARD LVCMOS33 } [get_ports { mcu_rst }];  # PL_KEY1  IO_L21P_T3_35 N15 


#=========================== QSPI0 Flash ======================================#
#####                spi0 define               #####
# set_property PACKAGE_PIN A7 [get_ports  qspi0_cs    ]; # QSPI_CS   PS_MIO1_500  A7
# set_property PACKAGE_PIN A5 [get_ports  qspi0_sck   ]; # QSPI_CLK  PS_MIO6_500  A5
# set_property PACKAGE_PIN A6 [get_ports {qspi0_dq[3]}]; # QSPI_D3   PS_MIO5_500  A6
# set_property PACKAGE_PIN B7 [get_ports {qspi0_dq[2]}]; # QSPI_D2   PS_MIO4_500  B7
# set_property PACKAGE_PIN D6 [get_ports {qspi0_dq[1]}]; # QSPI_D1   PS_MIO3_500  D6
# set_property PACKAGE_PIN B8 [get_ports {qspi0_dq[0]}]; # QSPI_D0   PS_MIO2_500  B8



#####                PMU define               #####
set_property -dict { PACKAGE_PIN M14   IOSTANDARD LVCMOS33 } [get_ports pmu_paden ];   # PL_LED1 IO_L23P_T3_35 M14 
set_property -dict { PACKAGE_PIN M15   IOSTANDARD LVCMOS33 } [get_ports pmu_padrst ];  # PL_LED2 IO_L23N_T3_35 M15 
set_property -dict { PACKAGE_PIN N16   IOSTANDARD LVCMOS33 } [get_ports mcu_wakeup ];  # PL_KEY2 IO_L21N_T3_35 N16 


## GPIOA
#####               MCU JTAG define           #####
set_property -dict { PACKAGE_PIN W13  IOSTANDARD LVCMOS33 } [get_ports { mcu_TDO }]; # PIN27 EX_IO1_13N IO_L4N_T0_34 W13
set_property -dict { PACKAGE_PIN U13  IOSTANDARD LVCMOS33 } [get_ports { mcu_TCK }]; # PIN26 EX_IO1_12P IO_L3P_T0_34 U13
set_property -dict { PACKAGE_PIN T11  IOSTANDARD LVCMOS33 } [get_ports { mcu_TDI }]; # PIN32 EX_IO1_15P IO_L1P_T0_34 T11
set_property -dict { PACKAGE_PIN T12  IOSTANDARD LVCMOS33 } [get_ports { mcu_TMS }]; # PIN30 EX_IO1_14P IO_L2P_T0_34 T12
set_property KEEPER true [get_ports mcu_TMS]


#=============================== UART 0 ======================================
set_property -dict { PACKAGE_PIN U12   IOSTANDARD LVCMOS33 } [get_ports { uart0_tx }]; # PIN29 EX_IO1_14N IO_L2N_T0_34 U12
set_property -dict { PACKAGE_PIN T10   IOSTANDARD LVCMOS33 } [get_ports { uart0_rx }]; # PIN31 EX_IO1_15N IO_L1N_T0_34 T10
#---------------------------- End of UART 0 --------------------------------


#LEDs
set_property -dict { PACKAGE_PIN K16   IOSTANDARD LVCMOS33 } [get_ports { led3 }]; # PL_LED3---> IO_L24P_T3_35-> K16
set_property -dict { PACKAGE_PIN J16   IOSTANDARD LVCMOS33 } [get_ports { led4 }]; # PL_LED4---> IO_L24N_T3_35-> J16

#KEYs
set_property -dict { PACKAGE_PIN T17   IOSTANDARD LVCMOS33 } [get_ports { key3 }]; # PL_KEY3--->  IO_L20P_T3_34 T17
set_property -dict { PACKAGE_PIN R17   IOSTANDARD LVCMOS33 } [get_ports { key4 }]; # PL_KEY4--->  IO_L19N_T3_34 R17


# 40P
# set_property -dict { PACKAGE_PIN W18   IOSTANDARD LVCMOS33 } [get_ports { gpioA_5 }]; # W18
# set_property -dict { PACKAGE_PIN P14   IOSTANDARD LVCMOS33 } [get_ports { gpioA_6 }]; # P14
# set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 } [get_ports { gpioA_7 }]; # Y16


#####                gpioA define              #####
# ## LED 4
# set_property PACKAGE_PIN M25  [get_ports {gpioA[24]}]     
# ## LED 3
# set_property PACKAGE_PIN N23  [get_ports {gpioA[23]}]     
# ## LED 2
# set_property PACKAGE_PIN N22  [get_ports {gpioA[22]}]     
# ## LED 1
# set_property PACKAGE_PIN V22  [get_ports {gpioA[21]}]     
# ## UART TX
# set_property PACKAGE_PIN L25  [get_ports {gpioA[17]}]
# ## UART RX
# set_property PACKAGE_PIN M24  [get_ports {gpioA[16]}]
# ## key_in C
# set_property PACKAGE_PIN AB25 [get_ports {gpioA[7]}]
# ## key_in R
# set_property PACKAGE_PIN W23  [get_ports {gpioA[6]}]
# ## key_in L
# set_property PACKAGE_PIN W24  [get_ports {gpioA[5]}]
# ## key_in D
# set_property PACKAGE_PIN AB26 [get_ports {gpioA[4]}]
# ## key_in U
# set_property PACKAGE_PIN AC26 [get_ports {gpioA[3]}]


#####         SPI Configurate Setting        #######
#set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] 
#set_property CONFIG_MODE SPIx4 [current_design] 
#set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]

set_property BITSTREAM.CONFIG.UNUSEDPIN Pullup [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design] 

4.2 vivado综合

检查源文件没有报错信息后,在vivado中点击左侧菜单的Run Synthesis进行综合,也可以点击Generate Bitstream直接进行 综合——实现——生成比特流的一系列操作,完成后可以查看时序以及资源占用的情况:

4.3 连接开发板并烧写程序

vivxado完成 Generate Bitstream 后,将开发板插上电,使用ZYNQ自带的JTAG下载器连接电脑,点击左侧菜单栏的 Open Hardware Manager,设置vivado自动识别开发板,然后点击 Program Device 烧写程序:

4.4 连接jtag调试器

利用蜂鸟官方的riscv调试器,根据约束文件中的管脚定义进行引脚的连接,注意连接方式为:
TCLK->TCLK,TDI->TDI,TDO->TDO,TMS->TMS,同时,也要连接一个GND。

4.5 使用Nuclei Studio调试

打开刚才的helloworld工程,连接串口,然后点击Run按钮运行程序:

如果出现以下报错内容,那么需要将openocd_hbirdv2.cfg文件中关于FLASH的语句都注释掉,禁止FLASH启动:

修改之后再次运行,程序在串口正常打印输出:

5. 问题处理

5.1 Windows下OpenOCD找不到设备及串口无法识别

通常情况下,Windows系统会自动为其安装正确的串口驱动,如果使用最新版本的Nuclei Studio IDE开发,其内置的OpenOCD是免驱的,也不需要用户手动安装JTAG调试驱动。但有些时候,Windows系统并不能自动安装正确的串口驱动,进而影响到IDE的程序烧写和串口连接功能。经实验,可尝试用下述方法予以解决:

  • 先下载蜂鸟调试器驱动和FT2232串口驱动到电脑上
  • 连接开发板,卸载新增的两个串行设备的同时也删除对应的驱动
  • 断开开发板,手动安装蜂鸟调试器驱动,即hbird_driver.exe驱动程序
  • 连接开发板,打开设备管理器,为另一个未识别的端口手动安装FT2232串口驱动
  • 重连开发板,程序可以正常烧写调试,串口也可以正常建立连接

具体图文教程来自于:GD32VF103 MCU_RISC-V论坛讨论_RISC-V MCU中文社区

5.2 Nuclei Studio IDE使用常见问题

相关说明页:Nuclei Studio常见问题_Nuclei Studio_RISC-V MCU中文社区

下载程序的过程中如果出现错误,可能原因经常有以下几个:

  • 1. 板子上的处理器没有正常跑起来或者改动了某些地方造成功能错误;
  • 2. 蜂鸟的JTAG引脚分配错误或者开发板和调试器的连线错误;
  • 3. openocd配置文件即openocd_hbirdv2.cfg有问题(ftdi_device_desc和ftdi_vid_pid与使用的调试器不符);
  • 4. 如果是Linux系统则99-openocd.rules文件可能有问题或者没有将用户添加到plugdev group中等。


原创声明:本文为CSDN博主「yuuki_ac」原创文章,遵循CC 4.0 BY-SA版权协议
转载需附原文链接:蜂鸟E203 hbirdv2项目复现与移植到ZYNQ7020及构建FPGA工程【基于Windows】-CSDN博客

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐