基于flash的FPGA的在线升级

一、理论

1.1 在线升级概念

在线升级是指通过网络或其他远程方式对软件、固件或系统进行更新和升级的过程。FPGA的在线升级是指在运行时对FPGA芯片中的逻辑配置进行更新或修改,而无需物理更换芯片。一般开发阶段,开发人员常用JTAG对FPGA进行配置,用于工程的功能修改\调试\更新。但当投入为产品时,想要进行FPGA的固件更新,再通过JTAG来配置FPGA显然是比较麻烦的,所以需要在线升级功能。

1.2 FPGA的配置方式

不同型号和系列的FPGA可能会支持不同的配置模式,这里以7系列FPGA为例,其支持以下几种配置方式:

图1.1 7系列fpga配置方式

对常用的方式作简介:

Master SPI 配置模式:通过SPI总线将配置文件从外部Flash中加载到FPGA内部。这种配置模式可以使用SPI Flash作为配置存储器。在这种模式下,FPGA器件将作为SPI总线的主设备,控制SPI Flash的读取和写入操作。本文基于flash的FPGA在线升级在硬件上正是采用这一种配置方式。Slave Serial 配置模式:通常使用另外的微处理器通过一个串行接口,将配置数据从主控芯片发送到 FPGA配置接口,此方式FPGA被动进行配置。SelectMAP 配置模式:是通过JTAG接口将配置文件从外部Flash中加载到FPGA内部。这种配置模式可以使用JTAG接口实现FPGA的配置。JTAG配置模式:JTAG配置默认最高优先级,在M[2:0]的任何选择下,优先JATG进行配置。

1.3 配置文件bit、bin、mcs

Bitstream(bit):Bitstream是一种二进制文件格式,用于存储FPGA的配置信息。它包含了FPGA逻辑元件的连接和功能等详细信息,以及配置所需的时序和逻辑设置。Bitstream文件通常由FPGA开发工具生成,并通过不同的配置方式(如JTAG、SPI等)加载到FPGA中。 Binary(bin):Binary也是一种二进制文件格式,但与Bitstream不同,它通常是指纯粹的二进制数据文件,没有特定的FPGA配置结构。在某些情况下,可以将FPGA的配置数据导出为二进制文件,这样可以方便地进行备份、传输或其他处理。 MCS(Motorola S-record):MCS是Motorola S-record文件的缩写,是一种常见的文本文件格式(ASCII文件),用于存储数据和程序代码。在FPGA领域,MCS文件通常用于存储FPGA的配置数据。MCS文件包含了地址、数据和校验等信息,可以用于直接编程或烧录FPGA。

