cruelfox 发表于 2021-6-18 07:27

【Perf-V评测】移植蜂鸟E203开源SOC到FPGA板

本帖最后由 cruelfox 于 2021-6-17 23:10 编辑

<p>  Perf-V开发板资料里面有E203 SOC移植好的工程,但是因为用的Vivado版本比我装的高,软件不能全部导进来,主要是因为其中两个Xilinx IP核的问题。</p>

<p>  我用Vivado中的IP Catalog重新定制了这两个模块的IP,但不知什么原因,最终生成的系统没有能够正常工作。原始工程(软件版本更高)中的 .bit 文件直接下载到 FPGA 中是可以工作的(JTAG连接可以发现RISC-V CPU),说明代码结构是没有问题的。于是我用它作参考,自己移植一下这个SOC.</p>

<p>&nbsp;</p>

<p>  上一篇帖子中我分享了E203 SOC RTL代码的仿真,将这套代码移植到FPGA的操作需要另外编写一个顶层模块,不用仿真测试的顶层模块(文件 tb_top.v)。在Perf-V自带FPGA工程中,顶层模块代码是 system.v, 相当于在 e203_soc_top 这个模块外面再套一层&ldquo;壳&quot;,将FPGA管脚信号连上去。</p>

<p>  看看 e203_soc_top 模块的输入输出端口:</p>

<p>  时钟信号及使能:有高频(HF)和低频(LF)两个时钟输入,分别是16MHz和32.768kHz. 使能信号是给出去的,可以不连。</p>

<p><span style="color:#2980b9;"><strong><span style="font-family:Courier;">&nbsp; input &nbsp;hfextclk,<br />
&nbsp; output hfxoscen,</span></strong></span></p>

<p><span style="color:#2980b9;"><strong><span style="font-family:Courier;">&nbsp; input &nbsp;lfextclk,<br />
&nbsp; output lfxoscen,</span></strong></span><br />
  JTAG口的信号:除了TDO都是输入,TDO还有一个输出使能信号。</p>

<p><span style="color:#2980b9;"><strong><span style="font-family:Courier;">&nbsp; input &nbsp; io_pads_jtag_TCK_i_ival,<br />
&nbsp; input &nbsp; io_pads_jtag_TMS_i_ival,<br />
&nbsp; input &nbsp; io_pads_jtag_TDI_i_ival,<br />
&nbsp; output &nbsp;io_pads_jtag_TDO_o_oval,<br />
&nbsp; output &nbsp;io_pads_jtag_TDO_o_oe,</span></strong></span><br />
  32个GPIO口的信号,每个口都有6个信号。</p>

<p><span style="color:#2980b9;"><strong><span style="font-family:Courier;">&nbsp; input &nbsp; io_pads_gpio_0_i_ival,<br />
&nbsp; output &nbsp;io_pads_gpio_0_o_oval,<br />
&nbsp; output &nbsp;io_pads_gpio_0_o_oe,<br />
&nbsp; output &nbsp;io_pads_gpio_0_o_ie,<br />
&nbsp; output &nbsp;io_pads_gpio_0_o_pue,<br />
&nbsp; output &nbsp;io_pads_gpio_0_o_ds,</span></strong></span></p>

<p><span style="color:#2980b9;"><strong><span style="font-family:Courier;">&nbsp; ......</span></strong></span><br />
  QSPI flash连接的信号,其中 DQ 是双向端口,因此每个口也有6个信号。</p>

<p><span style="color:#2980b9;"><strong><span style="font-family:Arial;">&nbsp; output &nbsp;io_pads_qspi_sck_o_oval,<br />
&nbsp; output &nbsp;io_pads_qspi_cs_0_o_oval,<br />
&nbsp; input &nbsp; io_pads_qspi_dq_0_i_ival,<br />
&nbsp; output &nbsp;io_pads_qspi_dq_0_o_oval,<br />
&nbsp; output &nbsp;io_pads_qspi_dq_0_o_oe,<br />
&nbsp; output &nbsp;io_pads_qspi_dq_0_o_ie,<br />
&nbsp; output &nbsp;io_pads_qspi_dq_0_o_pue,<br />
&nbsp; output &nbsp;io_pads_qspi_dq_0_o_ds,<br />
&nbsp; ......</span></strong></span></p>

