Parallella FPGA Tutorial 3: Interfacing peripherals to expansion cards


In this tutorial, I will cover the process of enabling an on-chip peripheral, connecting it to the EMIO interface, routing this through the FPGA, and connecting it to external pins. This requires reassigning pins which are currently assigned for GPIO. On the Linux side, we will update the device tree, and using a a breakout board (such as the Porcupine), do a loopback test to confirm the peripheral is both operational and connected to the outside world.

WARNING: Please do NOT attempt to interface this peripheral, or any GPIOs to other circuitry unless you understand the voltage levels at play here. In particular, unconfigured, the PMIC sets the FPGA GPIO bank to 2.5V during u-boot, and 2.9V thereafter. While you can use the I2C PMIC to change this to 3V3, there are consequences in terms of HDMI power consumption - and you still need to ensure you will not drive pins above 2.5V during boot anyway. I will be providing a further tutorial on safely interfacing pins - however I am still working through this / waiting for my hardware to arrive.

Step 1: Setting up the project, importing sources

A good starting point for this tutorial is Tutorial 0, which provides a good process for setting up a fresh project - in particular dealing with fetching a local copy of system.mhs, system.xmp, and version.v (but not all the intermediary cruft for system which unfortunately is checked into the parallella-hw repo).

Starting from Tutorial000_BaseProject, open this project then using Save Project As, create a new copy. Mine is called Tutorial003_PeripheralMapping.

Open the newly created project. Because we will be touching some further files shared between different projects, the first step is to import some specific files. The files to import are:

boards/parallella-I/constraints/parallella_z7020_loc.ucf
fpga/hdl/gpio/parallella_gpio_emio.v
fpga/hdl/parallella-I/parallella_z7_top.v
You should find these easily - they in the top and second level of the sources tree, and in the constraints tree.

Note that if doing this tutorial on the 7010 chip, you will be needing to modify parallella_z70x0_loc.ucf rather than parallella_z7020_loc.ucf. For the 7020, the pins we need to modify are in the latter file - while the 7010 does not have those pins (or use that file at all), so the pins to modify are in the former file.

Right click on each of the files to be imported in turn, and select Copy File Into Project.

Step 2: Configuring the Processing System

Launch the xps using the system file, as we did in the previous tutorial. We will need to enable a peripheral, map this to EMIO, and reduce the number of GPIO pins (since these will be used up by UART0).

[img] Click the I/O Peripherals box (green, top left).

[img] You will now see the configuration for all the configurable SoC peripherals. You will notice that the MIO bank is pretty much fully populated, but there are a number of non-enabled peripherals. And that is the subject matter of this tutorial - we can activate these peripherals and connect them via EMIO, through the FPGA fabric to pins otherwise used for GPIO - these can be brought out the GPIO_PEV inter-board connector to daughter boards.

[img] Now enable UART0 by clicking the checkbox next to the entry in the list, and assign the IO to EMIO. MIO represent physical pins, while EMIO (as you can see on the main diagram on the Zynq tab) connects to the FPGA region.

NOTE - in many cases, I gather you can also connect MIO pins to the FPGA fabric - so if you were to disable a peripheral, you could connect the pins it was using to an IPCore. For instance if you wanted to route UART1 (from the on-board header) directly to your IPCore rather than to the Arm which uses this to provide the serial console. I would not recommend messing with any of the other existing interfaces since the Zynq needs these to boot (QSPI and SD1), and to configure the PMIC (I2C) - so if you were to connect these to EMIO, nothing good will result. You might however route the ETH0 pins to EMIO and instantiate your own Industiral Ethernet IPCore as is being discussed here.

[img] Reduce the number of GPIOs by 4. We only will need 2, but due to issues in the current verilog, I found it necessary to work in bunches of 4.

NOTE - while we could have just reduced this by 2, I made the decision that since these GPIOs ultimately won't connect to physical pins, it is best to disable them all the way through the design - rather than simply not connecting them at the last point.

[img] Now close the I/O Configuration dialogue, and go to the Ports tab. If you expand the ports for UART_0, you will see that these are not yet connected.

[img] Make the UART_0 ports external by right clicking and selecting Make Ports External.

[img] Now expand the GPIO_0 ports. You will see that the 3 ports have width 48 ([0:47]). Reduce these to 44 ([0:43]).

Now it is time to return to planAhead and hook the new pins into our design, while removing some of the GPIO lines.

Step 3: Updating the Verilog files

[img] Edit system_stub.v to add the two UART0 pins to the ports definition of system_stub.

[img] Then add these to the module, one as an input, the other as an output. While there, remove some GPIO lines.

[img] Then connect the pins you just defined to the corresponding pins in the system_i black box. No need to adjust the GPIO lines - they are already connected, and we have adjusted the width at both ends.

[img] Now we need to wrestle with the current logic for automatically mapping all available GPIO to pins - since we have reduced the number of GPIO pins. Open parallella_gpio_emio.v and (as pictured) reduce the GPIO_NUM definition from 24 to 22. This frees up 2 pairs of pins - so 4 pins total (really only needed to be 2 pins, but the algorithm breaks on an uneven number of pairs - so must be a multiple of 4). I also added a SIG_MAX define for the step after the next.

[img] Reduce the width of the GPIO bus.

