IC的帆哥 - E203 https://icfgblog.com/index.php/tag/E203/ zh-CN Thu, 11 Nov 2021 20:14:00 +0800 Thu, 11 Nov 2021 20:14:00 +0800 Hummingbirdv2 E203 移植之引脚约束 https://icfgblog.com/index.php/Digital/293.html https://icfgblog.com/index.php/Digital/293.html Thu, 11 Nov 2021 20:14:00 +0800 icfg66 上文实现了将程序固化到itcm,接下来需要将E203烧写至Xilinx Zynq UltraScale+的板子上,主要需要解决几个问题:

  • 时钟:板子上的时钟是差分时钟,而且32.784kHz的时钟需要自己分频产生;
  • 添加IP:pll和reset ip
  • 管脚约束:需要保留的管脚有JTAG(四个),串口(gpioA[16],gpioA[17]),mcu_wakeup(连高电平)

其中JTAG口是用来调试软件代码,芯来SDK工具可以将二进制软件代码通过JTAG口写入itcm,再通过串口查看打印信息。要实现这一功能,还需要在修改软件的配置信息,参见这篇博文。修改.cfg文件,注释里面包含flash的几行 image.png image.png

一、导入文件

选择器件:如“xczu3eg-sfvc784-1-i” 导入顶层文件system.v,以及约束文件nuclei-master.xdc,并写一个bitstream.tcl文件:

set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
set_property SEVERITY {Warning} [get_drc_checks RTSTAT-1]
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]

否则会因为一些内部管脚没连,导致比特流文件生成出错 image.png

二、修改system.v

IO口

1、CLK100MHZ,CLK32768kHZ 改成sys_clk_p,sys_clk_n,并移入模块做wire型(之后再用pll和分频产生) 2、删除fpga_rst,随便去除后面assign语句中的fpga_rst信号 3、qspi 的相关的6个IO移入模块做wire型,并删除模块内IOBUF对其的定义 4、gpioA、gpioB移出IO,做wire型,并删除模块内IOBUF对其的定义 5、pmu两个信号移出IO,做wire型,并删除模块内IOBUF对其的定义 6、增加两个串口信号tx,rx,之后连接到gpioA[17]和gpioA[16],并在模块内增加IOBUF对其的定义

IP 添加

在Windows环境下用Vivado调试E203可以参考这篇文章添加缺少的mmcm和reset_sys模块。 mmcm实际上就是clock wizard,我这边参考配套手册添加的: image.png

其他重要选项

1、将信号dut_io_pads_bootrom_n_i_ival 连上1‘b0,表示从itcm中写入,非常重要。

三、管脚约束

分别设置时钟、mcu_rst、Jtag、串口、mcu_wakeup管脚。 如果综合出现e203_defines.v的错误,可以再TCL输入:

set_property file_type "Verilog Header" [get_files {your dir}/e203_defines.v]
set_property is_global_include true [get_files {your dir}/e203_defines.v]

代码详情

]]>
0 https://icfgblog.com/index.php/Digital/293.html#comments https://icfgblog.com/index.php/feed/tag/E203/
Hummingbirdv2 E203 移植之固化itcm https://icfgblog.com/index.php/Digital/292.html https://icfgblog.com/index.php/Digital/292.html Thu, 11 Nov 2021 20:04:00 +0800 icfg66 由于项目需求,希望将Hummingbird2 E203 SoC移植到Xilinx Zynq UltraScale+ MPSoC 上,核心板是XCU3EG。期间遇到一个大坑,debug足足用了7个小时,惨不忍睹。

一、Flash不够

芯来自己的MCU200T的开发板,有两个Flash:一个是FPGA Flash,用于保存mcs文件,上电时加载硬件电路,另一个是MCU Flash,用于保存软件二进制代码。芯来有自己的SDK做软件调试,软件代码编译成二进制文件后,有三种方式写入E203的指令存储器(itcm)做调试:

  • ilm: 直接将代码写入指令存储器(itcm),速度最快,但掉电会消失
  • flash:先保存到mcu flash,然后再拷贝到itcm上执行,掉电不消失
  • flashxip:直接保存到mcu flash,并读flash运行,速度较慢

但MPSoC只在ARM核的PS端有下载用的FPGA Flash,没有MCU Flash,如果需要掉电保存软件代码,该怎么办呢?

二、修改itcm

E203的指令存储器是通过寄存器实现的,如果我们把它修改成只读存储器ROM,然后预先读入.coe文件,那么就可以和E203一起烧入flash,这样一上电就可以运行代码。

说干就干,那么用哪种IP呢?BRAM还是Distributed ROM? 这得通过代码仔细分析时序:

//读部分逻辑
    reg [DW-1:0] mem_r [0:DP-1];  //DW=32,DP=8192
    always @(posedge clk)
    begin
        if (ren) begin
            addr_r <= addr;
        end
    end
  assign dout_pre = mem_r[addr_r];

这点挺有意思,当可读时,先一拍保存地址addr_r,然后直接读出数据,而且不可读的时候,输出会保持,这点用rom实现起来挺方便,于是便决定用简单的rom实现(先不管写入功能): image.png

潘多拉的魔盒就此打开,我先用芯来的SDK生成.verilog的代码二进制文件(可以参考这篇文章),然后以.coe文件的方式读入rom。有一点需要注意,.verilog文件还需要调整数据顺序,这里写了一个python脚本,实现转换:

#python hello.verilog
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-f','--file',dest='file',type=str,default='hummibird.verilog')

args = parser.parse_args()
file_data = "memory_initialization_radix=16;\nmemory_initialization_vector=\n"
total_len = 0
str_16 = []
with open(args.file,"r",encoding="utf-8") as f:
    for line in f:
        if(line[0]=="@"):
            temp = eval("0x"+line.strip("\n")[1:])
            for i in range(temp - total_len):
                str_16.append("00")
                total_len += 1
        else:
            temp = line.strip("\n").split(" ")
            for i in range(len(temp)):
                str_16.append(temp[i])
                total_len +=1
    for i in range(8-total_len % 8):
        str_16.append("00")

    row = int(total_len//8)+1
    for i in range(row):
        file_data += str_16[i*8+7]+ str_16[i*8+6]+str_16[i*8+5]+str_16[i*8+4]\
                     +str_16[i*8+3]+str_16[i*8+2]+str_16[i*8+1]+str_16[i*8+0]+",\n"
with open("out.coe","w",encoding= "utf-8") as f:
    f.write(file_data)

三、恶梦debug

看似一切完美,指令存储器不需要写入功能,tb仿真应该一切正常。检测了开始的几个周期,确定指令确实正确写入,但之后却一直不输出结果。好在另一台电脑有备份,跑一波正确的版本,发现2.5us之后,指令输出就出错了,为之奈何?说实话,当时真没勇气搞定这个bug,下了很大决心才决定回溯信号。

从这个output找,到底那个input出了问题,模块之间不停套娃。在这个过程中,还发现一个vivado的彩蛋:vivado和modelsim在仿真时不会保存所有信号,如果要查看其他信号的变化,就需要重新仿真,这一点不如iverilog和vcs方便;但vivado有个彩蛋是,它保存了仿真最后时刻所有信号的信息,而且将鼠标停留在代码上的信号,会显示该信号的数值(如果不显示,可以在scope菜单找到这个例化模块)。 image.png

tip: 仿真到bug出现的时刻点,然后可以在代码中查看此刻各信号的数值,大大加快速度。

第一阶段回溯到了寄存器堆regfile某个地址数据没有对上,接着看它是什么时候发生错误反转,再仿真都这个时刻,最后回溯最终是那个输入发生错误。

经过漫长的回溯,最终找到了数据存储器,scope一看,进入例化模块是rom!原来itcm和dtcm用的是同一个module,这样itcm改成了rom,dtcm也就不能写入!抽丝剥茧,终于搞定。

只需要另外添加一个itcm的module,分类例化即可,问题解决。

四、能不能加入写功能?

itcm的写功能主要用于调试,借助其SDK,通过Jtag口将代码直接传入itcm,方便debug,否则每次修改代码都需要重新烧一遍板子,非常费时费力。

写逻辑也挺有意思,除了常见的写使能we,还有mask信号wem,来确定32bit里面,哪几位8bit需要修改。分析了一下时序,动了些小聪明,发现可以用Dualport RAM实现。 image.png

module sirv_sim_ram_itcm
#(parameter DP = 512,
  parameter FORCE_X2ZERO = 0,
  parameter DW = 32,
  parameter MW = 4,
  parameter AW = 32 
)
(
  input             clk, 
  input  [DW-1  :0] din, 
  input  [AW-1  :0] addr,
  input             cs,
  input             we,
  input  [MW-1:0]   wem,
  output [DW-1:0]   dout
);

 reg [AW-1:0] addr_r;
 wire [MW-1:0] wen;
 wire ren;

 wire [DW-1:0] dout_pre,spo,din_temp,wen_ext;