<p>  额外的控制信号:</p>

<p><span style="color:#2980b9;"><span style="font-family:Courier;"><strong>&nbsp; // Erst is input need to be pull-up by default<br />
&nbsp; input &nbsp; io_pads_aon_erst_n_i_ival,</strong></span></span></p>

<p><span style="color:#2980b9;"><span style="font-family:Courier;"><strong>&nbsp; // dbgmode are inputs need to be pull-up by default<br />
&nbsp; input &nbsp;io_pads_dbgmode0_n_i_ival,<br />
&nbsp; input &nbsp;io_pads_dbgmode1_n_i_ival,<br />
&nbsp; input &nbsp;io_pads_dbgmode2_n_i_ival,</strong></span></span></p>

<p><span style="color:#2980b9;"><span style="font-family:Courier;"><strong>&nbsp; // BootRom is input need to be pull-up by default<br />
&nbsp; input &nbsp;io_pads_bootrom_n_i_ival,</strong></span></span></p>

<p><span style="color:#2980b9;"><span style="font-family:Courier;"><strong>&nbsp; // dwakeup is input need to be pull-up by default<br />
&nbsp; input &nbsp;io_pads_aon_pmu_dwakeup_n_i_ival,</strong></span></span></p>

<p><span style="color:#2980b9;"><span style="font-family:Courier;"><strong>&nbsp; &nbsp; &nbsp; // PMU output is just output without enable<br />
&nbsp; output io_pads_aon_pmu_padrst_o_oval,<br />
&nbsp; output io_pads_aon_pmu_vddpaden_o_oval&nbsp;</strong></span></span><br />
&nbsp;</p>

<p>  在Perf-V提供demo的 system.v 文件中,定义了一些&nbsp;FPGA 的输入输出端口。比如 Perf-V 板子上的 LED、RGB LED、按键、拨动开关、插座引出的I/O口、JTAG口等。特殊管脚比如时钟、JTAG,需要和板子适配;至于 E203 的 32 个GPIO口怎么对应到这些LED、开关、插座上去,可以随自己便。</p>

<p>  E203的一个GPIO口有6个信号,只须对应到FPGA的一个引脚。在demo的 system.v 中是这样写的:</p>

<pre>
<code>wire iobuf_gpio_7_o;
IOBUF
#(
    .DRIVE(12),
    .IBUF_LOW_PWR("TRUE"),
    .IOSTANDARD("DEFAULT"),
    .SLEW("SLOW")
)
IOBUF_gpio_7
(
    .O(iobuf_gpio_7_o),
    .IO(gpio_7),
    .I(dut_io_pads_gpio_7_o_oval),
    .T(~dut_io_pads_gpio_7_o_oe)
);
assign dut_io_pads_gpio_7_i_ival = iobuf_gpio_7_o &amp; dut_io_pads_gpio_7_o_ie;</code></pre>

<p>  这里 IOBUF 的写法是 Xilinx 软件支持的,含义可以猜出来。输出使能信号 dut_io_pads_gpio_7_o_oe&nbsp;决定了实际 FPGA 引脚是否驱动(输出),输入使能信号 dut_io_pads_gpio_7_o_ie 是决定引脚上的电平状态是否反映到内部信号,还有 dut_io_pads_gpio_7_o_pue (上拉允许) 和 dut_io_pads_gpio_7_o_ds (用途不明确?) 两个信号就没有使用。好象 FPGA 不支持动态改变引脚的上拉功能,所以用不上。</p>

<p>  GPIO在板子上的分配,demo里面写得略复杂了一点。我就先给简化了:GPIO0~8 直接连到三个 RGB LED,其余连到 Arduino 接口,再剩下的连到 PMOD 口上去。</p>

