分类目录归档:FPGA入不了门

MicroBlaze 不使用 片上 RAM 实现上电自启

最近在学Xilinx公司的开发流程,正巧学到了MicroBlaze处理器,由于使用的这款VC707开发板没有像ZYNQ系列集成了ARM的硬核,所以在处理一些复杂的逻辑的时候还是要借助软核来处理。但是在做MicroBlaze开发的时候遇到了一点问题,导致我花了很多天的时间去思考和测试。

主要问题是,当我在使用MicroBlaze处理器的时候,我希望能够实现上电之后从flash加载程序,并且执行。

但是查到的资料多都是使用片上的RAM资源来做RAM,然后在生成FPGA的bit文件的时候将代码嵌进去。当上电加载的时候,程序就在RAM中,这样自然可以直接运行。但是片上RAM寸土寸金,我程序大一点岂不是成本巨大?于是就有另外的方法了,那就是用一块小的片上RAM,给里边装上SREC Bootloader,实现上电了之后将程序加载到外部的SDRAM中,这个方法虽然折中了许多,但是给我的一个直接印象就是:

不美!

因为这一块小的片上RAM,它完成了Boot任务之后,我究竟该不该使用它呢?用的话容量太小也没有什么用处,不用的话,那么这些资源完全属于浪费,能挪到别的地方该多好?所以这块RAM实属鸡肋。

所以这几天我便研究了一下如何在不使用这块片上RAM的情况下完成上电自启。

首先分析原理:当不使用片上RAM的时候,也就是说我的代码一开始是放在flash当中的,所以我需要一段程序将flash中的程序复制进SDRAM里,因为flash是NAND Flash所以不能直接将flash当RAM使用,而SREC flash loader虽然也是完成这个任务,不过观察其代码,由于它使用了data空间,所以在SREC loader程序运行前还是需要将其本身加载到RAM中去。

所以详细的解决方案是:先编写一段程序将SREC flash loader复制到SDRAM中,再使用SREC flash loader加载用户程序。

先看硬件工程设计:

先将MicroBlaze上的LMB关掉,并且启用AXI指令总线。将指令总线和数据总线同时接到DDR Memory和External Flash上。

分配地址空间,这里我将DDR RAM分配在0x0至0x3FFFFFFF之间,将Flash分配在0x60000000之后。

由于我使用的是VC707评估板,其上搭载的是Virtex-7 XC7VX485T-2FFG1761C这块FPGA,其配置bit文件的大小为20273588字节即0x‭13559B4‬字节,所以从0x60000000到0x613559B4‬这段地址空间是不能使用的,所以我将程序分配在0x61800000之后。因此调整MicroBlaze的reset地址:

在例化的时候,可能会碰到一条Critical Warning,这里可以不用理会,这是由于没有使用片上RAM引起的。

接下来进行软件开发,首先编译生成SREC Bootloader:

将blconfig.h中flash映像地址改成如图所示。这个地址便是之后用户程序存储点。

将链接脚本中的配置更改为:

主要注意修改DDR的Base Address,由于Bootloader在加载用户程序的时候我们不希望其将自己破坏,所以放在内存中高位的位置。如果提示空间不足的话,可以考虑降低一点基地址或者减小一点堆栈空间。

编译生成elf文件后,还不能直接拿来使用,需要将其转换为内存映像,才能写入flash。

在SDK的Xilinx->Launch Shell中打开命令行,进入包含编译好elf文件的目录执行下面的命令生成内存映像文件:

1
data2mem -bd FILNAME.elf -d -o m FILNAME.mem

用UltraEdit打开文件,可以看到各个不同部分的内存数据已经全部被导出来了。

将0x3FFFE000后面这一段数据复制出来(其余区段部分为中断和复位跳转,可以删掉),转换成二进制流文件,这里我自己编写了一个简陋的小工具来完成这个操作(可执行程序和源码见下面的压缩包)。

mem2bit.zip

1
mem2bit -i FILNAME -o FILNAME

这样便生成了SREC Bootloader的内存映像文件。

但是光是这样也是不够的,还需要一段程序将其复制进入内存中,否则其对data段数据的写入操作会导致程序出错。

所以需要自己编写一段程序来完成这个操作。打开记事本编写并保存以下这段汇编代码,并更改其中的地址量适配自己的系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
.global _start
 _start:
addi r1, r0, 0x00001FF0    #r1 is flash data size
addi r2, r0, 0x61900000    #r2 is flash pointer
addi r3, r0, 0x3FFFE000    #r3 is mem pointer
addi r4, r0, -4*5          #r4 is loop back instruction counter
lwi r5, r2, 0              #load a 32-bit(4byte) data from flash
swi r5, r3, 0              #store the data to DDR memory
addi r2, r2, 4             #increase flash pointer by 4
addi r3, r3, 4             #increase memory pointer by 4
addi r1, r1, -4            #decrease flash data counter
bne r1, r4                 #check if flash data is all copied
brai 0x3FFFE000            #jump to srec bootloader place

