目录

1       概述....................................................................... 1

2       几种常见的boot方式......................................................... 1

2.1........................................................................................... 从EPCS串行存贮器中boot........ 1

2.2....................................................................................... 从外部CFI 并行flash中boot........ 1

3       EPCSboot................................................................................................................................................. 1

3.1..................................................................................... EPCS控制器的bootloader分析........ 2

3.2.................................................................................................................... EPCS控制器........ 3

3.3........................................................................................................ EPCS串行存贮器件........ 5

4       从并行flashboot.......................................................................................................................................... 5

4.1..................................................................................................... 并行flash配置控制器........ 5

4.2................................................................................................ 直接在Flash中运行程序........ 5

4.3....................................................................................................... 在RAM中运行程序........ 6

5       Bootloader解读.............................................................. 7

5.1............................................................................................................ boot_loader.s解读........ 8

5.2.......................................................................................... boot_loader_epcs_bits.s解读........ 10

5.3.............................................................................................. boot_loader_cfi_bits.s解读........ 21

6       Crt0.s解读........................................................................................................................................................ 23

1          概述

Nios II 的boot过程要经历两个过程。

1.         FPGA器件本身的配置过程。FPGA器件在外部配置控制器或自身携带的配置控制器的控制下配置FPGA的内部逻辑。如果内部逻辑中使用了Nios II,则配置完成的FPGA中包含有Nios II软核CPU。

2.         Nios II本身的引导过程。一旦FPGA配置成功后,Nios II 就被逻辑中的复位电路复位,从reset地址开始执行代码。Nios II 的reset地址可以在SOPC builder的“Nios II More‘CPU’setting”页表中设置。

2          几种常见的boot方式

2.1         EPCS串行存贮器中boot

这种boot方式,FPGA的配置数据和Nios II的程序都存放在EPCS器件中。FPGA配置数据放在最前面,程序放在后面,程序可能有多个段,每个段前面都插有一个“程序记录”。一个“程序记录”由2个32位的数据构成,一个是32位的整数,另一个是32位的地址,分别用于表示程序段本身的长度和程序段的运行时地址。这个“程序记录”用于帮助bootloader把各个程序段搬到程序执行时真正的位置。EPCS是串行存贮器,Nios II不能直接从EPCS中执行程序,它实际上是执行EPCS控制器的片内ROM的代码(即bootloader),把EPCS中程序的搬到RAM中执行。

2.2         从外部CFI 并行flashboot

这种boot方式还可以分为2种情况。

1.         程序直接在flash中运行。这种情况程序不需要另外的bootloader,Nios II 复位时reset地址(指向flash内部)开始执行程序,程序必须有启动代码用于搬移.rwdata段(因为.rwdata段是可读写的不能存放在flash中),同时如果.RODATA段和.EXCEPTIONS段连接时没有指定在flash中话(比如在RAM中),也会被搬到RAM中,并对.bss段清零,设置栈的指针。这些工作都在Crt0.s中完成。

2.         程序在RAM(包括On-chip Ram,SDRAM,SSRAM…泛指一般的RAM)中运行。这种情况需要有一个专门的bootloader,它把存放在flash中的各个程序段搬到程序执行时各个段真正的位置。

3          EPCSboot

要支持Nios II从EPCS中boot首先要求FPGA器件要支持主动串行配置。Altera的Cyclone,Cyclone II和Stratix II系列的FPGA支持主动串行配置。直到Nios II 5.1版本,Nios II 从EPCS中boot在Stratix II系列的FPGA上实现上仍有问题。所以这种方式主要用于Cyclone和Cyclone II系列的器件。

为了实现这种boot方式,用户必须在SOPC builder中添加一个EPCS控制器,无须给它分配管腿,Quartus II 会自动给它分配到专用管腿上。添完EPCS控制器后,SOPC builder会给它分配一个base address,这个地址是EPCS控制器本身携带的片上ROM在Nios II系统中的基地址,这个ROM存有一小段bootloader代码,用于引导整个过程。所以,必须在SOPC builder的“Nios II More‘CPU’setting”页表中把reset地址设置为这个基地址,使得Nios II 复位后从这个地址开始执行以完成整个引导过程。

3.1         EPCS控制器的bootloader分析

EPCS控制器带有一块片内ROM,内有Bootloader代码,Nios II 就靠这段代码完成boot过程。它把EPCS里的Nios II程序映象复制到RAM中,然后跳转到RAM中运行。由于程序映象是由elf2flash输出的,bootloader对被搬运的程序映象的位置和结构的解读必须和elf2flash工具一致。FPGA的配置数据从EPCS偏移为0的地址开始存放,紧挨着配置数据后面是一个32位的整数,指示程序段的长度,接着是一个32位的地址,指示程序执行时该程序段的地址,我们把这个长度和地址一起称为“程序记录”,“程序记录”随后就是程序段映象。一个程序可能有多个程序段,所以也就有多个“程序记录”和程序段映象。Bootloader必须知道FPGA配置数据的长度以读取配置数据后面的内容,不同型号的FPGA的配置数据长度是不同的,所以必须读取配置数据的头部信息获取配置数据的长度,进而逐个读取程序段映象的长度和运行时地址,然后把程序段映象搬到目的运行时地址。为了存取EPCS,bootloader构造了一些位置无关汇编代码。EPCS的存贮布局如下所示:

剩余空间

4字节的最后一个
“程序记录”的目的地址域A

0x00000000,4字节的最后一个
“程序记录”的长度域L

Ln个字节的第n个程序段映象

4字节的第n个程序段的目的地址An

4字节的第n个程序段的长度Ln

L2个字节的第2个程序段映象

4字节的第2个程序段的目的地址A2

4字节的第2个程序段的长度L2

Length+8~length+L+7

L1字节的第1个程序段映象

Length+4~length+7

4字节的第1个程序段目的地址A1

Length~length+3

4字节的第1个程序段长度L1

0~length-1

FPGA配置数据,长度为length

当bootloader读取到L时,L=0,表示前面所有的程序记录已经处理完毕,这个是最后的程序记录就直接跳到地址A的地方执行。显然A必须是程序的入口地址。如果L=0xffffffff(即-1),那么就忽略A并停机,这样,即使是一个只有FPGA配置数据而没有程序的EPCS也是安全的。当一个EPCS只有配置数据而没有程序的时候,sof2flash会在配置数据的末尾增加4个字节的0xff使bootloader不会有误动作。Bootloader的工作流程如下:

3.2         EPCS控制器

EPCS控制器手册没有对EPCS进行详细的说明只是建议用户使用Altera的HAL函数来存取。其实EPCS控制器由两个独立的部件构成:

1.Rom。大小是512个字节,也就是128 words。尽管EPCS控制器手册表述了Rom的大小是1K字节,实际上直到Nios II 5.1 EPCS控制器的Rom仍然是512个字节,因此手册中给出的寄存器偏移地址都需要修正。

2.SPI Master控制器。EPCS串行存贮器的接口符合SPI标准。Nios II 可以通过SPI Master来存取EPCS串行存贮器。这两个部件的地址(从Nios II 的角度看,以字节为单位)安排如下:

偏移地址

寄存器

R/W

位描述

31..0

0x000

Boot Rom Memory

R

Boot Loader Code 
epcs_controller_boot_rom.hex
or epcs_controller_boot_rom.dat