<pre>
<code>        assign led0_r = gpio_0;
        assign led0_g = gpio_1;
        assign led0_b = gpio_2;
        assign led1_r = gpio_3;
        assign led1_g = gpio_4;
        assign led1_b = gpio_5;
        assign led2_r = gpio_6;
        assign led2_g = gpio_7;
        assign led2_b = gpio_8;

        assign arduino_a = gpio_9;
        assign arduino_a = gpio_10;
        assign arduino_a = gpio_11;
        assign arduino_a = gpio_12;
        assign arduino_a = gpio_13;
        assign arduino_a = gpio_14;

        assign arduino_d = gpio_15;
        assign arduino_d = gpio_16;
        assign arduino_d = gpio_17;
        assign arduino_d = gpio_18;
        assign arduino_d = gpio_19;
        assign arduino_d = gpio_20;
        assign arduino_d = gpio_21;
        assign arduino_d = gpio_22;
        assign arduino_d = gpio_23;
        assign arduino_d = gpio_24;
        assign arduino_d = gpio_25;
        assign arduino_d = gpio_26;
        assign arduino_d = gpio_27;
        assign arduino_d = gpio_28;

        assign pmod_io = gpio_29;
        assign pmod_io = gpio_30;
        assign pmod_io = gpio_31;</code></pre>

<p>&nbsp;</p>

<p>  E203 SOC 的主时钟是 16MHz, 但开发板的晶振是 50MHz. 因此这里需要一个 PLL 把时钟转换一下。这就要用到 FPGA 提供的 IP 了。参考 demo 的实现,用一个 MMCM IP模块,得到 16MHz 和 8.388MHz 两个时钟,再将 8.388MHz 1/256分频到32768kHz左右作为 LF 时钟。暂时也不需要很准。其实 16MHz 也不是必须的,比如直接将 50MHz 分频到 25MHz 或 12.5MHz 给 RISC-V 用也问题不大,除了UART时钟受影响要重新设以外。</p>

<p>  E203 SOC 的复位信号(输入)是&nbsp;io_pads_aon_erst_n_i_ival, 在 demo 里面用了一个复位模块IP来提供,但是我之前遇到的失败就从这里引起。于是我不用它,简单点就用 PLL 的锁定信号和外部复位按键组合代替了:</p>

<pre>
<code>        assign reset_periph = ~ck_rst | ~mmcm_locked;
assign dut_io_pads_aon_erst_n_i_ival = ~reset_periph;</code></pre>

<p>  JTAG的4个信号照着 demo 里面那样处理,这是必不可缺少的。QSPI 的信号是连到板子上 flash 的,用来存放RISC-V程序。在调试阶段可以先不管 QSPI.</p>

<p>  连接好 FPGA 接口以后就可以开始软件综合了,发现错误再回去改。</p>

<p>&nbsp;</p>

<p>  还有一个工作是将 FPGA 的接口信号映射到 FPGA 管脚。demo工程里面已经有约束文件了,可以提取出来修改了用,不然全照着电路图一个一个设管脚就很繁琐了。</p>

<p>  完成综合实现之后,Vivado显示的 FPGA 资源使用情况:</p>

<p></p>

<p>  将生成的 .bit 文件下载到 FPGA 中之后,需要检查SOC的工作情况。这一步需要借助调试器,从SOC的JTAG口访问内部资源。这个JTAG不是FPGA的JTAG, 而是FPGA用户逻辑实现的和RISC-V相连的JTAG. 在开发板上两个JTAG口的针脚定义也一样,PerfXLab的下载线也应该是支持两种JTAG用法的。但是我不想拔来插去的,就用另外的工具来处理RISC-V这边的JTAG了。</p>

<p>  我用了一片 FT232R 当作调试硬件,用 openocd 连接开发板上的 E203 SOC:</p>

<p>有这样的信息提示就表示识别到 RISC-V CPU 了。</p>

<p>&nbsp;</p>

<p>  因为现在系统里面没有程序,CPU当处于异常状态。</p>

<p><span style="color:#8e44ad;">Open On-Chip Debugger<br />
&gt; halt<br />
&gt; reg pc<br />
pc (/32): 0x00000000</span></p>

<p><span style="color:#8e44ad;">&gt; step<br />
halted at 0x0 due to step<br />
&gt; mdw 0 4<br />
0x00000000: 00000000 00000000 00000000 00000000</span></p>

<p><span style="color:#8e44ad;">&gt;</span><br />
  要执行程序,一种办法是用 openocd 直接将代码写到内存里面去,然后修改 PC 寄存器,执行。</p>

