Getting started with syfala and Faust
Introduction
This tutorial is intended for beginner-users with no background in FPGA or C++ development. Only basic knowledge of Linux and using the terminal is necessary. Prior experience using the Faust language is also preferable, but not required.
Minimum hardware/software requirements
-
Linux system (preferably Ubuntu 18.04 or higher, Debian-based or Archlinux-based distributions in general).
-
16GB of RAM (32 is recommended for bigger DSP programs).
-
100 GB of free disk space (for AMD-Xilinx tools installation)
One of the following development boards:
-
Digilent Zybo Z7-10 - Zynq-7000 ARM/FPGA SoC Development Board
-
Digilent Zybo Z7-20 - Zynq-7000 ARM/FPGA SoC Development Board
-
Digilent Genesys ZU-3EG - Zynq UltraScale+ MPSoC Development Board
Objectives
In this tutorial, we will cover the essential topics to get started with the Faust programming language, syfala, and audio programming on FPGAs in general. We will start by briefly presenting all of these programs and environments, and then we will get our hands on a very concrete audio program example that we're going to build, flash on our FPGA development board, and control remotely, using different protocols, such as MIDI, OSC and HTTP.
Faust
Faust (Functional Audio Stream) is a functional programming language for sound synthesis and audio processing with a strong focus on the design of synthesizers, musical instruments, audio effects, etc. created at the GRAME-CNCM Research Department.
In order to get you started using Faust, we recommend that you follow this tutorial : https://faustdoc.grame.fr/manual/quick-start
Syfala
About syfala (and FPGAs)
Syfala is a set of command-line tools & scripts aiming to facilitate audio programming on FPGAs. It currently targets AMD-Xilinx devices, such as the Digilent Zybo Z7-10 & Z7-20, or the Genesys ZU-3EG. It takes a Faust .dsp
file or a C++ .cpp
file as input, and then configures and calls all the AMD-Xilinx softwares that will compile all the required binaries to be flashed on device.
The repository itself is composed of a few different elements:
- Command-line interface scripts (
syfala.tcl
). - Faust architecture files, for interfacing with High Level Synthesis (HLS).
- Tcp scripts, used to configure and call the AMD-Xilinx softwares, such as Vitis HLS, Vivado, Vitis...
- Baremetal/Linux C++ code, for the ARM control executable.
- VHDL code: custom i²s implementations.
- Other types of scripts (for preprocessing, Makefile, Embedded Linux...)
Installing the toolchain
Dependencies
Please follow the instructions in the file dependencies.md in order to install the AMD-Xilinx toolchain and various other dependencies.
Installing Syfala
git clone https://github.com/inria-emeraude/syfala.git syfala
cd syfala
make install
You'll also have to add the following environment variable to your shell resource file (~/.bashrc / ~/.zshrc)
export XILINX_ROOT_DIR=/my/path/to/Xilinx/root/directory
where XILINX_ROOT_DIR
is the root directory where all of the AMD-Xilinx tools (Vivado, Vitis, Vitis_HLS) are installed.
Your first example
Building
syfala <faust-file.dsp> [options]
# Depending on the development board that you have, the command will be:
syfala examples/faust/virtualAnalog.dsp --board Z10
--board Z20
--board GENESYS
This will run the full syfala toolchain on the virtualAnalog.dsp
Faust file, which will then be ready to be flashed on your board. Under the hood, syfala will follow multiple steps, which you'll be able to monitor from the console log:
1 - Sources
It will call Faust to generate the C++ code from the virtualAnalog.dsp
file, with a custom-made syfala architecture file. You'll be able to see the resulting code in build/syfala_ip/syfala_ip_preprocessed.cpp
.
2 - HLS
Once the C++ code is generated, it will call Vitis HLS, which will translate it into a self-contained Hardware Description Language (HDL) program, which we're going in this tutorial to call the DSP kernel. The resulting code of this kernel can be viewed in the build/syfala_ip/syfala/impl/vhdl/syfala.vhd
VHDL file.
3 - Project
Here, the DSP kernel generated by Vitis HLS is going to be imported into a more global Vivado design, which will include the processing system (PS), our custom-made Integrated Interchip Sound (i²s) transceiver, and various other modules as well. The resulting code can be viewed in build/syfala_project/syfala_project.gen/sources_1/bd/main/hdl/main_wrapper.vhd
4 - Synthesis & implementation
From here, Vivado will 'compile' the project's final design into what we call a bitstream, which is a hardware configuration file: it includes the description of the hardware logic, routing, and initial values for both registers and on-chip memory (e.g. LUT).
5 - ARM control executable
Finally, we will also need to cross-compile an ARM elf executable to take care of all initialization and control-rate computations, which are done on the CPU, and exchanged with the DSP kernel using a specific set of buses.
Reading performance reports
If the build went well, you should be able to see on your terminal a recap of the build's configuration and an overview of its performance reports. Since the HLS report is only an estimate, the Vivado summary is the one that you should really pay attention to.
virtualAnalog Z20
- DSP: 15% (35)
- Reg: 9% (9730)
- LUT: 16% (8820)
- BRAM: 0% (0)
Tot. 344 Cycles, 2.799us
Max. 2604 Cycles, 20,8333us
Lat. 13%
Resources
Here we can see that, on our Zybo Z7-20 development board, the final design doesn't take a lot of FPGA resources, so there's potentially room to add more cool stuff to our Faust program, if the latency is sufficiently low as well.
Latency
The report shows that the DSP kernel's computations are done in 2.799 microseconds (344 FPGA clock cycles at 122 MHz): that is way below the maximum allowed for the computation of a single sample, which is defined by the reciprocal of the sample-rate (1/48000
in our case, which equals to 20,8333
microseconds).
Exporting and re-importing your builds
Before flashing, or doing anything else, it's generally a good idea to save and export your build outputs, especially when compilation times are really long, like ours. You can do this with the following syfala command:
syfala export my-faust-virtual-analog-build
# output in 'export/my-faust-virtual-analog-build.zip'
The resulting .zip
file is then going to be available in the repository's export
directory. Later, you'll be able to re-import it by doing:
syfala import export/my-faust-virtual-analog-build.zip
Flashing
Now we have our program fully compiled and exported, we can try it on our development Board. In order to do so, two options:
- Flash it from USB (with JTAG), by connecting an USB cable to the board's UART port.
- Flash it from a bootable SD card.
Hardware configuration
In both cases, you'll have first to make sure that the board is set up with this configuration:
-
Power select jumper (JP6) should be on USB
-
Board should be connected (UART PROG port) to your machine with a micro-USB to USB cable.
-
Switches SW0, SW1, SW2, SW3 should be down
-
The audio input is LINE IN (blue), not ~~MIC IN~~.
-
The audio output is the black HPH OUT jack.
USB (JTAG)
-
Hardware configuration: jumper JP5 should be on JTAG.
-
Board should be powered up with SW4.
You can then quickly flash your program by entering the following syfala command from your terminal:
syfala flash
Keep in mind that when you shutdown your device and power it up again, it won't start your program again automatically (because it is not kept in memory), so you'll have to enter the syfala flash
command again. This is why working with SD cards is going to be a better solution if you have the need to reload your program every time your power up the board.
SD card
-
Jumper JP5 should be on SD.
-
SD card should have a FAT32 bootable partition.
To generate the bootable binary file boot.bin
, you can add the --boot
flag to your syfala command :
syfala examples/faust/virtualAnalog.dsp --board Z20 --boot
It won't re-compile the whole project, it will just take the hardware and software outputs of your build and package them into a single bootable binary file (boot.bin
), which you'll find in the build/sw_export
directory. You can then just copy this file into your SD card, and insert it in the board's SD card socket, and then power the board up with SW4.
Monitoring and controlling the board remotely
Once your build is flashed on your board, you'll first want to make sure that the program has been loaded and executed properly. You can obviously plug in a headset or a pair of speakers to check its audio output, but there are also a couple of ways of getting debug information, which you'll need in case the program is not working the way it is supposed to:
LEDs
Right after flashing your build, you can directly look at the board's LD12 LED, which is supposed to turn green when a bitstream has been properly loaded. Then, you can look at the LD5 RGB LED, which will go from blue to green during the ARM control program execution. Audio will usually start right after this LED turns green.
If an error occurs, and LD12 is still off, or LD5 turns red, the next step would be to check the board's console output, which is accessible through UART.
UART console
In order to view the board's console output, you will need to install a serial device i/o tool, such as minicom
or tio
. For this tutorial, we will use tio
. Once the board is connected with an USB cable (PROG UART port) to your laptop or desktop computer, you can enter the following command:
tio -b 115200 /dev/ttyUSB1
This will tell tio
to connect to the ttyUSB1
serial device interface with a baud rate of 115200
. Once it is done and the board has been powered-up again, you should see debug information being displayed on the console.
Remote control of Faust parameters
USB-UART control
All of the user-interface elements declared in the Faust DSP code are controllable. In baremetal mode, the preferred way of doing this remotely from your machine would be using the available GUI to Serial interface, that you can start with the following command, once the DSP program is running on your board:
syfala start-gui
Syfala should now be building the user-interface after having retrieved the .dsp
file's control parameters, and display it with a GTK interface when it's ready. You should now be able to play with the different buttons, sliders and knobs that have been defined in your Faust file.
Now, GTK-based sliders and buttons are perfectly fine for testing your program, but you'll probably want at some point to have a nicer way to control its parameters, by using for instance MIDI or OSC interfaces. For now, in baremetal mode, it is not possible (yet) to plug a MIDI controller directly on the board's USB port and control your Faust parameters, because MIDI-USB baremetal drivers are unfortunately not yet implemented in syfala. The same goes for Ethernet-OSC. To remedy this problem, we offer at the moment two solutions :
- The Embedded Linux for syfala (tutorials/embedded-linux-getting-started.md)
- On Linux, MIDI, OSC & HTTP libraries are available, and ready-to-use with syfala.
- MIDI, OSC, HTTP layers on top of the Serial interface.
- MIDI, OSC & HTTP interfaces will be used on your machine, and translated/channeled into the Serial interface to be transmitted to the board. This is not ideal and will introduce some more control latency to the setup, but is easy to use.
MIDI control
In order to build a target with MIDI-Serial support, you can add the --midi
flag to the command line, and reload the start-gui
command:
syfala examples/faust/virtualAnalogMIDI.dsp --board Z20 --midi
syfala start-gui
You'll also have to explicitly map in your Faust code the controls (sliders, knobs, etc.) to a MIDI control element (note, cc, pressure, etc.). An example of MIDI-mapping can be viewed in examples/faust/virtualAnalogMIDI.dsp
, and the Faust MIDI-mapping process is fully explained here : https://faustdoc.grame.fr/manual/midi
OSC control
In order to build a target with OSC support, you can add the --osc
flag to the command line:
syfala examples/faust/virtualAnalog.dsp --board Z20 --osc
syfala start-gui
The GUI control application will create an Open Sound Control-compliant UDP server. You'll be then able to control remotely the Faust DSP parameters by sending OSC messages like so:
-
/virtualAnalog/lfoRange 2000
-
/virtualAnalog/oscFreq 500
-
etc.
More on: https://faustdoc.grame.fr/manual/osc
HTTP control
In order to build a target with HTTP support, you can add the --http
flag to the command line:
syfala examples/faust/virtualAnalog.dsp --board Z20 --http
syfala start-gui
The GUI control application will create a HTTP server allowing users to control the Faust DSP parameters remotely (given that you are on the same network as your FPGA board).
At runtime, when executed, the application will print the device's current network IP (IPv4), and the port used by the HTTP server. You can then use any web browser, and control the application by entering the server's URL, for example http://192.168.0.1:5510
SPI-based controllers
[...]
Going further
Embedded Linux
Note: only available for Zybo Z7-10/Z7-20 boards (Genesys not yet supported) .
Starting from syfala 0.8, you can choose to build our custom-made embedded Linux, which will provide, in addition to all the things an OS has to offer (SSH control over Ethernet or Wi-Fi, package manager, etc.), the following features:
- No latency cost (the DSP kernel still dwells in the Programmable Logic)
- On-the-fly FPGA reprogramming from Linux (with no need to reboot or re-login).
- On-board USB-MIDI control (from the board's USB OTG port).
- Direct OSC/HTTP control, pre-mapped to the Faust DSP program's user-defined GUI elements (sliders, buttons, checkboxes, etc.).
From the syfala command line interface, all you need to do is add the --linux
flag:
syfala examples/virtualAnalog.dsp --linux
For the rest of the procedure, you can follow the linux getting-started tutorial
Using syfala with C++
If you'd rather write your DSP code directly in C++, or you have a more imperative need for performance and optimization, you can follow our C++ tutorial here (advanced users).