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. 到底什么是哈希Hash?

    知识点总结 ---------------------------------------------------------------------------------------------- ...

  2. SpringMVC 数组类型的参数: Cannot generate variable name for non-typed Collection parameter type

    我只想安静的传个数组类型的参数, 为什么各种报错... @DeleteMapping("del") @ApiOperation(value = "删除") pu ...

  3. <记录> HtmlHelper和 强类型页面

    HtmlHelper  路径生成 <!--普通写法--> <a href="/home/index">超链接</a> <!--利用Url类 ...

  4. 源码阅读经验谈-slim,darknet,labelimg,caffe(1)

    本文首先谈自己的源码阅读体验,然后给几个案例解读,选的例子都是比较简单.重在说明我琢磨的点线面源码阅读方法.我不是专业架构师,是从一个深度学习算法工程师的角度来谈的,不专业的地方请大家轻拍. 经常看别 ...

  5. SQL With (递归CTE查询)

    指定临时命名的结果集,这些结果集称为公用表表达式 (CTE).该表达式源自简单查询,并且在单条 SELECT.INSERT.UPDATE 或 DELETE 语句的执行范围内定义.该子句也可用在 CRE ...

  6. Html转成Image

    html转图片,原来用了html2image.jar,但是做不到复杂的css js渲染效果. 在网友推荐下,可以用wkhtmltoimage插件,这是用谷歌浏览器的内核webkit,网上搜一下可以下载 ...

  7. mysql 表结构

    1.登录数据库>mysql -u root -p 数据库名称 2.查询所有数据表>show tables; 3.查询表的字段信息>desc 表名称; 4.1添加表字段 alter t ...

  8. Tomcat9 在Windows中配置允许远程访问

    环境:Windows  Server 2019 Data Center+Tomcat 9 Tomcat在Windows中安装好了之后,默认只能从本机以http://localhost:8080的方式访 ...

  9. day41 mysql详细操作

    复习 create table 表名( id int primary key auto_increment, 字段名 数据类型[(宽度) 约束] )engine=innodb charset=utf8 ...

  10. gdbserver

    这里写下gdbserver的用法: 两台机子,宿主机A和目标机B. step1: 我们在B上安装gdbserver,在A上编译可执行程序a.out,把a.out拷贝到B上面去. step2: 在A上打 ...