0x004

0x1FC

0x200

Rx Data

R

31..8 (Not Implemented)

Rx Data(7..0)

0x204

Tx Data

W

31..8 (Not Implemented)

Tx Data(7..0)

0x208

Status

R/W

31..11

10

9

8

7

6

5

4

3

2

1

0

EOP

E

RRDY

TRDY

TMT

TOE

ROE

0x20C

Cotrol

R/W

31..11

10

9

8

7

6

5

4

3

2

1

0

IEOP

IE

IRRDY

ITRDY

ITOE

IROE

0x210

Reserved

-

0x214

Slaver Enable

R/W

31..16

15

14

13

3

2

1

0

SS_15

SS_14

SS_13

SS_3

SS_2

SS_1

SS_0

0x218

End of Packet

R/W

31..8 (Not Implemented)

End of character(7..0)

l         Rx Data寄存器
Nios II从Rx Data寄存器中读出从EPCS中接收到的数据。当接收移位寄存器收到满8位的数据,status寄存器的RRDY位被置1,同时数据被传入Rx Data寄存器。读取Rx Data寄存器会把RRDY位清掉,而往Rx Data写则没有影响。

l         Tx Data寄存器
Nios II把要发送的数据写到Tx Data寄存器。status寄存器中的TRDY位置1表示Tx Data寄存器准备好接收来自Nios II的新数据。Tx Data被写了之后,TRDY位就被置0,直到数据从Tx Data转移到发送移位寄存器又会被重新置为1。

l         Status寄存器
status寄存器包含有指示当前状态的位。几乎每一位都和control寄存器的一个中断允许位相关。Nios II任何时候都可以读取status寄存器,不会影响该寄存器的值。往status寄存器写将清除ROE,TOE和E这些位。下表描述了各个位的含义:

名称

含义

3

ROE

接收溢出错误。当Rx Data寄存器数据满的时候(RRDY为1),接收移位寄存器又往Rx Data寄存器写,那ROE位将被置1。而新的数据会覆盖老的数据。往status寄存器写可以把ROE位清0。

4

TOE

发送溢出错误。如果Tx Data寄存器数据还没有被转移到发送移位寄存器(TRDY为0),又往Tx Data寄存器写,那TOE就会被置为1。新的数被忽略。往status寄存器写可以清TOE为0。

5

TMT

发送移位寄存器空。如果一个发送过程正在进行中,那TMT为0;如果发送移位寄存器为空,则TMT为1。

6

TRDY

发送器准备好接收新的发送数据。当Tx Data寄存器空的时候,TRDY为1。

7

RRDY

接收器准备好送出接收到的数。当Rx Data寄存器满的时候,RRDY为1。

8

E

有错误产生。它是TOE和ROE的逻辑或。只要TOE或ROE中有一个为1,那它也为1。它给程序提供了一个判断有错误发生的方便的途径。往status寄存器写可以把E位清0。

9

EOP

包结束标志。该标志在下列情况下被置1:
1. 一个EOP字节被写入Tx Data寄存器

2. 一个EOP字节从Rx Data寄存器中读出
EOP字节就是End of Packet寄存器中的End of Character字节。往status寄存器写可以把EOP位清0。

l         Control寄存器
control寄存器控制SPI Master的操作。Nios II可以在任何时候读取control寄存器而不改变它的值。大部分control寄存器的位(IROE,ITOE,ITRDY,IRRDY和IE)控制status寄存器相应位的中断。比如当IROE设为1,就允许当status中的ROE为1时产生中断。只有当control寄存器和stauts寄存器中的相应位都为1的情况下,SPI Master才会产生中断。

名称

含义

3

IROE

允许ROE条件满足时产生中断。

4

ITOE

允许TOE条件满足时产生中断。

6

ITRDY

允许TRDY条件满足时产生中断。

7

IRRDY

允许RRDY条件满足时产生中断。

8

IE

允许E条件满足时产生中断。

9

IEOP

允许EOP条件满足时产生中断。

10

SSO

强制slave enable寄存器器中为1的位对应的ss_n有效,即输出电平0。

l         Slave enable寄存器
slave enable寄存器中的某一位置1表示相应的ss_n信号可以被驱动有效(即在control寄存器中写SSO位为1,或者有数据写入Tx Data寄存器准备开始传送数据)。Slave enable寄存器可以多位为1,但是需要有其它逻辑来处理多个SPI slave的冲突问题。

l         End of Packet寄存器
End of Packet寄存器包含End of Character,当某一Avalon master读出的Rx Data寄存器字节和End of Character一样,或者写入Tx Data的字节和End of Character一样时,SPI Master产生EOP标志。如果该Avalon master支持endofpacket信号,则会中断传输。

EPCS控制器在例化SPI Master时使用下列参数:数据位8位;SPI时钟SCLK频率20MHz;MOSI(ASDO)在SCLK的下降沿处输出;MISO(DATA0)在SCLK上升沿处采样;SCLK的初始相位为0;MSB先输出,LSB后输出;目标延迟100us(即ss_n输出为低到SCLK开始驱动输出时钟脉冲的延迟为100us)。

3.3         EPCS串行存贮器件

Altera的器件手册对EPCS器件有完整清楚的表述。在read byte,read status和read silicon ID操作时,发出命令后,所要的数据会马上从EPCS的DATA管腿移出。所以EPCS控制在发出命令后继续发送虚拟数据(比如0或随便什么值),在发送虚拟数据的同时接收EPCS送出的数据,就可以获取所要的数据。SPI接口的发送和接收是同时的,为了接收数据,你必须发送点什么,尽管这些数据是对方不需要的,同样在你发送命令或数据的同时也会收到点什么,尽管这些也不一定是你需要的。

4          从并行flashboot

4.1         并行flash配置控制器

Nios II应用常常把Nios II 程序和FPGA配置数据都存放在flash中。这就需要一个配置控制器来驱动flash输出配置数据完成FPGA的配置。配置控制器可以用一片CPLD来实现。Flash除了可以存贮FPGA配置数据和Nios II程序外还可以存贮其它数据(比如只读文件系统)。Flash中的配置数据区还可以分为两个区,一个用于用户逻辑,另一个用于出厂逻辑。当用户逻辑配置失败后,就会自动使用出厂逻辑,保证任何时候都有一个配置可以工作。另外,配置控制器还可以接收来自Nios II 的重配置请求,并驱动FPGA重新配置,完成FPGA的现场升级。Stratix开发板的配置控制安排偏移量为0的地方存放Nios II程序,而FPGA用户配置逻辑从偏移量0x600000开始,出厂配置则从偏移量0x700000开始。

Stratix开发板的并行flash配置控制器其实是一个地址序列生成器,地址生成器的输入时钟是板上时钟的4分频(比如,板上的晶振时钟是50MHz,则地址生成器的时钟就是12.5MHz)。上电的时候,由上电复位芯片提供的复位信号复位,地址生成器初始化为用户逻辑的配置数据的偏移量(比如Stratix板是0x600000),然后开始计数并驱动地址由低往高增长,使flash送出对应地址的配置数据。配置控制器监测FPGA的config_done信号,一旦发现FPGA配置完成就停止计数,并置flash的地址和其它控制线为高阻,以免影响Nios II对flash的操作。FPGA配置完成后,内部逻辑开始生效,复位Nios II,Nios II开始从reset地址执行程序。

