本例程主要是让 SoC FPGA 初学者了解 HPS/ARM 如何跟 FPGA 交互。“My First HPS-FPGA”工程演示了实现方法的细节。这个工程包括 Quartus II 工程和 ARM C 工程,它演示了 HPS/ARM 是如何去控制 FPGA 端的 LED。 html
在 Altera SoC FPGA 中,HPS 和 FPGA 之间的协议通讯主要是经过 AXI -bridge. AXI bridge 是 FPGA 和 HPS之间数据交互的接口总线,它包括 FPGA-to-HPS AXI、HPS-to-FPGA AXI 和 Light-weight HPS-to-FPGA AXI。
AXI(Advanced eXtensible Interface)是一种总线协议,该协议是ARM公司提出的AMBA(Advanced Microcontroller Bus Architecture)3.0协议中最重要的部分,是一种面向高性能、高带宽、低延迟的片内总线。它的地址/控制和数据相位是分离的,支持不对齐的数据传输,同时在突发传输中,只须要首地址,同时分离的读写数据通道、并支持Outstanding传输访问和乱序访问,并更加容易进行时序收敛。
linux
在Altera SOC FPGA中,HPS 做为主端(master),其能够访问 FPGA 端 Avalon MM slave 接口的全部组件。 HPSshell
做为主端时的 AXI-bridge 包括:bash
FPGA 做为主端时的 AXI-bridge 包括:
架构
下图FPGA 架构和链接到 HPS 的 L3 Switch 的 AXI 桥的方块图,每一个主端(M)和从端(S)都标示出了个字的位宽。 app
能够看出: 函数
HPS-to-FPGA 桥是被 level 3(L3) main switch 掌控,轻量级 HPS-to-FPGA 桥是被 L3slave peripheral switch 掌控。在这个 Quartus II 演示程序中,HPS-to-FPGA 被 ARM/HPS用来控制 FPGA 端的 LEDs。 oop
FPGA-to-HPS bridge 也能够做主端控制 L3 main switch,容许 FPGA 端的主端访问大部分的 HPS 从端。好比,FPGA 资源能够经过 FPGA-to-HPS 桥能够访问到 HPS 端的加速度传感器。 post
设计实现基于ARM的linux应用程序控制FPGA端的PIO控制器pio_led。pio链接到HPS/ARM linghtweitht axi bridge从而得到在HPS/ARM总线上的物理地址空间。linux 应用程序经过 linux 内核 memory-mapped device 驱动访问 PIO 控制器 pio_led 的寄存器物理地址进而控制 pio_led 进行相应动做。Altera SoCEDS 用来编译应用程序。 性能
显然,咱们的工程由两个部分组成
linux控制pio_led组件须要用到pio_led组件的属性信息,可是咱们的pio_led是由QSYS建立添加的,全部咱们须要用一个脚本生成对应的头文件来提供给Linux调用。
在工程根目录下建立脚本,脚本名为generate_hps_qsys_header.sh。如图所示
在其内写下以下指令
#!/bin/sh sopc-create-header-files \ #命令 "./som_hps.sopcinfo" \ #qsys文件--当前文件下 --single hps_0.h \ #要生成的文件 --module hps_som #模块名
运行 Altera SoC EDS command shell经过 shell 命令 cd 定位到 QuartusII 工程文件夹根目录。输入‘./generate_hps_qys_header.sh”并按 Enter 键执行,成功执行后,会生出名为 hps_0.h 的头文件。
打开生成的hps_0.h头文件,发现 在头文件中,包含 pio_led 在 Qsys 中分配的相对于 lwaxi 的基地址,它表现为一个宏定义 PIO_LED_BASE;pio_led 的位宽信息表示为宏定义PIO_LED_DATA_WIDTH.这两个参数将会是应用程序访问 pio_led 寄存器所须要的。
新建一个文件夹,将hps_0.h文件复制剪切到文件夹下,而后编写main.c程序。这里对mian.c程序的几个要点进行下讲解。
获得物理地址后,咱们还需将 pio_led 的物理地址映射成应用程序能够访问的虚拟地址。 下列程序展现了 C 应用程序从 pio_led 基地址转换出虚拟地址。
if( ( fd = open( "/dev/mem", ( O_RDWR | O_SYNC ) ) ) == -1 ) { printf( "ERROR: could not open \"/dev/mem\"...\n" ); return( 1 ); } virtual_base = mmap( NULL, HW_REGS_SPAN, ( PROT_READ | PROT_WRITE ), MAP_SHARED, fd, HW_REGS_BASE ); if( virtual_base == MAP_FAILED ) { printf( "ERROR: mmap() failed...\n" ); close( fd ); return( 1 ); } h2p_lw_led_addr=virtual_base + ( ( unsigned long )( ALT_LWFPGASLVS_OFST + PIO_LED_BASE ) & ( unsigned long)( HW_REGS_MASK ) );
首先,系统调用函数 open用来打开 memory 设备驱动“/dev/mem”,而后用系统调用函数 mmap 映射 HPS 的 L3 外设区域物理地址到虚拟地址并表示为一个空指针变量 virtual_base.而后能够经过virtual_base 增长以下两个偏移地址计算得出 pio_led 的虚拟地址。
第一个偏移地址在 hps.h 中被宏定义为 ALT_LWFPGASLVS_OFST。
第二个偏移地址是 pio_led 在 Qsys 中分配的相对 lwaxi 的基地址,这个在以前提到的头文件 hps_0.h 中,被宏定义为 PIO_LED_BASE。
pio_led 的虚拟地址被定义为空指针 h2p_lw_led_addr. 应用程序能够直接用这个指针变量访问 pio_led 控制器的寄存器。
在控制 led 以前须要理解 PIO 控制器 pio_led 的寄存器映射。PIO 控制器的映射关系图以下。 每一个寄存器都是 32 位宽度的。更详细的信息请参考 PIO 控制器的datasheet。
对于 LED 控制,咱们仅仅须要写输出值到偏移地址为 0 的寄存器。因为DE1-SoC 上的 LED 是高电平有效,因此写 0x00000000 到偏移地址为 0 的寄存器,十个红色 LED 将会熄灭。写 0x000003ff 到偏移地址为 0 的寄存器,十个红色 LED 将会亮起。
在 本例的C 程序中,写值到偏移地址为 0 的寄存器的 C 语言表达式为:
loop_count = 0; led_mask = 0x01; led_direction = 0; // 0: left to right direction while( loop_count < 60 ) { // control led, add ~ because the led is low-active *(uint32_t *)h2p_lw_led_addr = ~led_mask; // wait 100ms usleep( 100*1000 ); // update led mask if (led_direction == 0){ led_mask <<= 1; if (led_mask == (0x01 << (PIO_LED_DATA_WIDTH-1))) led_direction = 1; }else{ led_mask >>= 1; if (led_mask == 0x01){ led_direction = 0; loop_count++; } } } // while
其中*(uint32_t *)h2p_lw_led_addr = ~led_mask;会将空指针转换成无符号32位整型指针。因此C编译器知道是写32位值到h2p_lw_led_addr虚拟地址。
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include "hwlib.h" #include "socal.h" #include "hps.h" #include "alt_gpio.h" #include "hps_0.h" #define HW_REGS_BASE ( ALT_STM_OFST ) #define HW_REGS_SPAN ( 0x04000000 ) #define HW_REGS_MASK ( HW_REGS_SPAN - 1 ) int main() { void *virtual_base; int fd; int loop_count; int led_direction; int led_mask; void *h2p_lw_led_addr; // map the address space for the LED registers into user space so we can interact with them. // we'll actually map in the entire CSR span of the HPS since we want to access various registers within that span if( ( fd = open( "/dev/mem", ( O_RDWR | O_SYNC ) ) ) == -1 ) { printf( "ERROR: could not open \"/dev/mem\"...\n" ); return( 1 ); } virtual_base = mmap( NULL, HW_REGS_SPAN, ( PROT_READ | PROT_WRITE ), MAP_SHARED, fd, HW_REGS_BASE ); if( virtual_base == MAP_FAILED ) { printf( "ERROR: mmap() failed...\n" ); close( fd ); return( 1 ); } h2p_lw_led_addr=virtual_base + ( ( unsigned long )( ALT_LWFPGASLVS_OFST + PIO_LED_BASE ) & ( unsigned long)( HW_REGS_MASK ) ); // toggle the LEDs a bit loop_count = 0; led_mask = 0x01; led_direction = 0; // 0: left to right direction while( loop_count < 60 ) { // control led, add ~ because the led is low-active *(uint32_t *)h2p_lw_led_addr = ~led_mask; // wait 100ms usleep( 100*1000 ); // update led mask if (led_direction == 0){ led_mask <<= 1; if (led_mask == (0x01 << (PIO_LED_DATA_WIDTH-1))) led_direction = 1; }else{ led_mask >>= 1; if (led_mask == 0x01){ led_direction = 0; loop_count++; } } } // while // clean up our memory mapping and exit if( munmap( virtual_base, HW_REGS_SPAN ) != 0 ) { printf( "ERROR: munmap() failed...\n" ); close( fd ); return( 1 ); } close( fd ); return( 0 ); }
# TARGET = my_first_hps-fpga # CROSS_COMPILE = arm-linux-gnueabihf- CFLAGS = -static -g -Wall -I${SOCEDS_DEST_ROOT}/ip/altera/hps/altera_hps/hwlib/include LDFLAGS = -g -Wall CC = $(CROSS_COMPILE)gcc ARCH= arm build: $(TARGET) $(TARGET): main.o $(CC) $(LDFLAGS) $^ -o $@ %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY: clean clean: rm -f $(TARGET) *.a *.o *~
一样,打开Altera Embedded Command Shell,到工程目录下,编译hps工程以下所示。
首先,打开quartus II下载以前HPS基本概念及其设计工程的.sof文件到FPGA
而后将Hps生成的可执行文件,复制到linux系统中,并运行。
可看到FPGA侧的10个led等依次闪烁。并在执行60次后中止。