继续在Shell中输入命令将其转成内存映像文件:

1
2
3
4
mb-as FILNAME -o FILNAME.o -mlittle-endian
mb-ld FILNAME.o -Ttext 0x0 -o FILNAME.elf -oformat elf32-microblaze
data2mem -bd FILNAME.elf -d -o m FILNAME.mem
mem2bit -i FILNAME -o FILNAME

到此为止我分别得到了两个boot文件,分别是自己编写的boot0.boot,另一个是SRECBoootloader的boot.boot

接下来使用SDK中的flash工具将其烧写进相应的位置:

这样整个bootloader部分就做好了,接下来将应用程序烧写进0x61A00000中去便可以启动了。当然在编译应用程序前要注意修改程序的复位和中断点,由于最开始设置了复位地址,所以xilinx工具会自动生成在flash中的复位点,这里要注意。

将程序编译好了之后写入flash相应位置,需要设置为SREC可加载格式。

接下来将FPGA配置文件写入flash,便可以实现上电自启了。效果如图:

至此MicroBlaze不使用片上RAM自启动便做好了。

补充:在写完文章后,开发的过程中又碰到问题。是由于MicroBlaze的中断、异常的地址没有找到单独更改的方法,在手册中它们是以复位地址为基础加的(这一点就和Nios II有点差距了,灵活性明显不如Nios II),所以在做boot0的时候,需要将中断异常等进行重定向。所以将汇编部分的代码更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.section .vectors.reset,"ax"
.global _start
_start:
brai _main                 #reset jump
 
.section .vectors.sw_exception,"ax"
_vectors_usrvec:
brai 0x00000008            #user vector jump
 
.section .vectors.interrupt,"ax"
_vectors_int:
brai 0x00000010            #interrupt jump
 
.section .vectors.break,"ax"
_vectors_break:
brai 0x00000018            #break jump
 
.section .vectors.hw_exception,"ax"
_vectors_exception:
brai 0x00000020            #hardware exception jump
 
.section .text
_main:
addi r1, r0, 0x00001FF0    #r1 is flash data size
addi r2, r0, 0x61900000    #r2 is flash pointer
addi r3, r0, 0x3FFFE000    #r3 is mem pointer
addi r4, r0, -4*5          #r4 is loop back instruction counter
lwi r5, r2, 0              #load a 32-bit(4byte) data from flash
swi r5, r3, 0              #store the data to DDR memory
addi r2, r2, 4             #increase flash pointer by 4
addi r3, r3, 4             #increase memory pointer by 4
addi r1, r1, -4            #decrease flash data counter
bne r1, r4                 #check if flash data is all copied
brai 0x3FFFE000            #jump to srec bootloader place

同时在链接时应该使用-T参数加上链接脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ENTRY(_start)
 
SECTIONS
{
.vectors.reset 0x61800000 : {
   *(.vectors.reset)
} 
 
.vectors.sw_exception 0x61800008 : {
   *(.vectors.sw_exception)
} 
 
.vectors.interrupt 0x61800010 : {
   *(.vectors.interrupt)
} 
 
.vectors.break 0x61800018 : {
  *(.vectors.break)
}
 
.vectors.hw_exception 0x61800020 : {
  *(.vectors.hw_exception)
}
 
.text 0x61800050 : {
	*(.text)
}
}

其余步骤均与原文相同。

(完)

附:mem2bit源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// mem2bit.cpp : 定义控制台应用程序的入口点。
//
 
#include <fstream>
#include <iostream>
 
 
int main(int argc, char *argv[])
{
	char *infile_name, *outfile_name;
	bool flag_infile, flag_outfile;
	std::fstream infile;
	std::fstream outfile;
	int byte_buf;
 
	flag_infile = false;
	flag_outfile = false;
	for (size_t i = 0; i < argc; i++)
	{
		if (strcmp(argv[i], "-i") == 0)
		{
			infile_name = argv[i + 1];
			flag_infile = true;
			continue;
		}
		if (strcmp(argv[i], "-o") == 0)
		{
			outfile_name = argv[i + 1];
			flag_outfile = true;
			continue;
		}
	}
	if (!(flag_infile & flag_outfile))
	{
		std::cout << "Useage: mem2bit -i <FILENAME> -o <filname>\n";
		return 1;
	}
	infile.open(infile_name, std::ios::in);
	if (!infile.is_open())
	{
		std::cout < < "Open" << infile_name << "Error!\n";
		return 1;
	}
	outfile.open(outfile_name, std::ios::out | std::ios::binary);
	if (!infile.is_open())
	{
		infile.close();
		std::cout << "Open" << outfile_name << "Error!\n";
		return 1;
	}
	while (!infile.eof())
	{
		infile >> std::hex >> byte_buf;
		outfile.write((char *)&byte_buf, 1);
	}
	std::cout < < "Generate done!\n";
	infile.close();
	outfile.close();
 
    return 0;
}