[img] Alter the algorithm for tying of unused GPIOs to use the SIG_MAX definition we added above. This macro effectively tells this bit of code not to treat these 4 GPIOs as unused PS pins (stops at 44 rather than 48).

[img] Now edit parallella_z7_top.v. First we need to update the NUM_GPIOs macro.

[img] Then we need to add two physical pins to the ports of the top level module (we will define these in the constraints file shortly).

[img] Now add these to the module - one as an input, one as an output.

[img] Then reduce the bus width for the GPIOs, as well as updating the default value.

[img] Finally connect the pins to the ports on the system_stub module.

[img] Now we need to update the constraints file parallella_z7020_loc.ucf to define the new pins. I removed the top 4 GPIO pins, leaving 44,45 as unused, and using 46,47 (referred to here as GPIO_P[23] and GPIO_N[23]) for UART0.

And now we are done. It is now time to build and load the design (and troubleshoot if you didn't get something quite right the first time, or I led you astray).

NOTE - If you have any issues, please either leave a comment here, or better yet ask on the forum here where hopefully others who have gone through the tutorial already may be able to assist you.

Step 4: Configuring the Linux kernel device tree

In order to be able to use the UART0 peripheral, we need to tell Linux it exists, where its registers reside in the memory map, which interrupts to use, and so forth. The Parallella Linux kernel does not include definitions for the peripherals it does not use. It could include the definitions and simply not enable them - but that is not the path they have chosen. I may suggest they change this to make enabling peripherals simpler.

You will need either access to the sources used to build the kernel running on your parallella, or simply reverse-compile the /boot/devicetree.dtb on the board you are using. I will demonstrate the former, since I prefer to build the kernel from source anyway - and this way it remains in my source tree when I make other modifications. If you want to do the latter, you will want to run a command such as

dtc -I dtb -O dts -o devicetree.dts devicetree.dtb

In your Linux sources, the file to edit is arch/arm/boot/dts/zynq-parallella1.dtsi. Note that these instructions are based on the current parallella-linux sources at the time of writing.

The changes I made are as follows:

  • Add an alias for the new uart0.
  • Add a definition for uart0 just before the one for uart1.
diff --git a/arch/arm/boot/dts/zynq-parallella1.dtsi b/arch/arm/boot/dts/zynq-parallella1.dtsi
index 14855bb..3ff1515 100644
--- a/arch/arm/boot/dts/zynq-parallella1.dtsi
+++ b/arch/arm/boot/dts/zynq-parallella1.dtsi
@@ -7,7 +7,8 @@

        aliases {
                ethernet0 = ð
-               serial0 = &uart;
+               serial0 = &uart1;
+               serial1 = &uart0;
        };

        amba@0 {
@@ -32,8 +33,21 @@
                        arm,data-latency = <3 2 2>;
                        arm,tag-latency = <2 2 2>;
                };
-               /* UART-->MIO 8:9 */
-               uart: uart@e0001000 {
+               /* UART0-->EMIO */
+               uart0: uart@e0000000 {
+                       compatible = "xlnx,xuartps";
+                       reg = <0xe0000000 0x1000>;
+                       interrupts = < 0 27 4>;
+                       interrupt-parent = <&gic>;
+                       clock-names = "ref_clk", "aper_clk";
+                       clocks = <&clkc 23>, <&clkc 40>;
+                       port-number = <1>;
+                       current-speed = <115200>;
+                       device_type = "serial";
+               };
+
+               /* UART1-->MIO 8:9 */
+               uart1: uart@e0001000 {
                        compatible = "xlnx,xuartps";
                        reg = <0xe0001000 0x1000>;
                        interrupts = < 0 50 4>;

You may have noticed that these are backwards (0 ->1, 1 -> 0). This is simply because the parallella dts has numbered the peripherals in terms of what is enabled, rather than the hardware number. This seems to be common practice, and makes things nicer for the Linux user, but can be a little confusing at first when you hack the dts.

Rebuild your devicetree blob. If using Linux sources, you should run something like

parallella-linux$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- zynq-parallella1-headless.dtb
Note that although the dtc compiler couldn't care less what architecture you are working with, when using the Linux make system, you must provide the usual ARCH and CROSS_COMPILE commands you used to build the kernel.

If you simply reverse compiled a dtb from the target, forward compile it using

dtc -I dts -O dtb -o devicetree.dtb devicetree.dts

Step 5: Trying it out

You will first have to replace the devicetree blob and reboot the Parallella. I also booted from my modified bitstream - I am not sure what happens if you boot a devicetree which refers to a peripheral which is not actually enabled in the accompanying bitstream. Certainly the inverse would be quite safe - but this way around I cannot be sure without testing it. So I recommend you do that also.

I did my testing using a jumper to loop the RT/TX pins - I recommend this as an initial test. Unless you have a breakout board such as the Porcupine, getting to the pins in order to test is going to be very difficult here.

[img] Ensure your terminal software doesn't have local echo on by default, or you might trick yourself into thinking it is working. I have shown picocom output with and without local echo enabled to illustrate the point. Obviously you can just try it with and without the loopback jumper in place.

Here is an image showing the location of the jumper, if you built for the z7020 version using the same options as I did in my tutorial. Sorry about the quality - header location circled in red.

Tutorial003-jumper


Back to Blog