4.2         直接在Flash中运行程序

嵌入式应用有时希望程序能够直接在flash中运行,以节约RAM空间,降低成本。为了使程序直接在flash中运行,可以在SOPC builder中设置reset地址在flash中,连接程序的时候可以指定程序的.TEXT段和.RODATA段存放在flash中,而让.RWDATA和堆栈放在RAM中(这2个段都是可读写的,不能放在flash中)。同时还可以在SOPC builder中指定exception地址到flash中,也可以节约一点RAM空间。由于最后的flash映象文件.flash文件(.flash文件其实是.srec格式的文件)中没有bss段,所以程序的开始必须在RAM中建立bss段并清0,同时也把.RWDATA段从flash中拷贝到RAM中(.RWDATA段在程序运行的时候必须在RAM中),并设置好栈,建立好C程序的工作环境然后调用C用户入口函数。这些工作都是由Crt0.s来完成的。下面是Crt0.s在flash中运行的工作流程:

4.3         RAM中运行程序

程序在flash运行通常比在RAM中慢,所以有时也希望程序能够在RAM中运行。Nios II的reset地址仍然指向flash中(reset地址不能指向RAM,RAM在上电复位时还没有被初始化),在连接程序的时候可以把每个段都指定到RAM中,在SOPC builder中也可以把exception部分指定到RAM中。这样连接生成的可执行文件.elf文件就是适合在RAM中运行的程序。但在实际应用中这个程序最终存放在flash中,所以需要有一段bootloader代码,用于把flash中的程序映象拷贝到RAM中运行。工具elf2flash能够根据情况自动给你的程序在生成.flash文件时添加“程序记录”和bootloader。elf2flash判断其后随参数reset地址(就是Nios II的reset地址)和程序的入口地址是不是一样,如果一样就不添加“程序记录”和bootloader,如果不一样就添加。这个bootloader根据各个“程序记录”把程序映象拷贝到到RAM中并从RAM中执行。和EPCS一样,每个“程序记录”由两个32位的数据组成,一个是程序的长度,一个目的执行地址(即程序的运行地址)。Stratix 开发板上flash中的存贮分布如下:

0x700000~0x7FFFFF

出厂逻辑Safe Logic

0x600000~0x6FFFFF

用户逻辑User Logic

剩余空间

4字节的最后一个
“程序记录”的目的地址域A

0x00000000,4字节的最后一个
“程序记录”长度域L

Ln个字节的第n个程序段映象

4字节的第n个程序段的目的地址An

4字节的第n个程序段的长度Ln

L2个字节的第2个程序段映象

4字节的第2个程序段的目的地址A2

4字节的第2个程序段的长度L2

Length+8~length+L+7

L1字节的第1个程序段映象

Length+4~length+7

4字节的第1个程序段的目的地址A1

Length~length+3

4字节的第1个程序段的长度L1

0~length-1

Bootloader

Bootloader的工作流程如下:

运行完bootloader后仍然要执行Crt0.s,但此时Crt0.s的流程和程序在flash中直接运行的情况有一些区别:它没有初始化指令cache,也不会企图去装载别的段,这些步骤已经在bootloader中完成。程序映象已经包含这些段,在搬移程序映象的同时也装载了相应的段(.RODATA段,.RWDATA段和.EXCEPTIONS段),程序映象中不包含.bss段和栈,所以仍然需要清.bss段以及设置栈指针和全局指针。Bootloader没有存取存贮器数据,因此没有初始化数据cache,所以Crt0.s仍然要初始化数据cache。

5          Bootloader解读

Altera提供了两个bootloader程序,一个用于从EPCS器件中boot,另一个用于从flash器件中boot。它们的汇编源码和makefile都在C:/altera/kits/nios2_51/components/altera_nios2/sdk/src/boot_loader_sources目录中。其中boot_loader.s是公共部分,而boot_loader_epcs_bits.s则用于从EPCS器件中Boot,boot_loader_cfi_bits.s用于从flash中Boot。

5.1         boot_loader.s解读

#ifdef EPCS

#define FIND_PAYLOAD   sub_find_payload_epcs                     // 查找EPCS中数据负荷子程序

#define READ_INT       sub_read_int_from_flash_epcs             // 从EPCS中读取一个32位word

#define STREAMING_COPY sub_streaming_copy_epcs               // 从EPCS中拷贝流的子程序

#define CLOSE_DEVICE   sub_epcs_close                                     // 关闭EPCS器件的子程序

#else

#define FIND_PAYLOAD   sub_find_payload_cfi                        // 查找CFI并行flash中数据负荷的子程序

#define READ_INT       sub_read_int_from_flash_cfi                // 从CFI并行flash中读取一个32位的word

#define STREAMING_COPY sub_streaming_copy_cfi                  // 从CFI并行flash中拷贝流的子程序

#endif

#include "boot_loader.h"

.global reset

.global _start

.global main

.global end_of_boot_copier

reset:

_start:

main:

// 清除CPU的状态寄存器禁止中断,这个动作在硬件复位的时候其实已经自动完成。.

wrctl   status, r_zero

// 冲刷指令cache.

// Nios II 最多支持64Kbytes的指令cache,所以只初始化了64Kbytes的指令cache

movhi   r_flush_counter,%hi(0x10000)

cache_loop:

initi   r_flush_counter

// 没有必要初始化数据cache, bootloader不存取存贮器数据

addi    r_flush_counter, r_flush_counter,-32

bne     r_flush_counter, r_zero, cache_loop

// 冲刷流水线

flushp

// r_flash_ptr = find_payload();

// 调用查找数据负荷子程序寻找数据负荷

nextpc  return_address_less_4

br      FIND_PAYLOAD

// 拷贝.

//

// 在循环的开始,寄存器r_flash_ptr 包含“程序记录”的地址。

//

// 1) 读取“程序记录”的长度域(4-bytes)(r_data_size)

// 2) 读取“程序记录”的目的地址域(4-bytes)(r_dest)

// 3) 循环:

//       拷贝 r_data_size 个字节, 一次一个字节: *r_dest++ = *r_flash_ptr++

// 把0xFFFFFFFF装入r_halt_record,用于测试是否要停机。

subi    r_halt_record, r_zero, 1

per_record_loop:

//读取“程序记录”的长度域,r_data_size = READ_INT(r_flash_ptr++)。

nextpc  return_address_less_4

br      READ_INT

mov     r_data_size, r_read_int_return_value

// 读取“程序记录”的目的地址域,r_dest = READ_INT(r_flash_ptr++)。

nextpc  return_address_less_4

br      READ_INT

mov     r_dest, r_read_int_return_value

// 测试长度域是否为0

// 如果是就直接运行程序

beq     r_data_size, r_zero, last_program_record

// 如果长度域为-1(0xFFFFFFFF),就停机。

halt_record_forever:

beq     r_data_size, r_halt_record, halt_record_forever

// 使用拷贝流子程序搬移数据

nextpc  return_address_less_4

br      STREAMING_COPY

// 程序运行到这里,表明已经处理了当前的“程序记录”了,

// 而且知道这不是最后一个“程序记录”因为它的长度域不为0,

// 这就意味着要处理下一个“程序记录”。

br      per_record_loop

last_program_record:

// 处理完最后一个程序记录后就要把控制权转给实际的运行程序.

// r_dest是实际程序的入口地址

// 在中止boot-loader之前要关闭EPCS器件,如果不做这一步,

// 会导致HAL的open()调用要花好几秒钟才能打开EPCS器件

#ifdef EPCS

nextpc  return_address_less_4

br      CLOSE_DEVICE

#endif

// 跳转到目的地址运行程序

callr   r_dest

afterlife:        // 程序跑到这里表明有问题。

br      afterlife

.end

5.2         boot_loader_epcs_bits.s解读

// 从EPCS串行flash设备读取字节的子过程

// 通过寄存器和EPCS打交道获取字节数

#include "boot_loader.h"

.global sub_find_payload_epcs

.global sub_read_int_from_flash_epcs

.global sub_streaming_copy_epcs

.global sub_epcs_close

// EPCS控制和状态寄存器的偏移量

#define EPCS_RXDATA_OFFSET  0x00

#define EPCS_TXDATA_OFFSET  0x04

#define EPCS_STATUS_OFFSET  0x08

#define EPCS_CONTROL_OFFSET 0x0C

// EPCS的位掩码

#define EPCS_STATUS_TMT_MASK  0x20

#define EPCS_STATUS_TRDY_MASK 0x40

#define EPCS_STATUS_RRDY_MASK 0x80

#define EPCS_CONTROL_SSO_MASK 0x400

// EPCS命令

#define EPCS_COMMAND_READ 0x03

.text

//

// 查找EPCS的数据负荷

//

// 过程:

//     - 在偏移量为0的地方打开EPCS器件(FPGA配置数据在这里)

//     - 分析配置数据获取数据负荷开始的地址

//     - 关闭EPCS

//     - 在数据负荷的开始的地址再次打开EPCS

//

sub_find_payload_epcs:

// 修正并存贮返回地址

addi    r_findp_return_address, return_address_less_4, 4

//

// 计算EPCS控制/状态寄存器块的地址

// 它在离本段代码的开头偏移量为512个字节的地方

// 因为这段代码必须在512字节边界处,

// 我们简单地把当前地址园整到下一个512个地址的边界。

//

// |

// | 为了调试,你可以定义EPCS_REGS_BASE

// | 作为EPCS寄存器基地址。否则就假设下一个512字节边界。

// |

nextpc  r_findp_temp

#ifdef EPCS_REGS_BASE

movhi   r_epcs_base_address, %hi(EPCS_REGS_BASE)

addi    r_epcs_base_address, r_epcs_base_address, %lo(EPCS_REGS_BASE)

#else

ori     r_epcs_base_address, r_findp_temp, 511

addi    r_epcs_base_address, r_epcs_base_address, 1

#endif

//

// 在偏移量为0的地方打开EPCS器件

//

movi    r_flash_ptr, 0

nextpc  return_address_less_4

br      sub_epcs_open_address

//

// 分析器件配置数据顺序读出字节直到下面任一个条件满足

//       1) 我们找到0xA6 (其实应该是0x56,因为我们没有把位序颠倒过来)

//          当我们找到它时表示我们找到配置数据,可以接着算出它的长度。

//       2) 我们找到不是xFF字节,在这种情况我们根本没有在配置数据里查找

//          我们假定我一定是在一个boot loader记录。跳过整个配置数据长度的计算

//          开始装载。

//       3) 我们在任意长的时间内找到的都是0xFF。我们猜测flash是空的没有其它可利用资源

//

// 搜索随意的一大块字节

movi    r_findp_count, 0x400

// 我们要找的模板是0x56

movi    r_findp_pattern, 0x56

// 在我们找到0x56之前唯一可以接受的字节是0xFF

movi    r_findp_temp, 0xFF

fp_look_for_56_loop:

nextpc  return_address_less_4

br      sub_read_byte_from_flash_epcs

// 我们发现模板了吗?

beq     r_read_byte_return_value, r_findp_pattern, fp_found_sync

// 我们发现非0xFF的字节了吗?

bne     r_read_byte_return_value, r_findp_temp, fp_short_circuit

// 更新循环计数器开始循环

subi    r_findp_count, r_findp_count, 1

bne     r_findp_count, r_zero, fp_look_for_56_loop

// 我们没有找到模板或其它匹配的字节,挂起。

// 先关闭EPCS器件

nextpc  return_address_less_4

br      sub_epcs_close

fp_hang:

br      fp_hang

fp_found_sync:

// 同步模板后面紧跟着的4个字节是我们感兴趣

nextpc  return_address_less_4

br      sub_read_int_from_flash_epcs

// 这4个字节是配置的长度,它们的字节顺序是little-endian,但位序是反的。

nextpc  return_address_less_4

br      sub_read_int_from_flash_epcs

// 把长度放到r_flash_ptr 中

mov     r_flash_ptr, r_read_int_return_value

// 此时我们获得了长度但是在EPCS器件中Quarts

// 以相反的位序存贮字节

//

//   我们先把4位组反过来,再把2位组反过来,然后再把所有的位反过来。

//   就象这样:

//

//  76543210 – 4位组反序--> 32107654 – 两位组反序 --> 10325476 – 位反序 --> 01234567

//

//  下面是整个循环的进行机制

//       你会注意到这个反序过程只展示了一次

//       不用担心,所有的字节都会被反序

//

//   ("x" == unknown, "." == zero)

//

//                           byte        temp        mask    count

//                           --------    --------    --------  -----

//   初始态           76543210    xxxxxxxx    00001111    4

//

// 1 temp = byte & mask      76543210    ....3210    00001111    4

// 2 temp <<= count          76543210    3210....    00001111    4

// 3 byte >>= count          xxxx7654    3210....    00001111    4

// 4 byte &= mask            ....7654    3210....    00001111    4

// 5 byte |= temp            32107654    3210....    00001111    4

// 6 count >>= 1             32107654    3210....    00001111    2

// 7 temp = mask << count    32107654    00111100    00001111    2

// 8 mask ^= temp            32107654    00111100    00110011    2

//

//   loop on (count != 0)

//

//   temp = byte & mask      32107654    ..10..54    00110011    2

//   temp <<= count          32107654    10..54..    00110011    2

//   byte >>= count          xx321076    10..54..    00110011    2

//   byte &= mask            ..32..76    10..54..    00110011    2

//   byte |= temp            10325476    10..54..    00110011    2

//   count >>= 1             10325476    10..54..    00110011    1

//   temp = mask << count    10325476    01100110    00110011    1

//   mask ^= temp            10325476    01100110    01010101    1

//

//   loop on (count != 0)

//

//   temp = byte & mask      10325476    .0.2.4.6    01010101    1

//   temp <<= count          10325476    0.2.4.6.    01010101    1

//   byte >>= count          x1032547    0.2.4.6.    01010101    1

//   byte &= mask            .1.3.5.7    0.2.4.6.    01010101    1

//   byte |= temp            01234567    0.2.4.6.    01010101    1

//   count >>= 1             01234567    0.2.4.6.    01010101    0

//   temp = mask << count    01234567    01010101    01010101    0

//   mask ^= temp            01234567    01010101    00000000    0

//

// 初始化mask

movhi   r_revbyte_mask, 0x0F0F

addi    r_revbyte_mask, r_revbyte_mask, 0x0F0F

