1、学习目的

  本例程主要是让 SoC FPGA 初学者了解 HPS/ARM 如何跟 FPGA 交互。“My First HPS-FPGA”工程演示了实现方法的细节。这个工程包括 Quartus II 工程和 ARM C 工程,它演示了 HPS/ARM 是如何去控制 FPGA 端的 LED。

2、关于DE1-SOC的AXI总线(详细AXI总线协议看链接,这里不做详细介绍)

  在 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传输访问和乱序访问,并更加容易进行时序收敛。

  在Altera SOC FPGA中,HPS 作为主端(master),其可以访问 FPGA 端 Avalon MM slave 接口的所有组件。   HPS

  作为主端时的 AXI-bridge 包括:

  •  HPS-to-FPGA Bridge
  •  Lightweight HPS-to-FPGA Bridge

FPGA 作为主端时的 AXI-bridge 包括:

  •  FPGA-to-HPS Bridge

下图FPGA 架构和连接到 HPS 的 L3 Switch 的 AXI 桥的方块图,每个主端(M)和从端(S)都标示出了个字的位宽。

可以看出:

  • FPGA-to-HPS Bridge

    •   地址位宽时32-bit,数据位宽32-bit/64-bit/128-bit用户可以自行设置,ID时8-bit
  • HPS-to-FPGA Bridge

    •   址位宽时30-bit,数据位宽32-bit/64-bit/128-bit用户可以自行设置,ID时128-bit。有0x3FFF0000,接近1G 的寻址空间。
  • Lightweight HPS-to-FPGA Bridge

    •   地址位宽时21-bit,数据位宽只有32-bit,ID时8-bit。寻址空间只有2M,适合数据量不大、速度不快的数据传输。

  

   HPS-to-FPGA 桥是被 level 3(L3) main switch 掌控,轻量级 HPS-to-FPGA 桥是被 L3slave peripheral switch 掌控。在这个 Quartus II 演示程序中,HPS-to-FPGA 被 ARM/HPS用来控制 FPGA 端的 LEDs。

  FPGA-to-HPS bridge 也可以作主端控制 L3 main switch,允许 FPGA 端的主端访问大部分的 HPS 从端。比如,FPGA 资源可以通过 FPGA-to-HPS 桥可以访问到 HPS 端的加速度传感器。

3、项目目的

  设计实现基于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 用来编译应用程序。

4、项目组成

  显然,我们的工程由两个部分组成

  1. 带HPS的FPGA工程(这里我们采用HPS基本概念及其设计文章中创建的工程)
  2. 我们自己编写的相关HPS软件工程及与FPGA相关生成的头文件

5、生成HPS头文件

  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 寄存器所需要的。

6、编写HPS程序

  新建一个文件夹,将hps_0.h文件复制剪切到文件夹下,然后编写main.c程序。这里对mian.c程序的几个要点进行下讲解。

1、映射pio_led地址

  得到物理地址后,我们还需将 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-to-FPGA AXI 总线相对于 HPS 的 L3 外设区域基地址的偏移地址
  • pio_led 相对于轻量级 HPS-to-FPGA AXI 总线的偏移地址

第一个偏移地址在 hps.h 中被宏定义为 ALT_LWFPGASLVS_OFST。

第二个偏移地址是 pio_led 在 Qsys 中分配的相对 lwaxi 的基地址,这个在之前提到的头文件 hps_0.h 中,被宏定义为 PIO_LED_BASE。

pio_led 的虚拟地址被定义为空指针 h2p_lw_led_addr. 应用程序可以直接用这个指针变量访问 pio_led 控制器的寄存器。

2、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虚拟地址。

3、完整的main.c程序如下所示

#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 );
}

4、对应的Makefile规则如下

#
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 *~

  

7、编译工程及实验现象

  同样,打开Altera Embedded Command Shell,到工程目录下,编译hps工程如下所示。

  首先,打开quartus II下载之前HPS基本概念及其设计工程的.sof文件到FPGA

  然后将Hps生成的可执行文件,复制到linux系统中,并运行。

可看到FPGA侧的10个led等依次闪烁。并在执行60次后停止。

