基于flash的FPGA的在线升级

基于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

  1. Bitstream(bit):Bitstream是一种二进制文件格式,用于存储FPGA的配置信息。它包含了FPGA逻辑元件的连接和功能等详细信息,以及配置所需的时序和逻辑设置。Bitstream文件通常由FPGA开发工具生成,并通过不同的配置方式(如JTAG、SPI等)加载到FPGA中。

  2. Binary(bin):Binary也是一种二进制文件格式,但与Bitstream不同,它通常是指纯粹的二进制数据文件,没有特定的FPGA配置结构。在某些情况下,可以将FPGA的配置数据导出为二进制文件,这样可以方便地进行备份、传输或其他处理。

  3. 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.youkuaiyun.com/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配置接口
  1. 硬件上FPGA配置方式选择Master SPI 配置模式,即M[2:0]为3’b001。
  2. 由于FPGA上电启动,通过FPGA专用的spi引脚对flash读取,完成自身配置,配置完成之后FPGA专用引脚的CCLK便不能当做普通IO口使用,所以选择多路复用器进行spi通道选择。当需要在线升级功能时,选择spi_A通道,将.mcs文件并转串写入FLASH中;在完成在线升级后,再将多路复用器切换到spi_B通道。多路复用器选型为6通道、1:2、多路复用/复解器的器件TS3A27518ERTWR。
  3. 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 <Ivi.h>
#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<us; 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<PageNum; 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<PageNum; 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<PageNum; 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<PageNum; 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.youkuaiyun.com/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表示页的字节大小,为256个字节
	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上电重启都是更具新的配置文件进行配置。本次实验关键的地方:

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

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

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

在实际工程应用中,我们时常会遇到为解决某个老产品的BUG,需要在工程现场更新设备的FPGA代码,或者参加电信测试时需要现场升级设备FPGA程序以便于调试。公司现阶段所用的Altera FPGA程序代码一般存放于芯片配套的FLASH存储器中,而常见的对印制板上FLASH编程有几种方法,原始的方法是使用编程器,这种方法需要要将芯片取下,十分不便,或者通过JTAG接口连接到PC机上,但需要专用下载软件(一般由芯片生产厂商提供)。在测试现场或调测机房现场,要找到FPGA的专用下载线是比较困难的,且Altera FPGA的专用下载软件并不是每个PC设备上都有的。有时仅为了更新一个FPGA的程序就需要研发或客服人员亲自到现场去烧写程序,这既不便捷,也使得设备维护成本大大增加。  经过可行性与成本的考虑,我们找到一种既方便实用又低成本的方法来实现FPGA程序的在线升级。即在MCU中(单片机或ARM均可)用软件来模拟XModem协议,将程序文件传输到FPGAFLASH中。这种方法使用WINDOWS自带的超级终端软件来传送文件,无需安装专用软件,硬件支持仅需要一根通用串口线,只要在目标板MCU上增加一段实现XModem协议传输的代码,就可以方便的实现FPGA程序下载了。这种特点不仅方便了客服人员,也给研发和生产人员在现场调试和软件升级、修改中带来极大方便。
FPGA在线升级是指通过一些手段更新FPGA中的程序,而不需要进行常规意义下的下载程序。常用的工具有ICAP原语和flash在线升级面临两个问题:如何将新的FPGA程序下载到flash中,以及如何让FPGA执行新的程序。在线升级的意义在于,当产品上线时,由于JTAG接口过于笨重,一般不会保留该接口。因此,在没有JTAG接口的情况下,要想下载新的程序,就必须使用在线升级的方法。在线升级的策略一般是通过产品自带的通讯接口(如串口、以太网接口等)传输待更新的固件包,然后将固件包写入FPGA的配置flash中,覆盖原来通过JTAG烧写的代码。当芯片重新上电或进行一次重配置时,FPGA会执行新的固件包。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [基于FPGA在线升级](https://blog.youkuaiyun.com/zhangningning1996/article/details/104122587)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [FPGA远程固件在线升级](https://blog.youkuaiyun.com/weixin_45104510/article/details/129154915)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值