三、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;
end
for(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)
begin
if(!lite_mem_cen)
addr_holding[29:0] <= lite_mem_addr[31:2];
end
assign 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 / 1ps
module e906_top(
clk_fpga, //jing zhen clk input 50Mhz
rstn_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 allowed
uart_cfig.parity = PARITY_NONE; // PARITY_NONE / PARITY_ODD / PARITY_EVEN
uart_cfig.stopbit = STOPBIT_1; // STOPBIT_1 / STOPBIT_2
uart_cfig.wordsize = WORDSIZE_8; // from WORDSIZE_5 to WORDSIZE_8
uart_cfig.txmode = ENABLE; // ENABLE or DISABLE
// open UART device with id = 0 (UART0)
ck_uart_open(&uart0, 0);
// initialize uart using uart_cfig structure
ck_uart_init(&uart0, &uart_cfig);
//Section 1: Function
printf("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/100ps
module top();
parameter DATAWIDTH=8;
parameter ADDRWIDTH=17; //byte addrress width
parameter MEMDEPT = 2**(ADDRWIDTH-2); //divided by 4---- 32768
integer 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);
end
initial begin
fid0 = $fopen("case0.pat");
fid1 = $fopen("case1.pat");
fid2 = $fopen("case2.pat");
fid3 = $fopen("case3.pat");
for(i=0;i<MEMDEPT;i=i+1) begin
if((^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");
end
endmodule
以及questaSim运行do脚本
quit -sim
vlib work
vmap work work
vlog -work work top.v
vsim -voptargs=+acc work.top
add 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);
end
always @(posedge PortAClk)
begin
if(PortAWriteEnable)
begin
mem[PortAAddr] <= PortADataIn;
end
else
begin
PortADataOut <= 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上去,便于调试。