总结:bit和bin都是二进制文件,但bit是带有头信息的配置文件,bin文件是不带头信息的的配置文件,如图所示,就前面一部分配置信息不一样,其他的都一样。mcs是ASCII文件,其中两个ASCII字符用于表示数据的每个字节HEX文件,mcs文本结构可参考这篇博客(https://blog.csdn.net/hanhailong0525/article/details/122382501)。

图1.2 bit文件和bin文件16进制对比

二、方案设计及实现

2.1 目标

上位机能够将新的配置文件.mcs更新到FPGA的外部存储器件flash中,从而实现FPGA的在线升级。

2.2 实现方案

上位机通过pcie总线(并行)与FPGA相连,FPGA通过spi总线(串行)与flash相连。首先,上位机对.mcs文件进行预处理,把预处理后的文件数据传到FPGA中,然后FPGA将数据以并转串的方式写入flash中,更换flash里旧的配置文件,从而完成在线升级功能。其中spi_B为FPGA自我配置的专用spi引脚;spi_A为FPGA的普通IO口,用于FPGA向flash写.mcs文件数据的spi通道。

图2.1 在线升级实现方框图

2.3 硬件设计

图2.2 7系列FPGA spi配置接口

硬件上FPGA配置方式选择Master SPI 配置模式,即M[2:0]为3’b001。由于FPGA上电启动,通过FPGA专用的spi引脚对flash读取,完成自身配置,配置完成之后FPGA专用引脚的CCLK便不能当做普通IO口使用,所以选择多路复用器进行spi通道选择。当需要在线升级功能时,选择spi_A通道,将.mcs文件并转串写入FLASH中;在完成在线升级后,再将多路复用器切换到spi_B通道。多路复用器选型为6通道、1:2、多路复用/复解器的器件TS3A27518ERTWR。FLASH由于项目要求,选择读写操作类似于M25P16存储器的一个国产芯片GD25B128ESIG,容量为16MB。

2.4 逻辑设计

FPGA的任务是将上位机下传的预处理后的.mcs文件数据进行并转串处理,然后写入到flash中。设计中上位机将配置文件一个Byte一个Byte的下传,FPGA每接收到一个Byte,便将其并转串,通过spi写入到flash中。因此逻辑上需要设计spi逻辑接口模块,由上位机控制。本质上实现上位机对flash进行读写操作,而FPGA只起着数据转发的作用。 spi接口逻辑设计模块如下:

spi_interface.v

`timescale 1ns / 1ps

module spi_interface(

input ADSP_CLK, //系统时钟

input clk_1mhz, //生成spi cclk时钟,

input wr_start, //上位机控制spi写

input rd_start, //上位机控制spi读

input spi_miso, //spi miso

input [7:0] data_to_flash, //上位机要写入flash的Byte数据

output [7:0] data_to_dsp, //从flash读取要传入上位机的Byte数据

output spi_mosi, //spi mosi

output reg spi_sck //spi sck

);

wire spi_miso;

wire spi_mosi;

wire wr_clk;

wire rd_clk;

always @ (posedge ADSP_CLK) begin

if(wr_start == 1'b1) begin

spi_sck <= wr_clk;

end

else if(rd_start == 1'b1) begin

spi_sck <= rd_clk;

end

end

//spi写子模块

spi_write_data spi_write_data_inst(

.adsp_clk (ADSP_CLK) ,

.ref_freq (clk_1mhz) ,

.wr_start (wr_start) ,

.data_to_flash (data_to_flash) ,

.wr_clk (wr_clk) ,

.spi_mosi (spi_mosi)

);

//spi读子模块

spi_read_data spi_read_data_inst(

.adsp_clk (ADSP_CLK) ,

.ref_freq (clk_1mhz) ,

.rd_start (rd_start) ,

.spi_miso (spi_miso) ,

.rd_clk (rd_clk) ,

.data_to_dsp (data_to_dsp)

);

endmodule

spi_read_data.v

`timescale 1ns / 1ps

module spi_read_data(

adsp_clk,

ref_freq,

rd_start,

spi_miso,

rd_clk,

data_to_dsp

);

parameter data_width = 8;

parameter cnt_bit = 3;

input adsp_clk;

input ref_freq;

input rd_start;

input spi_miso;

output rd_clk;

output [data_width-1:0] data_to_dsp;

reg ref_freq_reg;

reg [cnt_bit:0] cnt_clk;

reg ref_freq_en;

reg ref_freq_en1;

reg [cnt_bit:0] cnt_rd;

reg [cnt_bit-1:0] data_bitsel;

reg spi_miso_reg;

reg [data_width-1:0] data_to_dsp_reg;

always @ (posedge adsp_clk)

begin

ref_freq_reg <= ref_freq;

end

always @ (posedge adsp_clk)

begin

if(rd_start == 1'b1)

begin

if((ref_freq_reg == 1'b1) && (ref_freq == 1'b0))

begin

if(cnt_clk < data_width)

begin

ref_freq_en <= 1'b1;

cnt_clk <= cnt_clk + 1'b1;

end

else

begin

ref_freq_en <= 1'b0;

end

end

end

else

begin

ref_freq_en <= 1'b0;

cnt_clk <= 4'b0000;

end

end

always @ (posedge adsp_clk)

begin

if((ref_freq_reg == 1'b1) && (ref_freq == 1'b0))

begin

ref_freq_en1 <= ref_freq_en;

end

end

always @ (posedge adsp_clk)

begin

spi_miso_reg <= spi_miso;

end

always @ (posedge adsp_clk)

begin

if(ref_freq_en == 1'b0)

begin

cnt_rd <= 4'b0000;

data_bitsel <= data_width - 1'b1;

end

else

begin

if((ref_freq_reg == 1'b0) && (ref_freq == 1'b1)) ///上升沿

begin

if(cnt_rd <= data_width)

begin

data_to_dsp_reg[data_bitsel] <= spi_miso_reg; ///相当于串行转8位并行

data_bitsel <= data_bitsel - 1'b1;

cnt_rd <= cnt_rd +1'b1;

end

end

end

end

assign rd_clk = (ref_freq_en & ref_freq);

assign data_to_dsp = data_to_dsp_reg;

endmodule

spi_write_data.v

`timescale 1ns / 1ps

module spi_write_data(

adsp_clk,

ref_freq,

wr_start,

data_to_flash,

wr_clk,

spi_mosi

);

parameter data_width = 8;

parameter cnt_bit = 3;

input adsp_clk;

input ref_freq;

input wr_start;

input [data_width-1:0] data_to_flash;

output wr_clk;

output spi_mosi;

reg ref_freq_reg;

reg ref_freq_en;

reg ref_freq_en1;

reg [cnt_bit:0] cnt;

reg [cnt_bit-1:0] data_bitsel;

reg [data_width-1:0] data_to_flash_reg;

reg spi_mosi_reg;

reg spi_mosi_reg1;

always @(posedge adsp_clk)

begin

ref_freq_reg <= ref_freq; ///一拍延时为的是构成后期的下降沿

end

always @(posedge adsp_clk)

begin

if(wr_start == 1'b0) //不能往SPI flash里面写数据

begin

ref_freq_en <= 1'b0;

cnt <= 4'b0000;

data_bitsel <= data_width - 1'b1;

data_to_flash_reg <= data_to_flash;

end

else ///此时开始往flash里面写fpga里面发过来的数据

begin

if((ref_freq_reg == 1'b1) && (ref_freq == 1'b0)) ///ref_freq_reg、ref_freq=1MHz //此处是FPGA下降沿的常用写法//

begin

if(cnt < data_width)

begin

ref_freq_en <= 1'b1;

spi_mosi_reg <= data_to_flash_reg[data_bitsel]; ///移位过程。FPGA发给SPI Flash的是字节(8位并行数据),而SPI只能接受串行数据,故每字节都按高位到低位读取

data_bitsel <= data_bitsel - 1'b1; ///相当于并转串

cnt <= cnt + 1'b1;

end

else

begin

ref_freq_en <= 1'b0; //ref_freq_en 为控制data_to_flash读写的

end

end

end

end

always @(posedge adsp_clk)

begin

if((ref_freq_reg == 1'b1) && (ref_freq == 1'b0))

begin

ref_freq_en1 <= ref_freq_en;

spi_mosi_reg1 <= spi_mosi_reg;

end

end

assign spi_mosi = spi_mosi_reg1;

assign wr_clk = (ref_freq_en1 & ref_freq); ///ref_freq_en==1有效时,可对spiflash进行写入;ref_freq是为1MHz的信号。也即当ref_freq_en==1写入有效时,写时钟为1MHz

endmodule

spi接口模块功能使用总结:例化spi_interface.v模块,当进行写操作时,上位机先将一个Byte数据放到data_to_flash[7:0]中,然后使控制信号wr_start由0变1,产生一个上升沿,且持续至少8个spi_clk时钟周期,使Byte数据并转串发出去;当进行读操作时,先使控制信号rd_start由0变1,产生一个上升沿,且持续至少8个spi_clk时钟周期,使要读的Byte数据串转并接收并放在寄存器data_to_dsp[7:0],由上位机读取。该spi接口功能模块全过程由上位机控制。

2.5 软件设计

2.5.1 flash驱动软件设计

所选型的存储芯片型号为GD25B128E,简介如图所示,该芯片读写方式与M25P16相类似,结合FPGA的spi接口模块逻辑设计,笔者给出FLASH的软件驱动代码作为参考,该驱动代码主要控制FPGA的spi_interface接口逻辑模块,间接性地操控FPGA对FLASH的读写操作,本质上是上位机对FLASH的读写操作。

图2.3 FLASH的数据手册简介截图

FLASH驱动代码 flash.c

//==============================================================================

//

// Title: flash.c

// Purpose: A short description of the implementation.

//

// Created on: 2023/9/13 at 17:52:11 by Windows User.

// Copyright: P R C. All Rights Reserved.

//

//==============================================================================

//==============================================================================

// Include files

#include

#include "flash.h"

#include "stdio.h"

//地址定义

#define regW_spi_wr 0x12C

#define regW_spi_rd 0x12d

#define regW_wrflash_data 0x12e

#define regW_spi_flash_cs 0x12f

#define regW_fpga_prog_b_ctrl 0x130

#define regW_sw_sel 0x131

#define regW_sw_en 0x132

#define regW_fpga_prog_b_en 0x133

#define regR_rdflash_data 0x12C

//高低电平定义

#define sHigh 0x01

#define sLow 0x00

//命令定义 转为32位

#define Fcmd_05H 0x05 //Read Status Register-1

#define Fcmd_35H 0x35 //Read Status Register-2

#define Fcmd_15H 0x15 //Read Status Register-3

#define Fcmd_06H 0x06 //Write Enable

#define Fcmd_60H 0x60 //Chip Erase

#define Fcmd_C7H 0xC7 //Chip Erase

#define Fcmd_20H 0x20 //Sector Erase

#define Fcmd_02H 0x02 //Page Program

#define Fcmd_03H 0x03 //Read Data

extern ViSession vi;

//定义一个 全局fifo 用来上位机下发数据

char *mcs_data_p = NULL;

void delayunit()

{

int i,j;

for(i=0; i<1; i++)

{

for(j=0; j<100; j++);

}

}

void delayus(unsigned int us )

{

int i;

for(i=0; i

{

delayunit();

}

}

/******************************************************************

函数名:Spi_sendByte

备注: 上位机通过pcie写入一个字节,然后fpga的spi并转串发送

参数: @pByte 要写入的字节

备注:

*****************************************************************/

void Spi_sendByte(unsigned char pByte)

{

ViSession io = Ivi_IOSession(vi);

WriteReg(io, regW_wrflash_data, (unsigned int)pByte); // 上位机下发一个字节

delayus(1); // 延时

WriteReg(io, regW_spi_wr, sHigh); // 拉高spi_wr spi指令发送

delayus(10); // 延时

WriteReg(io, regW_spi_wr, sLow); // 拉低spi_wr

delayus(1); // 延时

}

/******************************************************************

函数名:Spi_recvByte

备注: fpga的spi串转并接收一个字节,上位机通过pcie总线读接收到

参数: 返回一个字节

备注:

*****************************************************************/

unsigned char Spi_recvByte(void)

{

ViSession io = Ivi_IOSession(vi);

unsigned int rd_data;

delayus(1);

WriteReg(io, regW_spi_rd, sHigh); // 拉高spi_rd spi指令发送

delayus(10); // 延时

WriteReg(io, regW_spi_rd, sLow); // 拉低spi_rd

rd_data = ReadReg(io, regR_rdflash_data); //读取数据

delayus(1); // 延时

return ((unsigned char)rd_data);

}

/******************************************************************

函数名:Flash_wait_ready

功能: 不断访问寄存器1的WIP值,等待flash内部准备完成

参数:

备注:

*****************************************************************/

void Flash_wait_ready(void)

{

ViSession io = Ivi_IOSession(vi);

unsigned char rState;

unsigned char fBusy = 1;

while(fBusy)

{

WriteReg(io, regW_spi_flash_cs, sLow); // cs拉低

delayus(5);

Spi_sendByte(Fcmd_05H); // 发送读取状态寄存器1的命令05H

rState = Spi_recvByte(); // 接收返回的状态寄存器1值

delayus(5);

WriteReg(io, regW_spi_flash_cs, sHigh); // cs拉高

if((rState & 0x01) == 0) // 判断WIP的值,若为高,代表flash繁忙,等待flash准备好;

{ //若为低,代表flash空闲,可进行下一步操作,跳出循环

//fBusy = 0;

break;

}

delayus(200);

}

}

/******************************************************************

函数名:Flash_write_command

功能: 仅往flash里写入一个字节的命令

参数: @Fcmd 要写入flash的命令

备注:

*****************************************************************/

void Flash_write_command(unsigned int Fcmd)

{

ViSession io = Ivi_IOSession(vi);

WriteReg(io, regW_spi_flash_cs, sLow); // cs拉低

delayus(5);

Spi_sendByte((unsigned char)Fcmd); // 发送命令

delayus(5);

WriteReg(io, regW_spi_flash_cs, sHigh); // cs拉高

}

/******************************************************************

函数名:Flash_Chip_Erase

功能: flash 擦除全部,擦除后值都是1 命令 60H或C7H

参数: @Fcmd 要写入flash的命令

备注:

*****************************************************************/

void Flash_Chip_Erase()

{

ViSession io = Ivi_IOSession(vi);

Flash_write_command(Fcmd_06H); // 让flash处于可写状态

delayus(100);

Flash_write_command(Fcmd_C7H); // 写入擦除命令

delayus(100);

Flash_wait_ready(); // 等待flash内部完成擦除工作

delayus(100);

}

/******************************************************************

函数名:Flash_Sector_Erase

功能: flash 扇区擦除 命令20H

参数: @flashAddr 擦除扇区的地址

备注:

*****************************************************************/

void Flash_Sector_Erase(unsigned int flashAddr)

{

ViSession io = Ivi_IOSession(vi);

Flash_write_command(Fcmd_06H); // 让flash处于可写状态

delayus(100);

// printf("flash Sector Erase ing...\n");

WriteReg(io, regW_spi_flash_cs, sLow); // cs拉低

delayus(5);

Spi_sendByte(Fcmd_20H); // 上位机下发命令-Sector_Erase-

Spi_sendByte(((flashAddr & 0x00FF0000)>>16)); // 下发起始地址 16bit~24bit

Spi_sendByte(((flashAddr & 0x0000FF00)>>8)); // 下发起始地址 8bit~16bit

Spi_sendByte(((flashAddr & 0x000000FF)>>0)); // 下发起始地址 0bit~8bit

delayus(5);

WriteReg(io, regW_spi_flash_cs, sHigh); // cs拉高

delayus(100);

Flash_wait_ready(); // 等待flash工作完成扇区擦除

delayus(100);

// printf("finsh Sector Erase!\n");

}

/******************************************************************

函数名:Flash_Read_State

功能: 读取状态寄存器的值,有三个状态寄存器,分别是State1->05H,State2->35H,state3->15H

参数: @Fcmd 要读取的寄存器命令

备注:

*****************************************************************/

unsigned char Flash_Read_State(unsigned char Fcmd)

{

ViSession io = Ivi_IOSession(vi);

unsigned char rState;

WriteReg(io, regW_spi_flash_cs, sLow); // cs拉低

delayus(5);

Spi_sendByte(Fcmd); // 发送读取状态寄存器1的命令05H

rState = Spi_recvByte(); // 接收返回的状态寄存器1值

delayus(5);

WriteReg(io, regW_spi_flash_cs, sHigh); // cs拉高

return rState; // 返回状态值

}

/******************************************************************

函数名:Flash_Page_Program

功能: flash的页写入操作

参数: @flashAddr 写入页的起始地址24bit

@lenth 写入的数据长度

@dataArry[] 需要写入的数据数组

备注:长度lenth要小于等于256个字节

*****************************************************************/

void Flash_Page_Program(unsigned int flashAddr, unsigned char dataArry[], int lenth)

{

ViSession io = Ivi_IOSession(vi);

int num;

unsigned char wState=0;

unsigned char ReadDataArry[lenth];

Flash_write_command(Fcmd_06H); // 先让flash处于可写状态

delayus(50);

WriteReg(io, regW_spi_flash_cs, sLow); // cs拉低

delayus(5);

Spi_sendByte(Fcmd_02H); // 上位机下发命令-page program-

Spi_sendByte(((flashAddr & 0x00FF0000)>>16)); // 下发起始地址 16bit~24bit

Spi_sendByte(((flashAddr & 0x0000FF00)>>8)); // 下发起始地址 8bit~16bit

Spi_sendByte(((flashAddr & 0x000000FF)>>0)); // 下发起始地址 0bit~8bit

for(num = 0; num < lenth; num++) // 连续写入lenth个数据

{

Spi_sendByte(dataArry[num]);

}

delayus(5);

WriteReg(io, regW_spi_flash_cs, sHigh); // cs拉高

delayus(50);

Flash_wait_ready();

}

/******************************************************************

函数名:Flash_Read_Data_Byte

备注: flash进行数据读取

参数: @flashAddr 要读取的起始地址24bit

@lenth 要读取的数据长度

@dataArry[] 读取到数据要存放的数组

备注:lenth长度不限

*****************************************************************/

void Flash_Read_Data_Byte(unsigned int flashAddr, unsigned char dataArry[], int lenth)

{

ViSession io = Ivi_IOSession(vi);

int num;

unsigned char readData;

WriteReg(io, regW_spi_flash_cs, sLow); // cs拉低

delayus(5);

Spi_sendByte(Fcmd_03H); // 上位机下发命令-Read Data Bytes-

Spi_sendByte(((flashAddr & 0x00FF0000)>>16)); // 下发起始地址 16bit~24bit

Spi_sendByte(((flashAddr & 0x0000FF00)>>8)); // 下发起始地址 8bit~16bit

Spi_sendByte(((flashAddr & 0x000000FF)>>0)); // 下发起始地址 0bit~8bit

for(int NUM = 0; NUM < lenth; NUM++) // 读取十个数据,并存放到数组flash_arry[]

{

readData = Spi_recvByte();

dataArry[NUM] = (unsigned char)readData;

}

delayus(5);

WriteReg(io, regW_spi_flash_cs, sHigh); // cs拉高

}

/******************************************************************

函数名:Test_PageWrite()

功能: 往flash里页写入个数,然后在读回来比较,测试

参数: @PageNum 测试页的个数

备注:

*****************************************************************/

void Test_PageWrite(long int PageNum)

{

unsigned char testArry[257] = {0};

unsigned char flash_arry[257] = {0};

unsigned int WAddr = 0;

unsigned int RAddr = 0;

unsigned int cnt_reg = 0;

int ff_flag=0;

//全部擦除

Flash_Chip_Erase();

//页写入的值

for(int i=0; i<256; i++)

{

testArry[i]=0xaa;

}

printf("page program Start...\n");

for(int iii=0; iii

{

Flash_Page_Program(WAddr, testArry, 256);

if(((iii*100)/PageNum)>cnt_reg)

{

cnt_reg = (iii*100)/PageNum;

printf("%d%%\n", cnt_reg);

}

WAddr += 0x100;

Delay(0.001);

}

printf("100%%\n");

printf("page program finish!\n");

cnt_reg = 0;

printf("check page program...\n");

for(int ii=0; ii

{

Flash_Read_Data_Byte(RAddr, flash_arry, 256);

for(int NUM = 0; NUM < 256; NUM++)

{

if(flash_arry[NUM] != 0xff)

{

ff_flag++;

}

}

//检查0xff

if(ff_flag == 0) // 此时读到的全为0xff,记录地址

{

printf("err RAddr:%x\n", RAddr);

}

//打印进度

if(((ii*100)/PageNum)>cnt_reg)

{

cnt_reg = (ii*100)/PageNum;

printf("%d%%\n", cnt_reg);

}

RAddr += 0x100;

ff_flag = 0; //复位错误0xff标志位

}

printf("100%%\n");

printf("Check PP finish!\n");

}

/******************************************************************

函数名:TestPressure_PageWrite()

功能: 往flash里页写入个数,然后在读回来比较,测试

参数: @PageNum 测试页的个数

备注:

*****************************************************************/

void TestPressure_PageWrite(long int PageNum)

{

unsigned char testArry[257] = {0};

unsigned char flash_arry[257] = {0};

unsigned int WAddr = 0;

unsigned int RAddr = 0;

unsigned int cnt_reg = 0;

int ff_flag=0;

//全部擦除

Flash_Chip_Erase();

//页写入的值

for(int i=0; i<256; i++)

{

testArry[i] = i;

}

// 写入flash

printf("page program Start...\n");

for(int iii=0; iii

{

Flash_Page_Program(WAddr, testArry, 256);

if(((iii*100)/PageNum)>cnt_reg)

{

cnt_reg = (iii*100)/PageNum;

printf("%d%%\n", cnt_reg);

}

WAddr += 0x100;

Delay(0.001);

}

printf("100%%\n");

printf("page program finish!\n");

// 检查flash的值

cnt_reg = 0;

printf("check page program...\n");

for(int ii=0; ii

{

Flash_Read_Data_Byte(RAddr, flash_arry, 256);

for(int NUM = 0; NUM < 256; NUM++)

{

if(flash_arry[NUM] != testArry[NUM])

{

ff_flag++;

}

}

// 判断并打印出错的页地址

if(ff_flag != 0)

{

printf("err RAddr:%x\n", RAddr);

}

// 打印进度

if(((ii*100)/PageNum)>cnt_reg)

{

cnt_reg = (ii*100)/PageNum;

printf("%d%%\n", cnt_reg);

}

RAddr += 0x100;

ff_flag = 0;

}

printf("100%%\n");

printf("Check PP finish!\n");

}

2.5.2 mcs文件处理软件设计

这软件部分主要对.mcs文件进行预处理,将mcs文件转换成 在flash里存储FPGA的配置文件,即.bin文件。如上1.3部分所述mcs、bit和bin三者之间的关系,因为mcs文件包含了地址、数据和校验等信息,且是ASCII形式的文件,预处理操作即是.mcs文件将不必要的配置信息(地址、数据和校验等)去掉,且将ASCII格式转换成16进制格式文件的操作。实际上也可以直接将.bin文件直接写入到flash中。这部分是师兄写的,就不附上全码了,下面附上部分参考代码,仅供参考代码思路,结合.mcs文件结构来看比较容易懂。最重要的部分,我们实际只用把类型为type 00的数据段读取出来即可,再次推荐这篇博客(https://blog.csdn.net/hanhailong0525/article/details/122382501):

软件大致的流程框图如下

图2.4 在线升级软件流程图

代码如下:

/******************************************************************************

* @fn static uint8_t xilUpdate_TransASCII2HEX(_In_ char ascii)

* @brief 将ascii转为Hex

* @param[in] ascii : 字符码

* @param[out] 无

* @return Hex : 转换后的hex

* @note

******************************************************************************/

static uint8_t xilUpdate_TransASCII2HEX(_In_ char ascii)

{

if ((ascii >= 'A') && (ascii <= 'F'))

return ascii - 'A' + 10;

else if ((ascii >= 'a') && (ascii <= 'z'))

return ascii - 'a' + 10;

else

return ascii - '0';

}

/******************************************************************************

* @fn uint32_t xilUpdate_FileTotalSize(_In_ const char * path)

* @brief 获取文件大小 (以字节为单位)

* @param[in] path : 文件路径

* @param[out] 无

* @return size : 文件大小

* @note

******************************************************************************/

uint32_t xilUpdate_FileTotalSize(_In_ const char * path)

{

uint32_t size = 0;

FILE * fp = NULL;

if ((fp = fopen(path, "r")) == 0)

return 0;

fseek(fp, 0, SEEK_END);

size = ftell(fp);

fclose(fp);

return size;

}

/******************************************************************************

* @fn sint32_t xilUpdate_TranslateMCS2BIN(_In_ const char * mcsPath,

* _In_ const char * binPath)

* @brief 将MCS文件转换为BIN文件

* @param[in] mcsPath : MCS文件路径

* @param[in] binPath : BIN文件路径

* @param[out] 无

* @return retVal < 0 : 错误码

* retVal > 0 : BIN文件大小

* @note

******************************************************************************/

sint32_t xilUpdate_TranslateMCS2BIN(_In_ const char * mcsPath, _In_ const char * binPath)

{

uint8_t checkSum = 0;

sint32_t MCSEof = FALSE;

sint32_t error = UPDATE_ERROR_NONE;

char * mcsDat = NULL;

FILE * mcs_fp = NULL;

FILE * bin_fp = NULL;

M2B_Data M2Bdat = { 0 };

MCS_DataFormat * datFmt = NULL;

mcs_fp = fopen(mcsPath, "r");

bin_fp = fopen(binPath, "wb");

if ((mcs_fp == NULL) || (bin_fp == NULL))

return 0;

mcsDat = (char *)malloc(sizeof(char) * 64);

memset(mcsDat, 0, 64);

datFmt = (MCS_DataFormat *)mcsDat;

while (MCSEof == FALSE)

{

fgets(mcsDat, 64, mcs_fp); ///< 按行读取

if (datFmt->header != ':')

{

error = UPDATE_ERROR_FORMAT_ERR;

goto Exit;

}

M2Bdat.byteCnt = (uint8_t)((ASCII2HEX(datFmt->byteCnt[0]) << 4) + ASCII2HEX(datFmt->byteCnt[1]));///< 数据长度

M2Bdat.recType = (uint8_t)((ASCII2HEX(datFmt->recType[0]) << 4) + ASCII2HEX(datFmt->recType[1]));///< 数据类型

M2Bdat.hexAddr = (uint16_t)((ASCII2HEX(datFmt->hexAddr[0]) << 12) + (ASCII2HEX(datFmt->hexAddr[1]) << 8)

+ (ASCII2HEX(datFmt->hexAddr[2]) << 4) + (ASCII2HEX(datFmt->hexAddr[3])));///< 地址

checkSum = M2Bdat.byteCnt + M2Bdat.recType + ((M2Bdat.hexAddr >> 8) & 0xff) + (M2Bdat.hexAddr & 0xff);

switch (M2Bdat.recType)

{

case MCS_TYPE_DATA_RECORD : { ///< Type = 0: Data Record(数据记录)

for (int cnt = 0; cnt < (M2Bdat.byteCnt << 1); cnt += 2) ///< 读取数据

M2Bdat.dataRecord[cnt >> 1] = (uint8_t)((ASCII2HEX(datFmt->data.dataRecord[cnt]) << 4) + ASCII2HEX(datFmt->data.dataRecord[cnt + 1]));

for (int cnt = 0; cnt < M2Bdat.byteCnt; cnt ++) ///< 计算校验和

checkSum += M2Bdat.dataRecord[cnt];

M2Bdat.checkSum = (uint8_t)(((ASCII2HEX(*(mcsDat + 9 + (M2Bdat.byteCnt << 1)))) << 4) + ASCII2HEX(*(mcsDat + 9 + (M2Bdat.byteCnt << 1) + 1)));///< 校验和

M2Bdat.dataCounter += M2Bdat.byteCnt;

fwrite(&M2Bdat.dataRecord[0], 1, M2Bdat.byteCnt, bin_fp); ///< 写入数据

}break;

case MCS_TYPE_END_OF_FILE : { ///< Type = 1: End of File Record(文件结尾记录)

MCSEof = TRUE;

}break;

case MCS_TYPE_EXT_SEG_ADDR : { ///< Type = 2: Extended Segment Address Record(段地址记录)

M2Bdat.segAddress = (ASCII2HEX(datFmt->data.segAddress[0]) << 12)///< 读取段地址

+ (ASCII2HEX(datFmt->data.segAddress[1]) << 8)

+ (ASCII2HEX(datFmt->data.segAddress[2]) << 4)

+ (ASCII2HEX(datFmt->data.segAddress[3]));

M2Bdat.checkSum = (uint8_t)((ASCII2HEX(*(mcsDat + 13)) << 4) + ASCII2HEX(*(mcsDat + 14)));///< 读取校验和

checkSum += (M2Bdat.segAddress & 0xff) + ((M2Bdat.segAddress >> 8) & 0xff);///< 计算校验和

fseek(bin_fp, M2Bdat.segAddress << 16, SEEK_SET); ///< 设定写入位置

}break;

case MCS_TYPE_EXT_LINEAR_ADDR : { ///< Type = 4: Extended Linear Address Record(线性地址记录)

M2Bdat.offsetAddr = (ASCII2HEX(datFmt->data.offsetAddr[0]) << 12)///< 读取线性地址

+ (ASCII2HEX(datFmt->data.offsetAddr[1]) << 8)

+ (ASCII2HEX(datFmt->data.offsetAddr[2]) << 4)

+ (ASCII2HEX(datFmt->data.offsetAddr[3]));

M2Bdat.checkSum = (uint8_t)((ASCII2HEX(*(mcsDat + 13)) << 4) + ASCII2HEX(*(mcsDat + 14)));///< 读取校验和

checkSum += (M2Bdat.offsetAddr & 0xff) + ((M2Bdat.offsetAddr >> 8) & 0xff);///< 计算校验和

fseek(bin_fp, M2Bdat.offsetAddr << 16, SEEK_SET); ///< 设定写入位置

}break;

default : break;

}

checkSum = (uint8_t)(0x100 - checkSum);

if ((MCSEof == FALSE) && (checkSum != M2Bdat.checkSum)) ///< 检测校验和

{

error = UPDATE_ERROR_CHECK_FAIL;

goto Exit;

}

}

error = (sint32_t)M2Bdat.dataCounter;

Exit:

free(mcsDat);

fclose(mcs_fp);

fclose(bin_fp);

return error;

}

/******************************************************************************

* @fn sint32_t xilUpdate_ProgramBIN2FLASH(_In_ const char * binPath)

* @brief 在线升级,往FLASH写入bin文件

* @param[in] binPath : bin文件路径

* @param[out] 无

* @return retVal < 0 : 错误码

* retVal > 0 : BIN文件大小

* @note

******************************************************************************/

sint32_t xilUpdate_ProgramBIN2FLASH(_In_ const char * binPath)

{

uint32_t fsize;

uint32_t wrNum = XILUPDATE_SET_FLASH_OPS_SIZE; // 页的大小,256个字节

uint32_t blknum;

uint32_t counter = 0;

sint32_t error = UPDATE_ERROR_NONE;

uint32_t cnt_reg = 0;

unsigned char flash_arry[260];

uint8_t * datBuf = NULL;

FILE * fp = NULL;

printf("Update Online Begin:\n");

fsize = xilUpdate_FileTotalSize(binPath); ///< 获取文件大小

if ((fp = fopen(binPath, "rb")) == NULL)

return 0;

datBuf = (uint8_t *)malloc(sizeof(uint8_t) * XILUPDATE_SET_FLASH_OPS_SIZE);

blknum = fsize / XILUPDATE_SET_FLASH_OPS_SIZE; ///

blknum += (fsize % XILUPDATE_SET_FLASH_OPS_SIZE > 0) ? 1 : 0; ///< 计算写入的数据块数量,即要写入flash中要有多少页

/* 擦除flash */

if ( flash_ops.flash_EraseChip != NULL)

printf("Flash Chip Erase ing...\n");

flash_ops.flash_EraseChip();

printf("Chip Erase Done!\n");

/* 开始往flash写入数据 */

printf("Program Bin To Flash ing...\n");

printf("0%%");

for (uint32_t cnt = 0; cnt < blknum; cnt ++)

{

fread(datBuf, 1, wrNum, fp); ///< 读取BIN文件

if (flash_ops.flash_WriteBuffer != NULL) ///< 每次按页的大小写入flash

flash_ops.flash_WriteBuffer(XILUPDATE_SET_FLASH_PRAMADDR + cnt * XILUPDATE_SET_FLASH_OPS_SIZE, datBuf, wrNum);//该函数第一个参数为flash地址;第二个参数为要写入flash的数据缓存;第三个参数为要写入flash的数据量

counter += wrNum;

wrNum = ((fsize - counter) > XILUPDATE_SET_FLASH_OPS_SIZE) ? XILUPDATE_SET_FLASH_OPS_SIZE : fsize - counter;

if(((cnt*100)/blknum)>cnt_reg)

{

cnt_reg = (cnt*100)/blknum;

printf("\r%d%%", cnt_reg);

}

}

printf("\r100%%\n");

printf("Program Bin To Flash Done!\n");

error = (sint32_t)counter;

Exit:

free(datBuf);

fclose(fp);

return error;

}

三、实验结果及总结

实验结果:基于flash的在线升级,flash的读写时钟频率为1MHz,升级的.mcs文件大小为30MB,实际写入flash的文件.bin大小为11MB,整个升级过程耗时约5min,升级完成后掉电重启,fpga能够自配置,并能够实现所升级的功能,表明升级成功。 总结:要理解FPGA的升级就是FPGA配置文件的更换,基于flash的FPGA升级就是要把新的配置文件替代掉原来存放flash的旧配置文件,使得每次FPGA上电重启都是更具新的配置文件进行配置。本次实验关键的地方:

理解.mcs和.bin文件之间的关系及区别理解flash的读写操作及时序保证上位机下传的字节数据以并转串的形式能够正确无误写入flash中注意上位机到FPGA数据传输会产生的跨时钟域问题(这里笔者被搞了一周的时间,超级难过,最后问师兄,师兄说信号打两拍就解决了…)

文章写的有点草率,若能够帮到路过的你,是我的荣幸。你们的点赞,是我写文章的动力(比心),欢迎大家学习交流~

最后建议希望对大家有用:做技术自己单干对自己提升肯定很大,但一定不要闷头自己死磕,遇到瓶颈一定要多问,千万别憋大招,技术有交流才能更快的成长,不是每个坑都踩一遍才算成长,能知道坑在哪里不去踩也算成长!

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: