三、E906移植----FPGA生成可用的比特流并实现串口发送
三、E906移植----FPGA生成可用的比特流并实现串口发送
书接上回,第二篇把基本工程搭建了起来,跑了下综合看了看。本文就开始具体的修改了,连蒙带猜,修修补补,最终完成了板上串口发送“Hello world !”的功能。本文涉及到很多移植的具体细节,有些通用,有些需要根据不同需求作修改,我尽量把修改的目的讲清楚,不清楚的可以多看源码。
另外,把E906的amba 3.0 soc总线换成AXI4.0就基本上是E907的思路了,然而E907是不开源的,授权才能使用的,所以开源这事还是值得思考的,但是另一方面如果把E906的朝着支持AXI4.0总线的方向修改,也就是比较能够实用、卖钱的方向了。
1. 跑通布局布线、生成比特流
1.1 平台切换成zynq7020,资源对比
先贴个zynq7020资源方便比对
53k lut,106k FF, Block rams 140
上一篇我们原封不动的在zynq7045上跑了一遍,资源占用如下,是够的,如果你的板子芯片资源差不多,那资源优化部分暂时不需要修改,如果你的板子芯片资源不够或者即使够也想手动优化一下资源占用,就需要好好分析各个模块的资源占用了,后面默认是基于zynq7020来继进行修改。
z7045下:
·························
·························
z7020下:
光比较LUT是够的,但是bream是严重不够的。bram不够,编译就会调用lut ram,然而lut ram又需要很多额外的lut,最后是整个资源占用都会爆掉。
1.2 降低bram 资源占用
ram18e 包含 18Kbit 存储资源 —— (8+1)x 2 Kb —— 2KB —— reg[7:0] mem[0:2047]
一个ram18e 差不多是2KB的ram资源,一个ram36 差不多就是4KB。
z7045 下bram占用分布:
需要注意memory占用的几个地方:
i . x_dahb_mem_ctrl 占了192个bram ,是大头,里面挂了的ram相当于所谓的DTCM,紧耦合data ram;
ii. x_iahb_mem_ctrl 占了64个, 是所谓的ITCM,存放指令的紧耦合ram;
iii. x_smem_ctrl 占32个, 是soc外设总线上挂着的ram,相当于模拟外部接入ram;
iv. soc内部控制模块的,指令icache、
v. 数据dcache
vi. 用于分支预测BHT 的ram。
i、ii和iii的修改方式差不多,都是调用同一个memory生成子模块,修改其传入的数据位宽和深度即可;iv、v和 vi 的修改方式也是一致的,都是在头文件 “cpu_cfg.h” 修改对应的宏定义即可。另一个不太好回答的问题是,各部分具体降低多少bram? 我的思路是内部的cache和外部的ram是一种映射关系,最直白的方式就是大家统一乘上1/3或者1/4(默认E906占用bram大概是zynq7020的3倍),但是会造成利用不充分,x_smem_ctrl和x_dahb_mem_ctrl 内的ram其实在现有的例程中没有使用,因此又可以适当给iahb_mem 多加点。
具体修改:
① iv、v和 vi
iv、v和 vi 的修改相对比较集中一些,打开 cpu_cfg.h ,慢慢往下翻,会分别找到 define BHT_8K 、define ICACHE_16K、define DCACHE_16K几个宏定义。这就是这几个cache的占用了,其实也不是很多,一个ram16e是2KB, 一个ram32额是4KB,soc内部的cache一种大概也就占了一二十个bram。这里我们分别注释掉后替换成 define BHT_2K、define ICACHE_4K、define DCACHE_4K 。
保存再跑一下综合:
对比前面的lut 271%和 lut ram 497% 还是有明显下降的,注意10%是整个器件的1/10,53k lut下就降了5k , 5k lut还是能做很多事的;另一方面,大头还是在ITCM、DTCM上。
② x_iahb_mem_ctrl
打开 x_iahb_mem_ctrl 文件,目前占用64个,修改目标是占用降低一半。
首先可以看到 parameter IMEM_WIDTH = 18 的宏定义,从命名上理解应该是定义一个存储深度的量;
翻到末尾,可以看到如下定义
assign lite_mem_dout[31:0] = {ram3_dout[7:0], ram2_dout[7:0], ram1_dout[7:0], ram0_dout[7:0]};soc_fpga_ram #(8, IMEM_WIDTH-2) ram0(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram0_din),.PortAWriteEnable(ram_wen[0]),.PortADataOut(ram0_dout));soc_fpga_ram #(8, IMEM_WIDTH-2) ram1(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram1_din),.PortAWriteEnable(ram_wen[1]),.PortADataOut(ram1_dout));soc_fpga_ram #(8, IMEM_WIDTH-2) ram2(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram2_din),.PortAWriteEnable(ram_wen[2]),.PortADataOut(ram2_dout));soc_fpga_ram #(8, IMEM_WIDTH-2) ram3(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram3_din),.PortAWriteEnable(ram_wen[3]),.PortADataOut(ram3_dout));
再看这个soc_fpga_ram.v是如何描述mem的
parameter DATAWIDTH = 2;
parameter ADDRWIDTH = 2;
parameter MEMDEPTH = 2**(ADDRWIDTH);
reg [(DATAWIDTH-1):0] mem [(MEMDEPTH-1):0];
所以DATAWIDTH 就是8,ADDRWIDTH 是18-2=16,MEMDEPTH 就是65536, 共有4个ram块,
就是2 ^18 Byte = 2**16*4Byte = 65536 * 32b ,
这里插播一下tb.v是如何初始化ITCM的:
initial
begin$display("\t******START TO LOAD PROGRAM******\n");$readmemh("./case.pat", mem_inst_temp);for(i=0;i<65536;i=i+1)begin`RTL_IAHBL_MEM.ram0.mem[i][7:0] = ((^mem_inst_temp[i][31:24]) === 1'bx ) ? 8'b0:mem_inst_temp[i][31:24];`RTL_IAHBL_MEM.ram1.mem[i][7:0] = ((^mem_inst_temp[i][23:16]) === 1'bx ) ? 8'b0:mem_inst_temp[i][23:16];`RTL_IAHBL_MEM.ram2.mem[i][7:0] = ((^mem_inst_temp[i][15: 8]) === 1'bx ) ? 8'b0:mem_inst_temp[i][15: 8];`RTL_IAHBL_MEM.ram3.mem[i][7:0] = ((^mem_inst_temp[i][ 7: 0]) === 1'bx ) ? 8'b0:mem_inst_temp[i][ 7: 0];end
都是65536 ,是不是就恰好对上了!
另外这里提醒一下,tb的ram0对应的是[31:24]是高8bit,后面重写soc_fpga_ram模块时会有用。
回到主题上来,x_iahb_mem_ctrl.v里parameter IMEM_WIDTH = 18 改成17,占用就减半了,
另外有两个地方就是addr_hoding信号和ram_addr两个信号的位宽都定义成16位,会发现和IMEM_WIDTH 紧相关,所以也对应改成15位,其他模块大体上完成的功能就是在ahb总线上进行读写响应来实现系统与模块内ram的数据交互。
再跑综合:
又降了不少。
③ dahb_mem_ctrl
dahb_mem_ctrl本来是占用大头,放到后面主要是因为目前还不是很明白其中一些写法的用意,整个模块的功能和iahb_mem_ctrl差不多,都是通过apb_lite总线来与外部进行数据交互。
文件开局也定义了一个parameter DMEM_WIDTH = 20 ,但是整个文件没有出现第二次,拉到文件末尾会发现传入参数默认是写固定了的,共定义了8个ram块,ram0-ram3构成一组是17bit地址位,而ram4-ram7构成另一组地址是16bit,通过地址ram_addr[17]也就是18位来分别选通使能两组memory。
// memory unit is in DPTHx8 size, 4 units are instanced
soc_fpga_ram #(8, 17) ram0(.PortAClk (ram_clk),.PortAAddr(ram_addr[16:0]),.PortADataIn (ram0_din),.PortAWriteEnable(ram_wen[0]),.PortADataOut(ram0_dout));soc_fpga_ram #(8, 16) ram4(.PortAClk (ram_clk),.PortAAddr(ram_addr[15:0]),.PortADataIn (ram0_din),.PortAWriteEnable(ram_wen[4]),.PortADataOut(ram4_dout));
其实我不是很明白这种写法的用意,是为了模拟多个sram块吗? 打开tb.v ,可以看到相关初始化操作:
for(i=0;i<=65536;i=i+1)begin`RTL_DAHBL_MEM.ram0.mem[i][7:0] = 8'b0;`RTL_DAHBL_MEM.ram1.mem[i][7:0] = 8'b0;`RTL_DAHBL_MEM.ram2.mem[i][7:0] = 8'b0;`RTL_DAHBL_MEM.ram3.mem[i][7:0] = 8'b0;endfor(i=0;i<=65536;i=i+1)begin`RTL_DAHBL_MEM.ram4.mem[i][7:0] = 8'b0;`RTL_DAHBL_MEM.ram5.mem[i][7:0] = 8'b0;`RTL_DAHBL_MEM.ram6.mem[i][7:0] = 8'b0;`RTL_DAHBL_MEM.ram7.mem[i][7:0] = 8'b0;end
可以看出:①例程代码是没用DTCM的,都是赋初始值0; ②ram4-ram7是6553648bit 和RTL是对应的。
但是ram0-ram3的操作是对不上的,RTL给的是17bit,初始化只做了一半16bit,这点也是比较疑惑的。
但是好在,跑hello_world可能暂不需要DTCM,所以和上小节一样套路修改也没什么问题。
具体操作:
首先是把宏定义 DMEM_WIDTH = 20 用上,方便修改,就是 dahb_mem_ctrl 文件末尾涉及到memory的四五个模块中有关地址的地方,如 17 用 DMEM_WIDTH -3代替 ,16用DMEM_WIDTH -4代替,我下面贴一下改过后的后面代码,然后再把DMEM_WIDTH 改成19 ,memory占用就减半了,另外和上节不同的是文件开始定义的wire [29:0] ram_addr 就不用改了,明显这是把2^32 byte的索引 弄成2**30 * 32bit的索引。
//memory
always @(posedge pll_core_cpuclk)
beginif(!lite_mem_cen)addr_holding[29:0] <= lite_mem_addr[31:2];
endassign ram_clk = pll_core_cpuclk;
assign ram_addr[29:0] = lite_mem_cen ? addr_holding[29:0] : lite_mem_addr[31:2];
assign ram_wen[0] = (!lite_mem_cen && !lite_mem_wen[0]) && !ram_addr[DMEM_WIDTH -3];
assign ram_wen[1] = (!lite_mem_cen && !lite_mem_wen[1]) && !ram_addr[DMEM_WIDTH -3];
assign ram_wen[2] = (!lite_mem_cen && !lite_mem_wen[2]) && !ram_addr[DMEM_WIDTH -3];
assign ram_wen[3] = (!lite_mem_cen && !lite_mem_wen[3]) && !ram_addr[DMEM_WIDTH -3];assign ram_wen[4] = !lite_mem_cen && !lite_mem_wen[0] && ram_addr[DMEM_WIDTH -3];
assign ram_wen[5] = !lite_mem_cen && !lite_mem_wen[1] && ram_addr[DMEM_WIDTH -3];
assign ram_wen[6] = !lite_mem_cen && !lite_mem_wen[2] && ram_addr[DMEM_WIDTH -3];
assign ram_wen[7] = !lite_mem_cen && !lite_mem_wen[3] && ram_addr[DMEM_WIDTH -3];assign ram0_din[7:0] = lite_mem_din[7:0];
assign ram1_din[7:0] = lite_mem_din[15:8];
assign ram2_din[7:0] = lite_mem_din[23:16];
assign ram3_din[7:0] = lite_mem_din[31:24];
assign lite_mem_dout[31:0] = addr_holding[DMEM_WIDTH -3] ? {ram7_dout[7:0], ram6_dout[7:0], ram5_dout[7:0], ram4_dout[7:0]}: {ram3_dout[7:0], ram2_dout[7:0], ram1_dout[7:0], ram0_dout[7:0]};// memory unit is in DPTHx8 size, 4 units are instanced
soc_fpga_ram #(8, DMEM_WIDTH -3) ram0(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -4:0]),.PortADataIn (ram0_din),.PortAWriteEnable(ram_wen[0]),.PortADataOut(ram0_dout));soc_fpga_ram #(8, DMEM_WIDTH -3) ram1(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -4:0]),.PortADataIn (ram1_din),.PortAWriteEnable(ram_wen[1]),.PortADataOut(ram1_dout));soc_fpga_ram #(8, DMEM_WIDTH -3) ram2(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -4:0]),.PortADataIn (ram2_din),.PortAWriteEnable(ram_wen[2]),.PortADataOut(ram2_dout));soc_fpga_ram #(8, DMEM_WIDTH -3) ram3(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -4:0]),.PortADataIn (ram3_din),.PortAWriteEnable(ram_wen[3]),.PortADataOut(ram3_dout));soc_fpga_ram #(8, DMEM_WIDTH -4) ram4(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -5:0]),.PortADataIn (ram0_din),.PortAWriteEnable(ram_wen[4]),.PortADataOut(ram4_dout));soc_fpga_ram #(8, DMEM_WIDTH -4) ram5(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -5:0]),.PortADataIn (ram1_din),.PortAWriteEnable(ram_wen[5]),.PortADataOut(ram5_dout));soc_fpga_ram #(8, DMEM_WIDTH -4) ram6(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -5:0]),.PortADataIn (ram2_din),.PortAWriteEnable(ram_wen[6]),.PortADataOut(ram6_dout));soc_fpga_ram #(8, DMEM_WIDTH -4) ram7(.PortAClk (ram_clk),.PortAAddr(ram_addr[DMEM_WIDTH -5:0]),.PortADataIn (ram3_din),.PortAWriteEnable(ram_wen[7]),.PortADataOut(ram7_dout));
再跑一下综合,可以看到现在是够用了,但是lut 与lut ram占用有点过高,这会导致后续的时序跑不起来。
再看lut ram主要分布模块:
还是这个 dahb_mem_ctrl,所以 再减1/2,把DMEM_WIDTH 改成18,综合后如下:
bram 都没有用完,lut占用70%左右,差不多是个合适区间吧,后面那个x_smem_ctrl也懒得改了。
至此,综合部分算是基本调完了。
1.3 添加时钟块,约束引脚与时序,完成 Implemention 与 Generate Bitstream步骤
top文件只是调用了soc模块,时钟IP没添加,复位、uart引脚没绑定。。。。。。
① 添加时钟IP
悲观起见,输出50M、80M、100M 这3路时钟:
时钟例化进top文件中去,引用一下pll输出时钟。
②引脚约束
创建xcd约束文件,根据自己的板子约束对应引脚,提供一份仅供参考:
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]#timming
create_clock -period 20 -name clk50M -waveform {0 10} [get_ports clk_fpga]#IO
#clk_fpga
set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { clk_fpga }]
#rstn
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports rstn_fpga ]##### MCU JTAG define ######set_property PACKAGE_PIN R17 [get_ports jtg_clk]
#set_property PACKAGE_PIN C20 [get_ports jtg_trstn]
set_property PACKAGE_PIN P19 [get_ports jtg_tdi]
set_property PACKAGE_PIN R16 [get_ports jtg_tdo]
set_property PACKAGE_PIN N18 [get_ports jtg_tms]#set_property IOSTANDARD LVCMOS33 [get_ports jtg_clk]
#set_property IOSTANDARD LVCMOS33 [get_ports jtg_trstn]
set_property IOSTANDARD LVCMOS33 [get_ports jtg_tdo]
set_property IOSTANDARD LVCMOS33 [get_ports jtg_tdi]
set_property IOSTANDARD LVCMOS33 [get_ports jtg_tms]set_property KEEPER true [get_ports jtg_tms]
#set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets jtg_clk_IBUF] ##### UART #####
## UART TX
set_property PACKAGE_PIN P15 [get_ports uart_tx_fpga ]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx_fpga]## UART RX
set_property PACKAGE_PIN P16 [get_ports uart_rx_fpga]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rx_fpga]##### GPIO port A #####
#
#A7:
#A6:
#A5: key_in PL key1
#A4: PL_LED1
#A3: PL_LED0
#A2:
#A1:
#A0:
set_property PACKAGE_PIN M18 [get_ports {gpio_porta[7]}]
set_property PACKAGE_PIN M17 [get_ports {gpio_porta[6]}]
set_property PACKAGE_PIN J20 [get_ports {gpio_porta[5]}]
set_property PACKAGE_PIN H18 [get_ports {gpio_porta[4]}]
set_property PACKAGE_PIN J18 [get_ports {gpio_porta[3]}]
set_property PACKAGE_PIN M20 [get_ports {gpio_porta[2]}]
set_property PACKAGE_PIN D20 [get_ports {gpio_porta[1]}]
set_property PACKAGE_PIN K14 [get_ports {gpio_porta[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_porta[0]}]##ignore unconnected pins
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.UNUSEDPIN Pullup [current_design]
以及top文件:
`timescale 1ns / 1psmodule e906_top(clk_fpga, //jing zhen clk input 50Mhzrstn_fpga, uart_tx_fpga,uart_rx_fpga,gpio_porta,jtg_tdi,jtg_tdo,jtg_tms);input clk_fpga;input rstn_fpga;input uart_rx_fpga;output uart_tx_fpga;inout[7:0] gpio_porta;input jtg_tdi;output jtg_tdo;input jtg_tms ; wire clk_soc;
clk_wiz_0 u_clk_wiz_0(// Clock out ports.clk_out1( clk_soc ), //50M.clk_out2( ), //80M.clk_out3( ), //100M// Clock in ports.clk_in1( clk_fpga ));//instantiate soc
soc u_soc(.i_pad_clk ( clk_soc ),.i_pad_uart0_sin ( uart_rx_fpga ),.o_pad_uart0_sout ( uart_tx_fpga ),.i_pad_jtg_tclk ( clk_soc ),.i_pad_jtg_trst_b ( rstn_fpga ),.i_pad_jtg_nrst_b ( rstn_fpga ),.b_pad_gpio_porta ( gpio_porta ),.i_pad_jtg_tdi ( jtg_tdi ),.o_pad_jtg_tdo ( jtg_tdo ),.i_pad_jtg_tms ( jtg_tms ), .i_pad_rst_b ( rstn_fpga )
);endmodule
③ 先跑布局布线
等待一二十分钟后,查看timing,
可以看到50M时钟下,setup time slack 仅为0.06ns,关键路径逻辑层级来到26,速度基本也就到头了。
但是就速度来说不得不说的是,①ch2601 芯片能跑200Mhz ②平台换成z7045 同样是-2等级,速度能上80Mhz。 说明器件速率和资源占用都会影响最终速率(严谨点可能z7045的-2比起z7020的-2速率也有提升,没去实际看手册比较)。
④ 跑比特流
跑一下比特流生成,修改报错,确保整个流程是通畅的,方便后面正式修改。
···························································
································手动分割···············
···························································
2. 修改结构、加载程序、修改例程运行串口输出
经过这么久的铺垫,基本平台算是搭建完成,就可以慢慢深入了。
这一节涉及到软硬件交互协调,实际调试过程应该是硬件-软件-硬件-软件多次交互,一点点改动适配的过程,为了方便写,我这里会先介绍一下总体要干那些事、需要哪些文件、要改哪些部分,然后再分别从软件、硬件两方面把要修改的逐一罗列出来。
2.1 必要步骤
要把基本的程序跑起来,我们要做的事情大致包括:生成硬件平台,生成软件代码,然后软件装载进硬件。
关于硬件,确切的说是可综合的、可自动加载软件程序的、行为与tb中的激励行无本质差别的RTL构成的硬件平台。参照tb的行为描述:
一是前面搭建的基本硬件平台存在最致命的问题——无法加载软件代码进系统,对应的就是tb中读入文件并循环赋值初始化iahb_mem_ctrl中的memory那部分,而且E906相较其他更为复杂一点的是,它的32bit的ram是由4个8bit的soc_fpga_ram模块并行起来组成的,所以不能一次性把软件二进制数据读入reg数组中,不能像仿真那样一个一个循环给数据进ram;所以需要手动将tb中那个读入的文件分割成4份,分别装入ram块中。
二是电源管理pmu模块,tb中进行了强制赋值操作,可能有影响可能没有,改了肯定可以,不改行不行将在最后解答(因为第一次调试时,先发现的这个没有修改,后才进一步发现内存初始化步骤有问题)。
关于软件,一是官方没有直接的uart测试例程,但是提供了uart的库函数,需要读懂并添加调用;二是可以先在smart_run上跑通验证功能正确性,而不必每次都上板验证,但是又要注意跑仿真验证和实际板上的区别。
2.2 软件代码
借助smart_run平台的hello_world例程,备份后自行修改,可以参考下面我的:
#include "datatype.h"
#include "stdio.h"
#include "uart.h"t_ck_uart_device uart0 = {0xFFFF};//sys 50Mhz
void delay_ms(int time){int i,j;for(i=0;i<time;i++){for(j=0;j<50000;j++){;}}
}int main (void)
{int a;//--------------------------------------------------------// setup uart//--------------------------------------------------------t_ck_uart_cfig uart_cfig;uart_cfig.baudrate = BAUD; // any integer value is alloweduart_cfig.parity = PARITY_NONE; // PARITY_NONE / PARITY_ODD / PARITY_EVENuart_cfig.stopbit = STOPBIT_1; // STOPBIT_1 / STOPBIT_2uart_cfig.wordsize = WORDSIZE_8; // from WORDSIZE_5 to WORDSIZE_8uart_cfig.txmode = ENABLE; // ENABLE or DISABLE// open UART device with id = 0 (UART0)ck_uart_open(&uart0, 0);// initialize uart using uart_cfig structureck_uart_init(&uart0, &uart_cfig);//Section 1: Functionprintf("printf test!\n");//Section 2: Uart
while(1){for(a=0;a<10;a++){ck_uart_putc(&uart0, a+48);delay_ms(10);}
}return 0;
}
然后 make runcase CASE=helloworld CASE=vcs命令,执行仿真,查看仿真结果。
根据前面踩的坑,这里有三点有必要着重说明一下:
i. 仿真结果时间比程序预设执行时间慢很多,从printf test! 到后面的01234567…之间我等待了至少5分钟。
ii. 硬件工程中的uart_mnt()模块是具备监测uart引脚状态的功能的,尽管是仿真代码,但是是一个脉冲一个脉冲的把uart 的sout信号解析出来的。
iii. 程序里的printf()函数目前还没有重映射,所以实际上该函数是调用的PC端上提供的打印功能,只有第二排开始后面的红色字体才是真正的uart输出经过解析后的值,且是正确的。 所以会出现第一行打印很快,第二行打印很慢,因为后面是真的在一个指令一个指令的模拟仿真,而第一行直接调用后台输出。
打印成功后,最终想要的只是work目录下的 case.pat文件:
2.3 pat文件拆分
可以看到case.pat文件的数据默认每32bit(4byte)存放一个地址(@地址),我们需要将其拆分成4个文件,每个文件分别存放其中所有行的字节0、字节1、字节2、字节3,然后将未用到的地址对应的数据补0 。
我这里使用 questasim来进行数据的转换操作,贴一下参考文件,自己注意路径根据实际更改:
操作文件:
//split .pat file into 4 files
`timescale 1ns/100psmodule top();parameter DATAWIDTH=8;
parameter ADDRWIDTH=17; //byte addrress width
parameter MEMDEPT = 2**(ADDRWIDTH-2); //divided by 4---- 32768integer fid0,fid1,fid2,fid3;
reg[31:0] i;
reg[31:0] mem_temp [(MEMDEPT-1):0]; initial
begin $readmemh("/home/ICer/ic_prjs/opene906-main/smart_run/work/case.pat",mem_temp);
endinitial beginfid0 = $fopen("case0.pat");fid1 = $fopen("case1.pat");fid2 = $fopen("case2.pat");fid3 = $fopen("case3.pat");for(i=0;i<MEMDEPT;i=i+1) beginif((^mem_temp[i][31:24]) === 1'bx ) $fdisplay(fid3,"@%h %h",i,8'h00);else $fdisplay(fid3,"@%h %h",i,mem_temp[i][31:24]);if((^mem_temp[i][23:16]) === 1'bx ) $fdisplay(fid2,"@%h %h",i,8'h00);else $fdisplay(fid2,"@%h %h",i,mem_temp[i][23:16]);if((^mem_temp[i][15:8]) === 1'bx ) $fdisplay(fid1,"@%h %h",i,8'h00);else $fdisplay(fid1,"@%h %h",i,mem_temp[i][15:8]);if((^mem_temp[i][7:0]) === 1'bx ) $fdisplay(fid0,"@%h %h",i,8'h00);else $fdisplay(fid0,"@%h %h",i,mem_temp[i][7:0]);end$fclose("case0.pat"); $fclose("case1.pat"); $fclose("case2.pat"); $fclose("case3.pat");
endendmodule
以及questaSim运行do脚本
quit -sim
vlib work
vmap work work
vlog -work work top.v
vsim -voptargs=+acc work.topadd wave -color Green -height 20 /*
run 1000000 ns
具体操作:
① 在smart_run路径下新建一个文件夹,把上述两个文本文档复制进去。
②该目录下打开终端,输入vsim进入questaSim仿真器,运行do脚本
③得到输出结果文件
2.4 硬件更改
实在懒得解释了,看一遍应该都能理解。
① ram初始化适配
i. 在FPGA的/source/logical/mem的目录下,把soc_fpga_ram.v复制一份,重命名为soc_fpga_ram2.v;
ii. 编辑soc_fpga_ram2.v文件,主要区别在于多了一个字符串宏参数FILE,方便外部调用时调用不同文件。
module soc_fpga_ram2(PortAClk,PortAAddr,PortADataIn,PortAWriteEnable,PortADataOut
);
parameter DATAWIDTH = 2;
parameter ADDRWIDTH = 2;
parameter FILE="file.pat";
parameter MEMDEPTH = 2**(ADDRWIDTH);input PortAClk;
input [(ADDRWIDTH-1):0] PortAAddr;
input [(DATAWIDTH-1):0] PortADataIn;
input PortAWriteEnable;
output [(DATAWIDTH-1):0] PortADataOut; reg [(DATAWIDTH-1):0] mem [0:(MEMDEPTH-1)];
reg [(DATAWIDTH-1):0] PortADataOut;initial
begin$readmemh(FILE, mem);
endalways @(posedge PortAClk)
beginif(PortAWriteEnable)beginmem[PortAAddr] <= PortADataIn;endelsebeginPortADataOut <= mem[PortAAddr];end
end
endmodule
iii. 修改iahb_mem_ctrl.v最后几行调用ram的代码,如下,注意:传递路径根据实际写 ;ram0----pat3对应关系。
soc_fpga_ram2 #(8, IMEM_WIDTH-2,"/home/ICer/ic_prjs/opene906-main/smart_run/create_patfile/case3.pat") ram0(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram0_din),.PortAWriteEnable(ram_wen[0]),.PortADataOut(ram0_dout));soc_fpga_ram2 #(8, IMEM_WIDTH-2,"/home/ICer/ic_prjs/opene906-main/smart_run/create_patfile/case2.pat") ram1(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram1_din),.PortAWriteEnable(ram_wen[1]),.PortADataOut(ram1_dout));soc_fpga_ram2 #(8, IMEM_WIDTH-2,"/home/ICer/ic_prjs/opene906-main/smart_run/create_patfile/case1.pat") ram2(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram2_din),.PortAWriteEnable(ram_wen[2]),.PortADataOut(ram2_dout));soc_fpga_ram2 #(8, IMEM_WIDTH-2,"/home/ICer/ic_prjs/opene906-main/smart_run/create_patfile/case0.pat") ram3(.PortAClk (ram_clk),.PortAAddr(ram_addr),.PortADataIn (ram3_din),.PortAWriteEnable(ram_wen[3]),.PortADataOut(ram3_dout));
iiii. 打开FPGA工程,把soc_fpga_ram2 .v文件添加进源文件中,重新综合、布局布线、生成比特流。
这里又遇到个坑,前面新建工程时,千万不要勾选copy源文件进工程,否则修改是分开的,不同步,很麻烦。
第一次下载验证,此时未修改pmu中的相关信号电平,通过ch340模块接上串口助手,波特率调到9600,
结果显示——串口上位机有信号!!
如果接收有乱码,一是波特率设置减半问题。二是串口发送太快,上位机接收后把两个字符合成一个扩展ASCII,这种情况最好就是相邻uart发送字符串加个延时函数,然后最好是再发送个“\n”字符,因为上位机软件的uart缓存机制,大多都是读一行进行一次触发处理(可以参看我前面分享的一个QT写的串口上位机)。
为什么c文件中舒适化波特率19200,这里实际接收选择9600?因为,E906默认的时钟频率是100Mhz,从uart_mnt模块以及c代码库中的config.h也能找到相关描述。但是我们实际给的是50Mhz,soc系统默认是无法察觉自身时钟频率的,因此实际最终波特率应该减半!
② pmu中的信号修改
暂时不做修改,理由见上。但是把相关信号的具体位置记录一下,以便后续可能需要用到。
corec_pmu_sleep_out
pmu_corec_isolation
pmu_corec_sleep_in
corec_pmu_sleep_out 在cpu_sub_system_ahb 定义 output
添加 assign =0;pmu.v中
corec_pmu_sleep_out拉高 -->状态机进入PG_POWER_OFF -> pmu_corec_sleep_in 和pmu_corec_isolation拉高 没有其他模块直接交互
2.5 上板验证
见2.4结果,不再赘述,至此基本跑通整个移植过程。
3.总结
这应该是移植最核心的一篇,内容也比较多,做完后我觉得算是入门了。还是那句话,自己看官方源码才是最终解决方案。因为分享经验时效性不好说的,官方指不定后面更新一下源码库,有些直接复制过去就用不了。
这里留了一个小悬疑,就是printf函数的问题,其实这是我踩的一个大坑,因为仿真的时候总是发现调试窗口有输出的,我误以为也是uart的解析输出,所以明明自己写的测试c例程实际上没有通过uart发送数据,调试时被判定发送了,排错浪费了些时间。下一篇我将介绍如何在平台上将printf进行重映射到uart上去,便于调试。
三、E906移植----FPGA生成可用的比特流并实现串口发送相关推荐
- 2019.9.10 Xilinx FPGA Zynq 通过FPGA Manager加载比特流
使用/dev/ 字符设备加载比特流本文参考:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841750/Solution+Zynq+P ...
- Vivado 2018.3入门教程(三):生成比特流文件+硬件连接
这是我的Vivado 2018.3入门教程(三):生成比特流文件+硬件连接 我的Vivado 2018.3入门教程大致分为四个部分: Vivado 2018.3入门教程(一):创建工程+新建源文件. ...
- 【常见 error】Vivado生成比特流时报错[DRC NSTD-1]和[DRC UCIO-1]
目录 问题描述 错误信息翻译 解决办法 添加约束 执行TCL命令 更改顶层选项 问题描述 今天在生成工程的比特流时,出现如下的报错信息,经过上网查询相关错误的解决方案,以及自己摸索,发现可能是如下错误 ...
- 二、E906移植----项目解读与FPGA基本工程搭建
二.E906移植----项目解读与FPGA基本工程搭建 上篇介绍了基本的环境搭建,并跑了一下官方例程,查看了一下仿真波形.本篇则主要讲一下官方的例程是如何跑通的,涉及到哪些必要步骤,以及对应步骤具体是 ...
- FPGA初学(1)-- Vivado创建工程、管脚分配、综合、布局布线、生成比特流教程(详细)
前言: vivado的安装包.安装教程看这篇: Vivado安装包下载.安装教程 安装好vivado后,桌面会多出这三个文件: 其中,vivado HLS可以将C语言转化为RTL级实现,主要用于一些高 ...
- FPGA工具vivado中约束文件格式错误导致比特流生成失败
在vivado中生成比特流时,综合和实现都能成功,在最后一步bit 的生成出现报错. 检查代码.约束都没发现什么错误. 最后发现约束文件有一个小小的格式错误,改正后,bit流成功生成 改正前(T11后 ...
- 三叔学FPGA系列之二:Cyclone V中的POR、配置、初始化,以及复位
对于FPGA内部的复位,之前一直比较迷,这两天仔细研究官方数据手册,解开了心中的诸多疑惑,感觉自己又进步了呢..... 原创不易,转载请转原文,注明出处,谢谢. 一.关于POR(Power-On ...
- 基于芯科Host-NCP解决方案的Zigbee 3.0 Gateway技术研究(三)-移植到ESP32平台(1)
相关系列文章 基于芯科Host-NCP解决方案的Zigbee 3.0 Gateway技术研究(-)-Z3GatewayHost应用搭建 基于芯科Host-NCP解决方案的Zigbee 3.0 Gate ...
- Zynq 开发板FPGA比特流文件下载方式
Zynq开发板FPGA比特流文件可以通过三种途径下载: 1. 利用SDK生成的FSBL.elf文件自动加载FPGA比特流配置文件,将比特流文件,FSBL.elf文件和u-boot.elf文件利用SDK ...
最新文章
- 常用的6款Java开源报表制作工具
- C#如何在钉钉开发平台中创建部门
- 【Mysql Docker】备份 docker mysql 脚本
- JPA保存数据异常:org.hibernate.AnnotationException: @COLUMN(s) NOT allowed ON a @ManyToOne property
- spoj Longest Common Substring II
- 将数组作为参数,调用该函数时候给的是数组地址还是整个数组
- 深度模型压缩论文(01)- Meta Filter Pruning to Accelerate Deep Convolutional Neural Networks
- fb50 sap 报记账码未定义_XX项目SAP关键用户培训固定资产
- catia螺纹孔在二维图上不显示_螺钉的螺纹是搓牙加工的,那么螺母的螺纹是怎么加工的呢...
- pandas颠倒dataframe与series的顺序
- Spring MVC开发步骤以及执行流程
- 从零开始学习前端JAVASCRIPT — 14、闭包与继承
- Linux下一种高效多定时器实现,Linux下一种高效多定时器实现
- usb接口的可变焦相机 对应没有彩虹的投影仪
- oracle asm空间利用率,ASM磁盘组状态和使用率的监控
- redis6源码阅读之八(rax)
- 保研一年来的心路历程
- edge浏览器突然不能播放视频解决办法
- 计算机视觉与深度学习基本环境安装
- JIEDU7种EMI电磁屏蔽材料介绍