基于flash的FPGA的在线升级
一、理论
1.1 在线升级概念
在线升级是指通过网络或其他远程方式对软件、固件或系统进行更新和升级的过程。FPGA的在线升级是指在运行时对FPGA芯片中的逻辑配置进行更新或修改,而无需物理更换芯片。一般开发阶段,开发人员常用JTAG对FPGA进行配置,用于工程的功能修改\调试\更新。但当投入为产品时,想要进行FPGA的固件更新,再通过JTAG来配置FPGA显然是比较麻烦的,所以需要在线升级功能。
1.2 FPGA的配置方式
不同型号和系列的FPGA可能会支持不同的配置模式,这里以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.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.3 硬件设计
- 硬件上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的读写操作。
-
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):
- 软件大致的流程框图如下
- 代码如下:
/******************************************************************************
* @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上电重启都是更具新的配置文件进行配置。本次实验关键的地方:
- 理解.mcs和.bin文件之间的关系及区别
- 理解flash的读写操作及时序
- 保证上位机下传的字节数据以并转串的形式能够正确无误写入flash中
- 注意上位机到FPGA数据传输会产生的跨时钟域问题(这里笔者被搞了一周的时间,超级难过,最后问师兄,师兄说信号打两拍就解决了…)
文章写的有点草率,若能够帮到路过的你,是我的荣幸。你们的点赞,是我写文章的动力(比心),欢迎大家学习交流~
最后建议希望对大家有用:做技术自己单干对自己提升肯定很大,但一定不要闷头自己死磕,遇到瓶颈一定要多问,千万别憋大招,技术有交流才能更快的成长,不是每个坑都踩一遍才算成长,能知道坑在哪里不去踩也算成长!