Getting Started¶
To begin working with the software API, make sure you have setup your environment for your hardware target:
- Hardware Setup : Install drivers, setup your user and/or system for the target hardware
- Software Setup : Install python and required dependencies, setup the environment
This page provides a step-by-step guide to go through the most important functionalities of the API required to be able to interact with a chip.
Identify your hardware¶
The firmware is build for both Gecco with Nexys Video FPGA board and Astep FPGA Board with a Digilent CMOD A35 fpga board.
If you are using a Gecco board, you have to ensure the FPGA is flashed with a firmware that matches your hardware configuration:
- Which Chip Carrier Board is connected: V2/V3/V4 , Telescope ... ?
- Which signal types the Configuration and clock signals to the carriers are expected to be: LVDS or Single ended CMOS.
Consult the Configuration Summary Page to check your hardware.
Prepare the terminal environment¶
The sw/ folder contains a set of modules providing the python API to work with the firmware (config, readout etc..). To allow flexibility, it is recommenced to setup the terminal environment to add the sw/ folder to the PYTHONPATH variable. This way any python import requiring a driver present in this repository will work out of the box.
On Linux:
1 2 |
|
On Windows CMD:
1 2 |
|
On Windows PowerShell:
1 2 |
|
Choose a work folder for your scripts¶
To work on python scripts, you can place your scripts directly in the sw/ folder, and install a virtual environment there. There is a basic requirements.txt file provided which should cover the basic needs.
However, if you have sourced the load script, your environment is setup properly and the PYTHONPATH variable includes the sw/ folder. You can then work from any other folder, just make sure to install the dependencies provided in the requirements.txt file in your virtual environment
Opening a Board Driver¶
Before writing/reading to/from the firmware, the user must create a BoardDriver class instance configured with the right I/O interface for the target hardware.
The BoardDriver class provides high level methods to drive the main functionalities of the firmware.
Possible configurations are:
- Gecco over FTDI FIFO (Fast USB)
- Gecco over Uart (max. 921600bps serial, recommened if the user needs to use a debugging ILA core while interacting with the firmware)
- Gecco over SPI (not supported yet)
- CMOD over Uart
- CMOD over SPI (flight configuration, not tested yet)
Most users doing chip measurement will be using the Gecco over FTDI FIFO interface:
1 2 3 4 5 |
|
Closing the connections to the fpga board doesn't have to be explicit, the low-level drivers are detecting the python program exit to properly close all driver handles.
Reading/Writing to the FPGA¶
To Read and write to the FPGA, we have to send read/write requests to the Firmware's Register File, which is a memory map used to set configurations and read back data, like performance counters or data frames from sensors.
The list of registers with their description and addresses is documented on this page
The python API is using a module generated by KIT's Register File generator tool, which provides a set of read and/or write methods for users to easily access the RegisterFile while hidding low-level details.
Let's look at how to read the firmware version, which is a 32 bits integer present at the beginning of the register map:
1 2 3 4 5 6 7 |
|
The BoardDriver is providing a helper method to read the firmware version:
1 2 3 4 5 |
|
-
self.rfg: The rfg class member is a class instance of main_rfg type, which is the generated class containing all the methods to access the registers. This rfg value is created and configured when creating the BoardDriver in the previous step.
-
self.rfg.read_hk_firmware_version(): This call prepares a read request to the firmware version register address and sends it straight to the firmware. This method creates the read request so that it will return 4 consecutive bytes from the register file (32 bit value), then return an integer constructed from the 4 bytes.
-
async and await: All the method calls that require a request to the hardware are marked async, so that they must be called using the await keyword. This is required because the underlying drivers are using the Python AsyncIO library.
AsyncIO requirement¶
All the low-level method calls must be run in an asyncio loop. You can read the AsyncIO page to learn more about asyncio.
In a nutshell, going back to our previous example:
1 2 3 |
|
A python script by default does not run in an asyncio loop, which means users must pass method calls sending requests to the hardware to asyncio:
- wrap any method marked async in asyncio.run(xxx)
- Create an async definition passed to asyncio.run(xxx), in which you can simply await any async method
Tip
Whenever you see the await keyword, if you are not running in an asyncIO loop, just wrap the code line in an asyncio.run() call
Firmware Startup / Reset State¶
Upon reset, the firmware will set the signals to the layer in the following configuration:
- No Timestamp and Sample Clock output enabled to Carriers/Layers
- Each Carrier/Layer reset and hold (resn signal set to 0, hold to 1)
- Layer Readout modules are not self-triggering readout if the sensor pulls the interrupt signal low
To begin working with sensors, the user must first release the layer from reset and hold (if desired), and enable the clock outputs.
The BoardDriver class provides the necessary utility for this:
1 2 3 4 5 |
|
The user can also start scripts with a reset cycle to ensure you are beginning with a fully reset chip:
1 2 |
|
Enabling / Setting up Clocks¶
Next, we will want to enable the Timestamp and Sample clocks, and configure the SPI Clock for the Layers
For the sample clock, check the hardware target for the exact configuration, but by default on Gecco, the sample clock is routed to the main differential input of Astropix (v2/v3)
1 2 |
|
To configure the SPI Clock, a utility method can calculate the required clock divider to specify a certain SPI clock freqency.
A value of 1 Mhz should be fine to get started:
1 2 |
|
Chip Configuration Setup¶
The default method to configure chip is first to read a configuration file containing for the matching astropix, then configure the asic.
The configuration file is the same YAML file type as the one used historically:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Warning
The number of chips in the row can be set in the yaml file, but will be overriden by the chipsPerRow parameter. This way only a basic config file is required for all cases
Configuring the Chip¶
To prepare a chip configuration, the API is the same as in the historical python.
To get the Asic class instance to configure, just call the getAsic method on the BoardDriver:
1 2 |
|
Note
There is one Asic class instance per row, each containing configuration for the whole daisy chain. If the user wants to configure all the chips in the same way, there should be a loop over the number of configured rows so that all Asic classes are configured.
Now you can proceed as usual:
1 2 3 4 5 6 7 8 |
|
Gecco Card Configuration¶
If you are using Cards on Gecco, you can get the instances from the BoardDriver class.
For example:
1 2 3 4 5 |
|
Sensor Readout Basics¶
By default, the firmware won't auto-read the sensor. The user must send some dummy bytes to the sensor so that some data will be read back if any available.
For example:
1 2 |
|
- Discard received IDLE bytes, and increase a counter by one for each
- Decode and encapsulate any data frame, increase a counter by one, then write them to the output buffer when complete
For example, a simple functionality check could be:
- Take a layer out of reset, keep hold set
- Send some dummy bytes
- The IDLE bytes counter should increase by twice the number of send bytes (because the sensor output is twice as fast as the input)
- The Frame counter will stay 0 as Sensors in Hold mode are not returning any data
1 2 3 4 5 6 7 8 9 10 11 12 |
|
One you are ready to read some actual data, make sure you are running a meaniful configuration: Noise Run, Injections etc...
Don't forget to remove the hold mode so that actual data will be send by the sensor:
1 |
|
The Readout Buffer¶
If you are expecting some data from the sensor, and you have either send NULL bytes to the firmware to trigger some SPI byte readouts, or the firmwar is configured to automatically readout data upon interrupt, you should be able to read some bytes from the readout buffer.
The Readout buffer is only filled with payload data, following the layer interface framing described on that page - Idle bytes are always discarded.
There are two methods in the BoardDriver class to read some data:
- boardDriver.readoutGetBufferSize(): Returns the number of bytes available in the buffer
- boardDriver.readoutReadBytes(count): Reads count bytes from the buffer.
1 2 |
|
When reading out from the buffer, there are two options:
- One can read the buffer size and read only the available number of bytes. This method is likely to be inefficient, creating a lot of small-sized reads. However it can be useful to read the buffer size at the end of a script to finalize reading all the data for example.
- One can read larger number of bytes, however this method is likely to return a lot of empty bytes "0xFF" since reading may be faster than sensor data fetching. You should be careful to process the bytes stream to discard empty bytes, but not discard 0xFF bytes which are not empty bytes.
1 2 3 4 5 |
|