<p><span style="color:#8e44ad;">&gt; load_image f:/rv32.hex<br />
1602 bytes written at address 0x80000000<br />
72 bytes written at address 0x80001000<br />
16 bytes written at address 0x80002fb8<br />
downloaded 1690 bytes in 1.156250s (1.427 KiB/s)</span></p>

<p><span style="color:#8e44ad;">&gt; resume 0x80000000<br />
&gt; halt<br />
halted at 0x80000090 due to debug interrupt<br />
&gt;</span><br />
  GDB也可以拿来调试程序。</p>

<p><span style="color:#8e44ad;">GNU gdb (GDB) 8.3.0.20190516-git<br />
Copyright (C) 2019 Free Software Foundation, Inc.<br />
License GPLv3+: GNU GPL version 3 or later &lt;http://gnu.org/licenses/gpl.html&gt;<br />
This is free software: you are free to change and redistribute it.<br />
There is NO WARRANTY, to the extent permitted by law.<br />
Type &quot;show copying&quot; and &quot;show warranty&quot; for details.<br />
This GDB was configured as &quot;--host=i686-w64-mingw32 --target=riscv-nuclei-elf&quot;.<br />
Type &quot;show configuration&quot; for configuration details.<br />
For bug reporting instructions, please see:<br />
&lt;http://www.gnu.org/software/gdb/bugs/&gt;.<br />
Find the GDB manual and other documentation resources online at:<br />
&nbsp; &nbsp; &lt;http://www.gnu.org/software/gdb/documentation/&gt;.</span></p>

<p><span style="color:#8e44ad;">For help, type &quot;help&quot;.<br />
Type &quot;apropos word&quot; to search for commands related to &quot;word&quot;.<br />
(gdb) set arch riscv:rv32<br />
The target architecture is assumed to be riscv:rv32<br />
(gdb) target remote localhost:3333<br />
Remote debugging using localhost:3333<br />
warning: No executable has been specified and target does not support<br />
determining executable automatically. &nbsp;Try using the &quot;file&quot; command.<br />
0x80000090 in ?? ()<br />
(gdb) x /3i $pc<br />
=&gt; 0x80000090: &nbsp;sw &nbsp; &nbsp; &nbsp;gp,-140(t5)<br />
&nbsp; &nbsp;0x80000094: &nbsp;j &nbsp; &nbsp; &nbsp; 0x80000086<br />
&nbsp; &nbsp;0x80000096: &nbsp;auipc &nbsp; a0,0x2<br />
(gdb) si<br />
halted at 0x80000094 due to step<br />
0x80000094 in ?? ()<br />
(gdb) si<br />
halted at 0x80000086 due to step<br />
0x80000086 in ?? ()<br />
(gdb) si<br />
halted at 0x80000088 due to step<br />
0x80000088 in ?? ()<br />
(gdb) si<br />
halted at 0x8000008c due to step<br />
0x8000008c in ?? ()<br />
(gdb) si<br />
halted at 0x80000090 due to step<br />
0x80000090 in ?? ()</span></p>

<p>  上面载入内存的代码是E203代码树中测试指令集的一个程序,看起来,测试内容完成之后就进入一个循环了。单步跟踪的结果是 0x80000086 到 0x80000094 之间循环。</p>

<p>  看一下程序的 dump 内容对比:</p>

<pre>
<code>80000086 &lt;write_tohost&gt;:
80000086:        4521                        li        a0,8
80000088:        30052073                  csrs        mstatus,a0
8000008c:        00001f17                  auipc        t5,0x1
80000090:        f63f2a23                  sw        gp,-140(t5) # 80001000 &lt;tohost&gt;
80000094:        bfcd                        j        80000086 &lt;write_tohost&gt;</code></pre>

<p>  这样的程序没有进行 I/O 操作,不能从硬件上观察到变化。下面需要用程序测试下 GPIO.</p>

cruelfox 发表于 2021-6-18 07:27

<p>  E203 SOC 里的 GPIO 设备寄存器表:</p>