// 装入count

movi    r_findp_count, 4

fp_reverse_loop:

// 屏蔽高一半的位把结果装入TEMP寄存器

and     r_findp_temp, r_flash_ptr, r_revbyte_mask       // 1

// 把TEMP中的位左移4位

sll     r_findp_temp, r_findp_temp, r_findp_count       // 2

// 把PTR中字节右移4位

srl     r_flash_ptr, r_flash_ptr, r_findp_count         // 3

// 屏蔽掉高4位

and     r_flash_ptr, r_flash_ptr, r_revbyte_mask        // 4

// 把PTR和TEMP中的位组合起来

or      r_flash_ptr, r_flash_ptr, r_findp_temp          // 5

// 更新移位计数器

srli    r_findp_count, r_findp_count, 1                 // 6

// 左移MASK 2位

sll     r_findp_temp, r_revbyte_mask, r_findp_count     // 7

// 更新MASK

xor     r_revbyte_mask, r_revbyte_mask, r_findp_temp    // 8

// 循环直到移位计数器为0

bne     r_findp_count, r_zero, fp_reverse_loop

//

// 这个长度是以位为单位的长度,把它圆整到以字节为单位的长度。

//

addi    r_flash_ptr, r_flash_ptr, 7      // r_flash_ptr += 7

srli    r_flash_ptr, r_flash_ptr, 3      // r_flash_ptr /= 8;

fp_short_circuit:

// 关闭EPCS器件

nextpc  return_address_less_4

br      sub_epcs_close

// 重新打开EPCS器件(at r_flash_ptr)

nextpc  return_address_less_4

br      sub_epcs_open_address

jmp     r_findp_return_address

////////

// EPCS_Open_Address

//

// 打开EPCS器件以便于我们读取给定地址开始的字节流

// 地址在r_flash_ptr给出

//

// 这只是一个sub_tx_rx_int_epcs 子过程的头部

// 没有必要修正返回地址,相反它直接跳转到sub_tx_rx_int_epcs

// 然后让子过程返回到原来的调用者那里。

//

//   寄存器用法:

//       参数:       r_flash_ptr

//       临时寄存器: r_eopen_eclose_tmp

//       返回值:  --none--

//

sub_epcs_open_address:

// 不需要修正返回地址,这只是一个子过程的头部

// 通过控制寄存器使能EPCS器件的片选

movi    r_eopen_eclose_tmp, EPCS_CONTROL_SSO_MASK

stwio   r_eopen_eclose_tmp, EPCS_CONTROL_OFFSET (r_epcs_base_address)

// 把读命令送入既定的寄存器中

movhi   r_epcs_tx_value, (EPCS_COMMAND_READ << 8)

// 把flash指针送入低24位中

or      r_epcs_tx_value, r_epcs_tx_value, r_flash_ptr

// 跳转到sub_tx_rx_int 子过程

br      sub_tx_rx_int_epcs

// 现在EPCS器件已经在r_flash_ptr处打开

////////

// 关闭EPCS

//

// 终止当前的EPCS事务

//

sub_epcs_close:

// 修正返回地址

addi    return_address_less_4, return_address_less_4, 4

// 等待控制器说发送器空

close_ready_loop:

ldwio   r_eopen_eclose_tmp, EPCS_STATUS_OFFSET (r_epcs_base_address)

andi    r_eopen_eclose_tmp, r_eopen_eclose_tmp, EPCS_STATUS_TMT_MASK

beq     r_eopen_eclose_tmp, r_zero, close_ready_loop

// 清除SSO位释放CS

stwio   r_zero, EPCS_CONTROL_OFFSET (r_epcs_base_address)

// 返回

jmp     return_address_less_4   // 我们已经修复了返回地址

////////

// sub_read_int_from_flash_epcs

//

// epcs_rx_tx的另外一个入口

//

// 在进入sub_tx_rx_int_epcs先把epcs_tx_value清0

//

sub_read_int_from_flash_epcs:

// 这个子过程读取EPCS器件的下一个32位word,

// 假设一个有效的读命令和地址已经发出去,片选也是使能的

// 给发送的内容清0。

//

mov     r_epcs_tx_value, r_zero

//

// 进入sub_tx_rx_int_epcs子过程

//

////////

// sub_tx_rx_int_epcs

//

//   这个子过程往flash写4个字节同时也读回4个字节

//   这4个字节没有什么地址对齐的限制

//   这个子过程写的时候是高位在先,读的时候是低位在先

//   因为EPCS处理命令的时候是高位在先,但是SOF文件的

//   编码却是低位在先

//

//   这个子过程和tx_rx_byte共享输入参数

//   只要tx_rx_byte 不破坏它的输入参数,

//   那这么做就是安全的。

//

//   寄存器用法:

//      入口参数:        r_epcs_tx_value

//      局部变量:        r_trie_count

//      局部返回指针:    r_riff_return_address

//      返回的值:        r_read_int_return_value

//

sub_tx_rx_int_epcs:

// 修正返回地址

addi    r_riff_return_address, return_address_less_4, 4

//

// 写(高位在先)然后读(低位在先)

//

// 清楚返回的值

mov     r_read_int_return_value, r_zero

// 发送/接收的字节数

movi    r_trie_count, 4

trie_loop:

// 定位发送字节,使符合参数格式要求

roli    r_epcs_tx_value, r_epcs_tx_value, 8

// 发送/接收一个字节

nextpc  return_address_less_4

br      sub_tx_rx_byte_epcs

// 把它反在结果寄存器的低位字节

or      r_read_int_return_value, r_read_int_return_value, r_read_byte_return_value

// 循环移位结果寄存器以便于最后一个字节在高位字节

//  把其它字节移到低位字节

roli    r_read_int_return_value, r_read_int_return_value, 24

// 计数器减1,继续循环。

subi    r_trie_count, r_trie_count, 1

bne     r_trie_count, r_zero, trie_loop

// 返回

jmp     r_riff_return_address

////////

// sub_read_byte_from_flash_epcs

//

// epcs_rx_tx.的另一个入口

//

//   在进入epcs_tx_rx 之前把epcs_tx_value清0

//

sub_read_byte_from_flash_epcs:

// 该过程读取EPCS器件的下一个字节,

// 假设一个读命令和地址已经发送,片选也已经使能。

//

// 只要发送0给器件,我们就能收到下一个字节。

//

mov     r_epcs_tx_value, r_zero

//

// 进入sub_tx_rx_byte_epcs子过程

//

////////

// sub_tx_rx_byte_epcs

//

// EPCS器件很有趣,每次你发送一些东西,同时也会收到东西。

// 每次你想收到东西,你就必须发送一些东西。

// 这个子过程把它的入口参数内容发送给EPCS, and returns whatever was

// 然后返回它从EPCS获取的值。

//

// 寄存器用法:

//   输入参数:       r_epcs_tx_value

//   临时寄存器:     rf_temp

//   返回值:   r_read_byte_return_value

//

sub_tx_rx_byte_epcs:

// 修正返回地址Fix-up return-address  (NOTE: LEAF)

addi    return_address_less_4, return_address_less_4, 4

// 等待控制器准备好接收TX字节,然后发送它。

tx_ready_loop:

ldwio   rf_temp, EPCS_STATUS_OFFSET (r_epcs_base_address)

andi    rf_temp, rf_temp, EPCS_STATUS_TRDY_MASK

beq     rf_temp, r_zero, tx_ready_loop

stwio   r_epcs_tx_value, EPCS_TXDATA_OFFSET (r_epcs_base_address)

// 等待从EPCS接收的字节有效,然后获取它。

rx_ready_loop:

ldwio   rf_temp, EPCS_STATUS_OFFSET (r_epcs_base_address)

andi    rf_temp, rf_temp, EPCS_STATUS_RRDY_MASK

beq     rf_temp, r_zero, rx_ready_loop

ldbuio  r_read_byte_return_value, EPCS_RXDATA_OFFSET (r_epcs_base_address)

// 返回

jmp     return_address_less_4   // 返回地址已被修正

////////

// 流拷贝

//

//   拷贝r_data_size字节,从r_flash_ptr到r_dest。

//

//   寄存器用法:

//       参数:r_data_size – 要拷贝的字节数

//       参数:r_dest    - 拷贝的目的地址

//       隐含条件:    r_flash_ptr – 拷贝的源地址

//       临时寄存器: rf_temp

//       返回值:无

//

//   所有参数在子过程中都会被破坏

//

//   Note: we don't keep the flash ptr up to date.  Instead

//           we just keep streaming from the EPCS device

//

sub_streaming_copy_epcs:

// 修正返回地址  (NOTE: LEAF)

addi    return_address_less_4, return_address_less_4, 4

// 为了更好的可读性,给r_data_size再定义一个别名

#define r_dest_end r_data_size

// 通过长度计算结束地址

add     r_dest_end, r_data_size, r_dest

subi    r_dest_end, r_dest_end, 1

// 等待EPCS控制器准备好接收TX字节

epcs_copy_initial_wait:

ldwio   rf_temp, EPCS_STATUS_OFFSET (r_epcs_base_address)

andi    rf_temp, rf_temp, EPCS_STATUS_TRDY_MASK

beq     rf_temp, r_zero, epcs_copy_initial_wait

// 给EPCS送0

stwio   r_zero, EPCS_TXDATA_OFFSET (r_epcs_base_address)

//

// do {

//   *r_dest++ = (char*)r_flash_ptr++)

// while (r_dest <= r_dest_end);

//

epcs_copy_loop:

// 等待读取的字节有效

ldwio   rf_temp, EPCS_STATUS_OFFSET (r_epcs_base_address)

andi    rf_temp, rf_temp, EPCS_STATUS_RRDY_MASK

beq     rf_temp, r_zero, epcs_copy_loop

// 读取EPCS的一个字节,并立即要求下一个字节

// 不必等待TX准备好,如果RX准备好了TX也一样。

ldwio   rf_temp, EPCS_RXDATA_OFFSET (r_epcs_base_address)

stwio   r_zero, EPCS_TXDATA_OFFSET (r_epcs_base_address)

// 存贮读到的字节,并更新目的地址指针

stbio   rf_temp, 0(r_dest)

addi    r_dest, r_dest, 1

// 循环直到目的地址指针指向结束地址

bne     r_dest, r_dest_end, epcs_copy_loop

epcs_copy_last_wait:

// 等待最后读取的字节有效

ldwio   rf_temp, EPCS_STATUS_OFFSET (r_epcs_base_address)

andi    rf_temp, rf_temp, EPCS_STATUS_RRDY_MASK

beq     rf_temp, r_zero, epcs_copy_last_wait

// 读取最后一个字节

ldwio   rf_temp, EPCS_RXDATA_OFFSET (r_epcs_base_address)

// 存贮最后一个字节

stbio   rf_temp, 0(r_dest)

// 返回

jmp     return_address_less_4   // Don't worry--we fixed it.

// 文件结束

5.3         boot_loader_cfi_bits.s解读

#include "boot_loader.h"

.global sub_find_payload_cfi                   // 查找数据负荷的子程序

.global sub_read_int_from_flash_cfi                  // 从CFI并行flash中读取32位word的子程序

.global sub_streaming_copy_cfi               // 从CFI并行flash中拷贝流的子程序

////////

// Read_Int_From_Flash_CFI

//

//   伪子程序,它从flash中读取4个字节并把它们拼起来形成一个整数

//   这4个字节没有地址对齐的要求

//   寄存器用法:

//      内部变量:      r_riff_count

//      内部指针:    r_riff_return_address

//      返回值:        r_read_int_return_value

//

sub_read_int_from_flash_cfi:

// 修正中断返回地址,即在返回地址寄存器上加4

addi    r_riff_return_address, return_address_less_4, 4

//

// 读取字节然后把它们移进返回寄存器中

//

// 先对返回寄存器清0

mov     r_read_int_return_value, r_zero

// 返回的字节数

movi    r_riff_count, 4

riffc_loop:

// 返回一个字节并泵进一下r_flash_ptr

ldbuio  r_read_byte_return_value, 0(r_flash_ptr)

addi    r_flash_ptr, r_flash_ptr, 1

// 把它以逻辑或运算的方式送入结果寄存器的低位字节

or      r_read_int_return_value, r_read_int_return_value, r_read_byte_return_value

// 循环左移结果寄存器使最后一个字节在高位字节,

// 把其它字节移到低位字节

roli    r_read_int_return_value, r_read_int_return_value, 24

// 计数器减1并循环

subi    r_riff_count, r_riff_count, 1

bne     r_riff_count, r_zero, riffc_loop

// 返回.

jmp     r_riff_return_address

////////

// 流拷贝

//

//   拷贝 r_data_size 字节从r_flash_ptr 到 r_dest

//

//   寄存器用法:

//       参数:   r_data_size 要拷贝的字节数

//       参数:   r_dest     拷贝的目的地址

//       隐含的寄存器参数:    r_flash_ptr  拷贝的源地址

//       临时寄存器:  rf_temp

//       返回值: 无

//

//   所有的参数寄存器都会在这个子过程中被破坏

//

sub_streaming_copy_cfi:

// 修正返回地址  (NOTE: LEAF)

addi    return_address_less_4, return_address_less_4, 4

// 为更好的可读性,给同一个寄存器定义了两个别名。

#define r_dest_end_plus_one r_data_size

// 把长度转化成结束地址加1

add     r_dest_end_plus_one, r_data_size, r_dest

//

// do {

//   *r_dest++ = (char*)r_flash_ptr++)

// while (r_dest != r_dest_end_plus_one);

//

cfi_copy_loop:

ldbuio  rf_temp, 0(r_flash_ptr)

addi    r_flash_ptr, r_flash_ptr, 1

stbio   rf_temp, 0(r_dest)

addi    r_dest, r_dest, 1

// 循环直到目的地址destination == 1 + 结束地址

bne     r_dest, r_dest_end_plus_one, cfi_copy_loop

// Return

jmp     return_address_less_4   // 不用担心,我们已经修正了它的值。.

////////

// 查找数据负荷

//   把数据负荷的第一个字节的偏移量送到r_flash_ptr返回。

// CFI:

//    数据负荷紧挨着boot-copier的后面存放,使用一些nextpc 这些位置无关

//    的指令来查找。

sub_find_payload_cfi:

// 修正并存贮返回地址

addi    r_findp_return_address, return_address_less_4, 4

nextpc  r_flash_ptr

payload_offset_base:

addi    r_flash_ptr, r_flash_ptr, (end_of_boot_copier - payload_offset_base)

// 找到数据负荷r_flash_ptr现在包含有数据负荷的地址。

jmp     r_findp_return_address

//

// 对于一个基于flash的启动代码,我们把它放在

// |reset地址,然后把数据紧挨着它存放,end_of_boot_copier

// 就是数据负荷的地址。

end_of_boot_copier:

// 数据在这里。

.end

6          Crt0.s解读

Nios II c程序在运行之前需要做一些初始化工作。如果程序直接从falsh中运行则Crt0.s是最先执行的代码,如果程序不是直接从flash中运行则Crt0.s是执行完bootloader后最开始执行的代码。

#include "nios2.h"

#include "system.h"

/*

* 宏ALT_LOAD_SECTIONS用于"run from flash"模式。它用于确定

* 是否有section(.RODATA段,.RWDATA段或.EXCEPTIONS段)

* 需要从flash装到RAM中。如果有的话就调用函数alt_load()加以装载。

*/

#define __ALT_LOAD_SECTIONS(res, text, rodata, exc) /

((res##_BASE != rodata##_BASE) ||                 /  // 如果复位地址和.RODATA段,.RWDATA段

(res##_BASE != rwdata##_BASE) ||                 /          // 或.EXCEPTIONS段所在存贮器基地址不同,

(res##_BASE != exc##_BASE))                                  // 则表明需要装载。符号“##”用于拼接两个名字。

#define _ALT_LOAD_SECTIONS(res, text, rodata, exc) /

__ALT_LOAD_SECTIONS(res, text, rodata, exc)

#define ALT_LOAD_SECTIONS _ALT_LOAD_SECTIONS(ALT_RESET_DEVICE,  /

ALT_RODATA_DEVICE, /

ALT_RWDATA_DEVICE, /

ALT_EXCEPTIONS_DEVICE)

/*

* 这是Nios II的入口地址

*

* 复位的时候只有包含有复位向量的cache line是初始化的,

* 所以第一个cache line 的代码要初始化其它的指令cache。

* 一个指令cache line大小是32个字节,所以代码长度不能超过8个指令。

* 注意:自动生成的linker script要求.init section小于0x20个字节

*/

.section .entry, "xa"            // .entry段可执行可分配的

.align 5               // 和2^5=32字节边界对齐

/*

* 用户C代码要么在hosted mode 的mainn中,要么在standalone mode的alt_main中

*/

.globl main

.globl alt_main

/*

* 生成一个软件multiply/divide中断处理引用

* 这样一旦有下面的宏定义,它们就会被连入可执行文件中。

*/

#ifndef ALT_NO_INSTRUCTION_EMULATION

.globl alt_exception_muldiv

#endif

#ifdef ALT_TRAP_HANDLER

.globl alt_exception_trap

#endif

/*

* 有些工具需要知道reset vector在哪里

*/

.globl __reset

/*

* 连接器定义的符号,用于初始化.bss

*/

.globl __bss_start                          // .bss段的开始地址

.globl __bss_end                            // .bss段的结束地址

/*

* 明确声明可以使用r1 (汇编临时寄存器at)。

* 这个寄存器正常是保留个编译器使用的。

*/

.set noat

.type __reset, @function               // 把__reset作为函数符号

__reset:

#ifdef ALT_RUN_FROM_FLASH

/*

* 如果我们在"run from flash"模式,那我们必须把代码放在

* reset 地址,初始化指令cache后跳转到入口(注意:

* 一旦.text段和reset 地址一样的话,"run from flash"就会

* 被设置).  如果我们没有在"run from flash"模式,那

* boot loader就会初始化指令cache就不需要这段代码了。

*/

/*

*   如果定义了ALT_SIM_OPTIMIZE 那这段代码不会在硬件上运行

*   这个定义移去了初始化的指令cache和数据cache。它假设这些在

*   仿真模型中已经做了

*/

#ifndef ALT_SIM_OPTIMIZE

/* 初始化指令cache的所有cache line */

#if NIOS2_ICACHE_SIZE > 0

/*

* 假设指令cache大小是2的幂

*/

#if NIOS2_ICACHE_SIZE > 0x8000

movhi r2, %hi(NIOS2_ICACHE_SIZE)          // 2的幂最高位为1,其它都是0,所以只要

#else                                                                           // 给高位字节赋值,低位字节清0就可以了。

movui r2, NIOS2_ICACHE_SIZE                            // 小于32k时位长不超过16位,直接赋值就可以。

#endif

0:

initi r2                                                                // Nios II的cache是直接映射型,

addi r2, r2, -NIOS2_ICACHE_LINE_SIZE             // 只要对一段和cache大小一样的内存对应的cache,

bgt r2, zero, 0b                                                 // 初始化即可以达到初始化整个cache的目的。

1:

/*

* 下面的调试信息块告诉仿真器不用运行上面的循环,

* 而使用内部的快速代码

*/

.pushsection .debug_alt_sim_info

.int 1, 1, 0b, 1b

.popsection

#endif /* NIOS2_ICACHE_SIZE > 0 */

/*

* 初始化cache后调用.text段的入口

*/

#endif /* ALT_SIM_OPTIMIZE */

movhi r1, %hiadj(_start)                // 装入_start的高16位

addi r1, r1, %lo(_start)                           // 装入_start的低16位

jmp r1                                              // 跳转到.text段入口

.size __reset, . - __reset               // 给函数符号__reset设置大小=当前位置-__reset开始的位置

#endif

/*

* .text段的开始,当程序用loader装载运行的时候同时也是代码的入口

*/

.section .text

.align 2                 // 4字节对齐

.globl _start

.type _start, @function                 // 把_start作为函数符号

_start:

/*

*   如果定义了 ALT_SIM_OPTIMIZE那这段代码不会在硬件上运行。

*   这个宏定义移去了指令和数据cache的初始化部分,我们假设仿真

*   模型已经做了这些工作。

*/

#ifndef ALT_SIM_OPTIMIZE

/*

* 在初始化指令cache后我们必须初始化数据cache

*/

#if NIOS2_DCACHE_SIZE > 0

/*

* 假设数据cache大小是2的幂

*/

#if NIOS2_DCACHE_SIZE > 0x8000

movhi r2, %hi(NIOS2_DCACHE_SIZE)                           // 2的幂只有最高位是1,其它位都是0

#else                                                                            // 所以大于32k的数,只要存高位字节就可以

movui r2, NIOS2_DCACHE_SIZE                                    // 其它位置为0,小于32k的数,则可以直接

#endif                                                                                   // 赋值。

0:

initd 0(r2)                                                           // Nios II的cache是直接映射型的,所以只要

addi r2, r2, -NIOS2_DCACHE_LINE_SIZE                     // 初始化任何一块和cache一样大小的内存相关

bgt r2, zero, 0b                                                          // cache就可以初始化整个cache。

1:

/*

* 下面的调试信息块告诉仿真器不用执行上面的循环,

* 而是执行内部的快速代码。

*/

.pushsection .debug_alt_sim_info

.int 2, 1, 0b, 1b

.popsection

#endif /* NIOS2_DCACHE_SIZE > 0 */

#endif /* ALT_SIM_OPTIMIZE */

/*

* 现在caches已经被初始化,设置栈指针。

* 我们假设由连接器提供的值已经4字节对齐了。

*/

movhi sp, %hiadj(__alt_stack_pointer)           // __alt_stack_pointer由连接器脚本定义。

addi sp, sp, %lo(__alt_stack_pointer)

/* 设置global pointer. */

movhi gp, %hiadj(_gp)                                               // _gp由连接器脚本定义。

addi gp, gp, %lo(_gp)

#ifdef ALT_STACK_CHECK

/*

* 如果需要的化就设置栈顶变量。连接器已经在存贮器中设置了该变量的拷贝

*/

ldw   et, %gprel(alt_stack_limit_value)(gp)

#endif

#ifndef ALT_SIM_OPTIMIZE

/*

* 给.bss段清0。

*

* 这里使用了符号:__bss_start and __bss_end,,这些在连接器脚本

* 中定义的变量。它们标志了.bss的开始和结束,连接器脚本保证

* 这些值都是32位对齐的。

*/

movhi r2, %hiadj(__bss_start)

addi r2, r2, %lo(__bss_start)

movhi r3, %hiadj(__bss_end)

addi r3, r3, %lo(__bss_end)

beq r2, r3, 1f

0:                                                     // 给.bss段清0。

stw zero, (r2)

addi r2, r2, 4

bltu r2, r3, 0b

1:

/*

* 下面的调试信息块告诉仿真器不用执行上面的循环,

* 而执行内部的快速代码。

*/

.pushsection .debug_alt_sim_info

.int 3, 1, 0b, 1b

.popsection

#endif /* ALT_SIM_OPTIMIZE */

/*

* 如果是从flash中运行的就把其它段装入RAM中。

*/

#ifdef ALT_RUN_FROM_FLASH                         // 如果没有bootloader即从flash直接执行,

#if ALT_LOAD_SECTIONS                          // 判断是否有段需要从flash中装到RAM中,

call alt_load                                               // 有的话就调用alt_load函数装载。

#endif /* ALT_LOAD_SECTIONS */

#endif /* ALT_RUN_FROM_FLASH */

/* 调用C入口 */

call alt_main

/* alt_main永远都不会返回,所以我们在这里不需要再做任何事情。

*/

.size _start, . - _start                             // 给函数符号_start赋值大小=当前位置-_start开始的地址

#ifdef ALT_STACK_CHECK

/*

* 如果我们想检查堆栈溢出那我们需要知道堆栈的基地址

*/

.globl  alt_stack_limit_value

.section .sdata,"aws",@progbits

.align  2

.type   alt_stack_limit_value, @object

.size   alt_stack_limit_value, 4

alt_stack_limit_value:

.long   __alt_stack_limit

#endif

http://blog.csdn.net/haijiaoyouzi/article/details/3165057

Nios II的Boot过程分析的更多相关文章

  1. 关于Nios II的启动分析(转载)

    原文地址:http://hi.baidu.com/goatdai/item/cc33671545d89243e75e06ad 常用到的存储器包括SDRMA.SRAM.FLASH.Onchip_memo ...

  2. Nios II uCLinux/Linux启动分析

    1. 说明 本文采用的Linux源码版本来自Altera公司FTP.不考虑zImage生成的Compress过程.因为zImage是内核binary文件经过gzip 压缩,并在头部添加解压缩代码实现的 ...

  3. NIOS II 中直接调用Modelsim仿真

    STEP1:创建一个工程,实现并编译该工程,编写TestBench文件. STEP2:设置启动Modelsim的路径 选择Nios II菜单Tools->Options..,在弹出的界面中,选择 ...

  4. 初探NIOS II之hello_world

    平台背景: 操作系统:win7  64bit 开发板:DE2-115 Quartus ii:15.0及配套的NIOS ii开发平台 一.硬件系统的建立 1.在Quartus里新建工程,这是很基本的就不 ...

  5. NIOS ii 流水灯

    为了做项目的前期验证工作,实验室购买了某开发板,下面是基于该板子的实现过程.作为笔记记录,供入门者参考. 1:创建一个Quartus II的工程 next选择器件,然后finish.我的器件是cycl ...

  6. sof文件和NIOS II的软件(elf)合并为jic文件以使用Quartus Programmer烧写

    将Altera FPGA的sof文件和NIOS II的elf固件合并为一个jic文件以使用Quartus Programmer烧写   我们在学习和调试NIOS II工程的时候,一般都是先使用Quar ...

  7. 给NIOS II CPU增加看门狗定时器并使用

    给NIOS II CPU增加看门狗定时器并使用   配置看门狗定时器: 设置计时溢出时间为1秒 计数器位宽为32位 勾选No Start/Stop control bits 勾选Fixed perio ...

  8. 给NIOS II CPU添加一颗澎湃的心——sysclk的使用

    给NIOS II CPU添加一颗澎湃的心——系统时钟的使用 本实验介绍如何在Qsys中添加一个定时器作为NIOS II的心跳定时器,并在NIOS II中软件编程使用该定时器. 将上一个实验watchd ...

  9. NIOS II CPU复位异常的原因及解决方案

    NIOS II CPU复位异常的原因及解决方案   近期在用nios ii做项目时,发现一个奇怪的现象,在NIOS II EDS软件中编写好的代码,烧写到芯片中,第一次能够正常运行,但是当我按下板卡上 ...

随机推荐

  1. (二十一)golang--字符串中的函数

    golang中ascii对应的字符占一个字节,而汉字占三个字节. (1)统计字符串的长度len (2)字符串遍历,同时处理有中文的问题r:=[]rune(str) (3)字符串转整数:n,err:= ...

  2. 产品分析:华为短信APP体验的问题和建议

  3. [ISE调试] 在ISE调试过程中,遇到过的error以及消除办法

    1.Incompatible IOB's are locked to the same bank 15,具体如右图, 于是去查引脚配置,发现 也就是说,在bank=15的这组IO里面,我既选了LVAM ...

  4. SpringBoot系列教程JPA之指定id保存

    原文链接: 191119-SpringBoot系列教程JPA之指定id保存 前几天有位小伙伴问了一个很有意思的问题,使用 JPA 保存数据时,即便我指定了主键 id,但是新插入的数据主键却是 mysq ...

  5. 初探three.js

    相信大多数选择前端的小伙伴都有一个设计师的梦,今天我来说一说three.js.three.js是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机.光影.材质等各种对象.学习了 ...

  6. 扛把子组20191121-10 Scrum立会报告+燃尽图 06

    此作业的要求参见http://edu.cnblogs.com/campus/nenu/2019fall/homework/10070 一.小组情况: 队名:扛把子 组长:孙晓宇 组员:刘信鹏 韩昊 宋 ...

  7. python3 之 函数传参

    一.可变对象与不可变对象 在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象. 不可变类型:变量赋值 a=5 后 ...

  8. setBounds方法,与setLayout(null)

    首先把相关容器的布局方式设为 setLayout(null); 然后调用组件的  setBounds() 方法 设置button的位置为(100,100) 长宽分别为 60,25 jButton.se ...

  9. Kafka原理详解

    Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量 ...

  10. Xamarin.Forms 学习系列之优秀UI收集

    1.各种优秀UI例子 https://github.com/jsuarezruiz/xamarin-forms-goodlooking-UI 输入框例子 https://github.com/enis ...