通过HPS控制FPGA的GPIO的更多相关文章

  1. 通过HPS控制FPGA端的GPIO

    该笔记主要记录HPS端如何通过AXI Bridge控制FPGA端口的GPIO,主要是如何操作FPGA侧的Led 1.AXI Bridge         AXIB主要包括H2FB.F2HB.LWH2F ...

  2. HPS端如何通过AXI Bridge控制FPGA端口的GPIO

    该笔记主要记录HPS端如何通过AXI Bridge控制FPGA端口的GPIO,主要是如何操作FPGA侧的Led 1.AXI Bridge         AXIB主要包括H2FB.F2HB.LWH2F ...

  3. 关于HPS和FPGA之间的桥接学习笔记一

    为了实现FPGA和HPS之间的存储器共享和数据传输,Altera SoC FPGA提供了两种方式用于FPGA和HPS通信.分别是FPGA to SDRAM和AXI bridge. FPGA to SD ...

  4. linux驱动初探之杂项设备(控制两个GPIO口)

    关键字:linux驱动.杂项设备.GPIO 此驱动程序控制了外接的两个二极管,二极管是低电平有效. 上一篇博客中已经介绍了linux驱动程序的编写流程,这篇博客算是前一篇的提高篇,也是下一篇博客(JN ...

  5. 基于boa服务器的web控制mini2440的GPIO口

    win7 系统  虚拟机:ubuntu12.04 开发板:mini2440 上一篇已经详细的讲解了如何配置boa服务器,在这里我们就要利用boa服务器带来的便利,利用web控制开发板上的GIPO口,这 ...

  6. FPGA开发随笔汇总

    点击标题即可进入相关随笔. DE-SOC开发板VrilogHDL开发相关部分: (本过程需要Verilog HDL 的基本语言基础) 1.FPGA的发展史及FPGA 的基础架构 2.首先看一下友晶DE ...

  7. HPS基本概念及其设计

    DE1-SOC开发版上的FPGA在一个基于ARM的用户定制系统(SOC)中集成了分立处理器(HPS).FPGA和数字信号处理(DSP)功能.HPS是基于ARM cortex-A9双核处理器,具有丰富的 ...

  8. 可编程逻辑(FPGA)与硬核处理器(HPS)之间互联的结构

    本周我想进一步探究可编程逻辑(FPGA)与硬核处理器(HPS)之间互联的结构.我发现了三种主要方式,它们是如何映射并处理通信的,哪些组件需要管控时序并且有访问权限. AXI Bridge 为了能够实现 ...

  9. Kernel 中的 GPIO 定义和控制

    最近要深一步用到GPIO口控制,写个博客记录下Kernel层的GPIO学习过程! 一.概念 General Purpose Input Output (通用输入/输出)简称为GPIO,或 总线扩展器. ...

随机推荐

  1. mysql数据库存中文字段

    mysql数据默认编码是拉丁,而我们更多的使用utf8, 在创建库的时候执行参数即可: CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET ...

  2. T-SQL中CTE表 with关键字

    Select字句在逻辑上是SQL语句最后进行处理的最后一步,所以,以下查询会发生错误: SELECT YEAR(OrderDate) AS OrderYear, COUNT(DISTINCT Cust ...

  3. mysql 索引type介绍

    以下全部详细解析explain各个属性含义: 各属性含义:    id: 查询的序列号    select_type: 查询的类型,主要是区别普通查询和联合查询.子查询之类的复杂查询 SIMPLE:查 ...

  4. linux显示历史命令history

    history history//显示历史命令

  5. 通过scp拷贝文件时无需交互输入密码

    工作中经常需要把一些文件从一个服务器传输到另一台服务器,linux环境下最习惯的方式当然是scp,但是scp需要交互输入密码有时候觉得麻烦,记录几种无需手动输入密码的方法. 方法一:建立SSH互信 此 ...

  6. MySQL InnoDB引擎B+树索引简单整理说明

    本文出处:http://www.cnblogs.com/wy123/p/7211742.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错 ...

  7. c++冒号作用

    转自http://www.360doc.com/content/13/0605/11/3373961_290615318.shtml 1.冒号(:)用法 (1)表示机构内位域的定义(即该变量占几个bi ...

  8. R语言-增加图例

    legend()函数 > plot(rain$Tokyo,type="l",col="red", + ylim=c(0,300), + main=&quo ...

  9. 【VBA】杨辉三角

    Private Sub Workbook_Open() Dim loopA As Integer Dim loopB As Integer Dim loopNum As Integer Dim top ...

  10. Django的rest_framework的视图之基于ModelViewSet视图源码解析

    前言 今天一直在整理Django的rest_framework的序列化组件,前面一共写了2篇博客,前面的博客给的方案都是一个中间的状态的博客,其中有很多的冗余的代码,如果有朋友不清楚,可以先看下我前面 ...