<p>  简单的 LED 操作只需要使能 GPIO 输出 (output_en对应位写1),然后写输出数据寄存器就可以了。GPIO的设备基地址是 <span style="color:#c0392b;">0x10012000</span>,因此直接写个程序来点亮板上的 RGB LED:</p>

<pre>
<code class="language-cpp">#include&lt;stdint.h&gt;

typedef struct{
uint32_t pin;
uint32_t in_en;
uint32_t out_en;
uint32_t odata;
}GPIO_Type;

volatile GPIO_Type * GPIO = (volatile GPIO_Type *)0x10012000;

void micro_delay(void)
{
        int i;
        for(i=0;i&lt;100;i++)
        {
                asm volatile ("nop\n");
                asm volatile ("nop\n");
        }
}

void test(void)
{
        int r,g,b;

        GPIO-&gt;out_en = 7;
        for(;;)
        {
                for(r=0;r&lt;=8;r++)
                {
                        for(g=0;g&lt;=8-r;g++)
                        {
                                int t;
                                b=8-r-g;
                                for(t=0;t&lt;2000;t++)
                                {
                                        int i;
                                        GPIO-&gt;odata = 0x1FE;        // show R
                                        for(i=0;i&lt;r;i++)
                                                micro_delay();
                                        GPIO-&gt;odata = 0x1FD;        // show G
                                        for(i=0;i&lt;g;i++)
                                                micro_delay();
                                        GPIO-&gt;odata = 0x1FB;        // show B
                                        for(i=0;i&lt;b;i++)
                                                micro_delay();
                                }
                        }
                }
        }
}</code></pre>

<p>  变色的效果就是 R/G/B 交替亮的时间不等,并逐渐改变时长造成。至于延时,用最简单的空指令加循环。</p>

<p>&nbsp;</p>

<p>  类比ARM cortex-M0平台的编译方法,用RISC-V的toolchain来干活:</p>

<p>  编译C文件:</p>

<p><span style="color:#c0392b;"><strong>riscv-nuclei-elf-gcc -c -Os led.c -march=rv32imc -mabi=ilp32</strong></span></p>

<p>指定了指令集分支为 RV32IMC, 这里用的指令简单,估计不指定也能工作。ABI指定如果省略了,GCC会报错。</p>

<p>  链接成ELF:</p>

<p><span style="color:#c0392b;"><strong>riscv-nuclei-elf-ld led.o -Ttext 0x80000000</strong></span></p>

<p>指定了代码段的位置,0x80000000 是 E203 SOC 的 ITCM RAM.</p>

<p>  反汇编看程序代码:</p>

<p><span style="color:#c0392b;"><strong>riscv-nuclei-elf-objdump -d a.out</strong></span></p>

<pre>
<code>Disassembly of section .text:

80000000 &lt;micro_delay&gt;:
80000000:       06400793                li      a5,100
80000004:       0001                  nop
80000006:       0001                  nop
80000008:       17fd                  addi    a5,a5,-1
8000000a:       ffed                  bnez    a5,80000004 &lt;micro_delay+0x4&gt;
8000000c:       8082                  ret