assign din_temp = (wen_ext & din) | (~wen_ext &spo);  //下一时刻更新数据
 assign wen = ({MW{cs & we}} & wem);
 assign wen_ext={ {8{wen[7]}},{8{wen[6]}},{8{wen[5]}},{8{wen[4]}},{8{wen[3]}},{8{wen[2]}},{8{wen[1]}},{8{wen[0]}}};

    dist_mem_gen_rewr u_rewr(
        .a(addr),           //写地址
        .d(din_temp),   //写数据
        .dpra(addr_r),  //读地址
        .clk(clk),
        .we(we),       //读使能
        .spo(spo),    //写地址的当前数据,需要结合wen和din得到下一时刻的更新数据din_temp
        .dpo(dout_pre)   //读数据
    );

    assign ren = cs & (~we);
    always @(posedge clk)
    begin
        if (ren) begin
            addr_r <= addr;
        end
    end
endmodule

这样就不仅能烧录flash,而且运行时可以修改代码做测试,最后可以将测试好的代码重新烧写进flash,方便开发。

五、能不能用BRAM?

总感觉写功能的实现挺low,是否更快捷的方式?查看xilinx关于ram的说明文档发现bram自带wea功能,非常容易替换。 image.png

module sirv_sim_ram 
#(parameter DP = 512,
  parameter FORCE_X2ZERO = 0,
  parameter DW = 32,
  parameter MW = 4,
  parameter AW = 32 
)
(
  input             clk, 
  input  [DW-1  :0] din, 
  input  [AW-1  :0] addr,
  input             cs,
  input             we,
  input  [MW-1:0]   wem,
  output [DW-1:0]   dout
);
    reg [AW-1:0] addr_r;
    wire [MW-1:0] wen;
    wire ren;
    wire [DW-1:0] dout_pre;
    
    assign ren = cs & (~we);
    assign wen = ({MW{cs & we}} & wem);

    blk_mem_gen_itcm u_dtcm(
        .clka(clk),
        .addra(addr),
        .dina(din),
        .wea(wen),
        .clkb(clk),
        .addrb(addr),
        .doutb(dout_pre),
        .enb(ren)
    );

endmodule

几个特别注意点:

  • itcm的位宽是64bit,长度8192;dtcm的位宽是32bit,长度是16384,这一点在e203_define.v和config.v里面有说明,添加IP的时候要注意这两个不同点。
  • Basic 勾选 Write Enable,Byte Size 选8bits
  • Port A Options ,Enable Port Type 选择“Always Enabled”
  • PortB Options,不要选 Primitives Output Register
  • Summary 确认 PortB Latency为1 Cycles

最后附上这些模块的代码,区分了itcm和dtcm,上层例化的时候要注意,相应的ram ip设置可以参考截图。

]]>
0 https://icfgblog.com/index.php/Digital/292.html#comments https://icfgblog.com/index.php/feed/tag/E203/