Difference between revisions of "FPGA registers access from Linux userspace"

From ArmadeusWiki
Jump to: navigation, search
(Introduction)
 
(26 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
[[Category:FPGA]]
 
[[Category:FPGA]]
  
 +
== Introduction ==
  
==Changing FPGA IP's registers from Linux user space with fpgaregs==
+
There are '''two methods''' to access FPGA registers on APF*.
  
 +
* For '''APF9328, APF27 and APF51''' registers access are done with a memory bus (parallel).
 +
* For '''APF6_SP''' registers access are done with PCIe
  
This tool allows you to access FPGA registers from linux userspace/console. This way you can easily debug your driver and your IP directly from the Linux console.
+
== Memory bus ==
 +
=== fpgaregs ===
 +
====Installation====
 +
If the fpgaregs package is not already installed on your APF (''/usr/bin/fpgaregs''), you can select it in the Buildroot menuconfig:
 +
<pre class="host">
 +
$ make menuconfig
 +
</pre>
  
=== fpgaaccess driver ===
+
<pre class="config">
First, you have to compile fpgaaccess driver, select it from linux-menuconfig menu (Device Drivers ---> Armadeus specific drivers ---> FPGA Drivers   ---> Armadeus fpgaaccess driver) :
+
Package Selection for the target  --->  
$ make linux-menuconfig
+
    [*] Hardware handling / blockdevices and filesystem maintenance --->  
 +
        [*]   fpgaregs
 +
</pre>
  
You can select it under kernel or as module then compile it:
+
<pre class="host">
  $ make
+
  $ make  
 +
</pre>
  
If you choose module (M) you have to insert it in kernel (the module is in armadeus/target/linux/modules/fpga/fpgaaccess/fpgaaccess.ko):
+
Then reflash your rootfs or install it manually.
$ insmod fpgaaccess.ko
+
  
This will load a characters device, you can find Major and minor number in dmesg :
+
====Usage====
  
$ dmesg | grep fpgaaccess
+
''fpgaregs'' can be used to do read or write accesses (16 or 32 bits wide) to the FPGA, from Linux userspace/console.
fpgaaccess: MAJOR: 250 MINOR: 0
+
  
Then make the node :
+
=====16 bits read=====
$ mknod /dev/fpgaaccess c 250 0
+
<pre class="apf">
 +
# fpgaregs w <address>
 +
</pre>
  
=== fpgaregs user binary ===
+
Where <address> is an address relative to FPGA's mapping in hexadecimal value. Example:
 +
<pre class="apf">
 +
# fpgaregs w 0
 +
</pre>
  
If you want you can use the driver with system call '''pread()''' and '''pwrite()''', but it easiest to use the program provided by driver : fpgaregs.
+
=====16 bits write=====
 +
<pre class="apf">
 +
# fpgaregs w <address> <value>
 +
</pre>
  
To compile it, go to ''target/linux/module/fpga/fpgaaccess'' directory then make it :
+
Where <value> is hexadecimal value to write.
$ make fpgaregs
+
  
The generated file is located in ''target/linux/module/fpga/fpgaaccess''. Now you can copy it on your target.
+
=====32 bits read=====
Then in linux, launch it like that:
+
<pre class="apf">
*for read:
+
# fpgaregs l <address>
# fpgaregs hex_address
+
</pre>
*for write
+
# fpga hex_address hex_value
+
*Examples:
+
**Show register at internal FPGA address 0x0010:
+
# fpgaregs 0x10
+
  
**Write 0x0123 to FPGA register 0x0020:
+
=====32 bits write=====
# fpgaregs 0x20 0x0123
+
<pre class="apf">
 +
# fpgaregs l <address> <value>
 +
</pre>
 +
 
 +
=== the mmap problem ===
 +
 
 +
{{Note| This problem is corrected with latest Toolchains compilation options}}
 +
 
 +
First of all, you need to get a file descriptor for ''/dev/mem'' using the ''open()'' function
 +
<source lang="c">
 +
ffpga=open("/dev/mem",O_RDWR|O_SYNC);
 +
</source>
 +
Now you have a valid file descriptor to access your FPGA.
 +
 
 +
The O_SYNC option is recommended to avoid Linux to cache the content of /dev/mem and delay any modification done in this file.
 +
 
 +
 
 +
To access fpga register, fpgaregs use the ''mmap()'' system call :
 +
 
 +
<source lang="c">
 +
void * ptr_fpga;
 +
ptr_fpga = mmap (0, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, ffpga, FPGA_ADDRESS);
 +
</source>
 +
 
 +
Thanks to this function, fpga registers are accessible directly on memory with pointer ptr_fpga. To read and write in 16bits or in 32 bits we will cast the pointer value in ''unsigned short'' or ''unsigned int'' :
 +
 
 +
'''16bits'''
 +
''write''
 +
<source lang="c">
 +
        *(unsigned short*)(ptr_fpga+(address)) = (unsigned short)value;
 +
</source>
 +
''read''
 +
<source lang="c">
 +
          value = *(unsigned short*)(ptr_fpga+(address));
 +
</source>
 +
 
 +
'''32 bits'''
 +
''write''
 +
<source lang="c">
 +
        *(unsigned int*)(ptr_fpga+(address)) = (unsigned short)value;
 +
</source>
 +
 
 +
''read''
 +
<source lang="c">
 +
          value = *(unsigned int*)(ptr_fpga+(address));
 +
</source>
 +
 
 +
==== The problem ====
 +
 
 +
By default, if the specific ''arm920t'' target is not specified, ''arm-linux-gcc'' will try to generate compatible read/write for all ARM9 model when it access register in 16bits. Indeed it seems that not all ARM9 have 16bits read/write capabilities (''ldrh'' asm instruction).
 +
 
 +
As the interface between i.MXL and FPGA on APF9328 has no 8bits read/write capabilities, each 8 bits access is recognized by the FPGA as a 16bits access. So on each 16bits access of the i.MXL, FPGA will process two 16bits access instead of 1. That is a problem when accessing a FIFO for example.
 +
 
 +
To avoid this painful problem don't forget the ''-mcpu=arm920t'' option when compiling ''fpgaregs'' for APF9328 and ''-mcpu=arm926ej-s'' for APF27.
 +
 
 +
== PCIe ==
 +
 
 +
On [[APF6_SP]] FPGA memory is accessed with PCIe, to read/write on FPGA space an example is available with [[Pci_debug | pci_debug]].
 +
 
 +
=== Pci_debug ===
 +
 
 +
See [[Pci_debug | pci_debug]] page to know how to access PCIe address space with a command line tool.
 +
 
 +
==Links==
 +
* http://sources.redhat.com/ml/crossgcc/2005-08/msg00120.html : Explanation of the problem.

Latest revision as of 09:59, 27 February 2017


Introduction

There are two methods to access FPGA registers on APF*.

  • For APF9328, APF27 and APF51 registers access are done with a memory bus (parallel).
  • For APF6_SP registers access are done with PCIe

Memory bus

fpgaregs

Installation

If the fpgaregs package is not already installed on your APF (/usr/bin/fpgaregs), you can select it in the Buildroot menuconfig:

 $ make menuconfig
Package Selection for the target  ---> 
    [*] Hardware handling / blockdevices and filesystem maintenance  ---> 
         [*]   fpgaregs 
 $ make 

Then reflash your rootfs or install it manually.

Usage

fpgaregs can be used to do read or write accesses (16 or 32 bits wide) to the FPGA, from Linux userspace/console.

16 bits read
# fpgaregs w <address>

Where <address> is an address relative to FPGA's mapping in hexadecimal value. Example:

# fpgaregs w 0
16 bits write
# fpgaregs w <address> <value>

Where <value> is hexadecimal value to write.

32 bits read
# fpgaregs l <address>
32 bits write
# fpgaregs l <address> <value>

the mmap problem

Note Note: This problem is corrected with latest Toolchains compilation options


First of all, you need to get a file descriptor for /dev/mem using the open() function

ffpga=open("/dev/mem",O_RDWR|O_SYNC);

Now you have a valid file descriptor to access your FPGA.

The O_SYNC option is recommended to avoid Linux to cache the content of /dev/mem and delay any modification done in this file.


To access fpga register, fpgaregs use the mmap() system call :

void * ptr_fpga;
ptr_fpga = mmap (0, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, ffpga, FPGA_ADDRESS);

Thanks to this function, fpga registers are accessible directly on memory with pointer ptr_fpga. To read and write in 16bits or in 32 bits we will cast the pointer value in unsigned short or unsigned int :

16bits write

        *(unsigned short*)(ptr_fpga+(address)) = (unsigned short)value;

read

          value = *(unsigned short*)(ptr_fpga+(address));

32 bits write

        *(unsigned int*)(ptr_fpga+(address)) = (unsigned short)value;

read

          value = *(unsigned int*)(ptr_fpga+(address));

The problem

By default, if the specific arm920t target is not specified, arm-linux-gcc will try to generate compatible read/write for all ARM9 model when it access register in 16bits. Indeed it seems that not all ARM9 have 16bits read/write capabilities (ldrh asm instruction).

As the interface between i.MXL and FPGA on APF9328 has no 8bits read/write capabilities, each 8 bits access is recognized by the FPGA as a 16bits access. So on each 16bits access of the i.MXL, FPGA will process two 16bits access instead of 1. That is a problem when accessing a FIFO for example.

To avoid this painful problem don't forget the -mcpu=arm920t option when compiling fpgaregs for APF9328 and -mcpu=arm926ej-s for APF27.

PCIe

On APF6_SP FPGA memory is accessed with PCIe, to read/write on FPGA space an example is available with pci_debug.

Pci_debug

See pci_debug page to know how to access PCIe address space with a command line tool.

Links