8000000e &lt;test&gt;:
8000000e:       7179                  addi    sp,sp,-48
80000010:       d422                  sw      s0,40(sp)
80000012:       80001437                lui   s0,0x80001
80000016:       0a442783                lw      a5,164(s0) # 800010a4 &lt;__global_pointer$+0xfffff800&gt;
8000001a:       c65e                  sw      s7,12(sp)
8000001c:       c462                  sw      s8,8(sp)
8000001e:       c266                  sw      s9,4(sp)
80000020:       c06a                  sw      s10,0(sp)
80000022:       d606                  sw      ra,44(sp)
80000024:       d226                  sw      s1,36(sp)
80000026:       d04a                  sw      s2,32(sp)
80000028:       ce4e                  sw      s3,28(sp)
8000002a:       cc52                  sw      s4,24(sp)
8000002c:       ca56                  sw      s5,20(sp)
8000002e:       c85a                  sw      s6,16(sp)
80000030:       471d                  li      a4,7
80000032:       c798                  sw      a4,8(a5)
80000034:       1fe00b93                li      s7,510
80000038:       1fd00c13                li      s8,509
8000003c:       1fb00c93                li      s9,507
80000040:       4d25                  li      s10,9
80000042:       4921                  li      s2,8
80000044:       4981                  li      s3,0
80000046:       8a4a                  mv      s4,s2
80000048:       4481                  li      s1,0
8000004a:       a82d                  j       80000084 &lt;test+0x76&gt;
8000004c:       7d000a93                li      s5,2000
80000050:       0a442783                lw      a5,164(s0)
80000054:       4b01                  li      s6,0
80000056:       0177a623                sw      s7,12(a5)
8000005a:       033b1c63                bne   s6,s3,80000092 &lt;test+0x84&gt;
8000005e:       0a442783                lw      a5,164(s0)
80000062:       4b01                  li      s6,0
80000064:       0187a623                sw      s8,12(a5)
80000068:       03649863                bne   s1,s6,80000098 &lt;test+0x8a&gt;
8000006c:       0a442783                lw      a5,164(s0)
80000070:       4b01                  li      s6,0
80000072:       0197a623                sw      s9,12(a5)
80000076:       034b4463                blt   s6,s4,8000009e &lt;test+0x90&gt;
8000007a:       1afd                  addi    s5,s5,-1
8000007c:       fc0a9ae3                bnez    s5,80000050 &lt;test+0x42&gt;
80000080:       0485                  addi    s1,s1,1
80000082:       1a7d                  addi    s4,s4,-1
80000084:       fc9954e3                bge   s2,s1,8000004c &lt;test+0x3e&gt;
80000088:       0985                  addi    s3,s3,1
8000008a:       197d                  addi    s2,s2,-1
8000008c:       fba99de3                bne   s3,s10,80000046 &lt;test+0x38&gt;
80000090:       bf4d                  j       80000042 &lt;test+0x34&gt;
80000092:       37bd                  jal   80000000 &lt;micro_delay&gt;
80000094:       0b05                  addi    s6,s6,1
80000096:       b7d1                  j       8000005a &lt;test+0x4c&gt;
80000098:       37a5                  jal   80000000 &lt;micro_delay&gt;
8000009a:       0b05                  addi    s6,s6,1
8000009c:       b7f1                  j       80000068 &lt;test+0x5a&gt;
8000009e:       378d                  jal   80000000 &lt;micro_delay&gt;
800000a0:       0b05                  addi    s6,s6,1
800000a2:       bfd1                  j       80000076 &lt;test+0x68&gt;</code></pre>

<p>因为使用了压缩指令,所以指令长度是32-bit, 16-bit混合的。</p>

<p>  生成 bin 文件:</p>

<p><span style="color:#c0392b;"><strong>riscv-nuclei-elf-objcopy -Obinary a.out led.bin</strong></span></p>

<p>&nbsp;</p>

<p>  然后,在 openocd 中装载这个 .bin 程序文件:</p>

<p><strong><span style="color:#000000;">&gt; halt</span><br />
<span style="color:#999999;">halted at 0x80000008 due to debug interrupt</span><br />
<span style="color:#000000;">&gt; load_image f:/led.bin 0x80000000</span><br />
<span style="color:#999999;">4264 bytes written at address 0x80000000<br />
downloaded 4264 bytes in 2.734375s (1.523 KiB/s)</span></strong></p>

<p>  因为没有初始化代码,需要手动初始化一下堆栈,然后设置PC运行的地址:</p>

<p><span style="color:#000000;"><strong>&gt; reg sp 0x90001000</strong></span></p>

<p><span style="color:#000000;"><strong>&gt; resume 0x8000000e</strong></span></p>

<p>  这样就能从 test() 函数的入口开始运行了,可看到 RGB LED 的变化。</p>

Jacktang 发表于 2021-6-19 09:03

<p>用Vivado显示的 FPGA 资源使用情况,这个功能还是不错的</p>

<p>直接用JTAG连接可以发现RISC-V CPU</p>

<p>谢谢分享</p>

RPM 发表于 2023-5-8 20:28

<p>请问相关工程博主还有保存吗?澎峰那资料下载不了了</p>
页: [1]
查看完整版本: 【Perf-V评测】移植蜂鸟E203开源SOC到FPGA板