MicroZed Software
The python script tdc_demo.py
can be downloaded in the repository.
With this file should be put the devmem.py
file .
This software is used to configure the ADC board but also to process the
delay between the pulses. The incoming pulses are first transformed into
damped sines. We then apply a fitting function on it to determine the
best suited mathematical model. Among the parameters of the mathematical
model is the phase. By determining the phase for all the incomming
pulses and by computing the difference between them, it is possible to
figure out what was the delay between them.
As a reminder, the board uses a Zynq device. On such SoC, we have both a FPGA (Programmable Logic or PL) and a dual core ARM (Processing System or PS). On this board are installed python3, the numpy and scipy libraries.
Gateware Setup
The first part of the script is used to set the gateware and reset the ADC. First, the ADC is configured by the SPI communication. Before taking the measurements, the ADC is reset by tuning it off and on. Multiples parameters can be set using via this interface such as the ADC output mode or the output phase. It is also possible to test if the gateware is working properly by asking to the ADC to generate a bit pattern.
The reset sequence is ordered like this :
- SERDES clock distribution
- SERDES itself
- ADC core
The ADC is controled by the GPIO0 pins 5, 6, 7 respectively the chip select, the clock and the data pins. The script is used to configure the ADC in DDR mode.
Two LEDs are also provided on the board. They are connected on GPIO0 pin
0 and 4. In the tdc_demo.py
they are used to tell the user if the
recording has been triggered.
The AXI bus can be accessed by the Linux running on the board by accessing slot 0x40000000 in the memory.
Data acquisition
The first step to acquire data is to configure the data buffer and the trigger. A pretrigger can be set for prototyping/debugging. In our case, it has been set to 10 samples. This allows us to see if the trigger worked correctly by plotting the waveform. If the trigger threshold level is not correctly set, it is possible to miss the first alternations of our signal, this may generate errors.
Once the triggers are set and armed, we are able to catch the data. One of the LEDs should be turned on to inform that the setup is done and the system is waiting for data to come. For the moment, the code is poling the triggers register in order to detect when data has been caught.
The other LED should light up when the pulse has been detected and the data transfer form the PL to the PS can be done. Once the PS has read the buffers, the fitting algorithm can be applied.
Fitting algorithm
The fitting algorithm is done on 110 pts. This number of points has been experimentally determined as optimal. The 15 firsts points of the frame are not used. On one side, this is because of the pre trigger. The 10 firsts points are just used for debugging. The 5 following points are removed to "clean the signal", more on that later.
The fitting is done by a scipy built-in function:
scipy.optimize.curve_fit(f, xdata, ydata)
assuming ydata = f(xdata, *params) + eps
. As we use a damped sine shape signal, the used
mathematical model
is
A*np.sin(2*np.pi*f/FS*xdata + 2*np.pi*f*phi/1e6)* np.exp(-tau*xdata) + offset
Our parameters are:
-
A
: the amplitude. -
f
: the signal frequency. This is estimated in comparison with the sampling frequency (FS). This constant has to be set at the beginning of the script. Any error on this frequency will generate errors on the measurements. -
phi
: the phase shift. This phase shift is here returned as a picosecond delay. -
tau
: the damping factor of our function. -
offset
: as our signal is not perfectly centered on zero, the offset parameters has to be computed.
The parameter phi
returns the phase shift in picosecond. If this
fitting function is applied to the signal coming on different channels,
we can deduce their independent phase shift. The difference between
these phase shifts can be computed to determine the delay between the
pulses.
Correction of the incoming wave form
The five first points are removed from the fitting algorithm. This is due to the fact that the signal shapes at the beginning is distorted (figure 1). This distortion is due to the CMOS gate slew rate (see filter-section for more explanations). The best way to avoid those distortion to add errors on the fitting algorithm is to remove them before applying the fitting.
Figure 1 - Distortion at the beginning of the pulse
Correction of the fitting algorithm
It is possible that the signal is interpreted the wrong way by the algorithm. For example, it is possible that the mathematical model that fits the signal has a negative amplitude. The phase delay is then determined with a multiple of the signal frequency of error. It is possible to correct it by analysing both the phase and the amplitude sign on a case by case basis. The actual solution does not work in all the cases and a deeper analysis is required to improve it.
Improvements
This works for delays smaller than the sampling period (10 ns). It is
possible to determine bigger delays but some steps have to be added to
the algorithm and maybe some gateware modification would be required.
For bigger delays, the trigger on the different channels has to be
independent and the time between them has to be counted. This can be
done by implementing a counter in the ADC core.
The mathematical model can be improved in order to increase the precision on the measurements. The Improvements wiki page indicates a direction for the futur researches.
Class descriptions
GPIO
This class is used to handle the GPIOs.
Functions:*
-
__init__(self, mem, addr)
: The builder. Offset address is 0x0000. -
direction(self, pin, is_out)
: Set the pin direction (0 for in and 1 for out). -
set(self, pin, value)
: Set or clear an output value on one pin. -
get(self, pin)
: Return boolean input pin value.
BitBangedSPI
Used to control the SPI communication trough the GPIOs.
Functions:*
-
__init__(self, gpio, cs_pin, sck_pin, data_pin)
: The builder. All the pins are defined here. -
cs(self, value)
: Set the value on the chip select pin. -
sck(self, value)
: Set the value on the clock pin. -
set_sdata(self, value)
: Used to send one data bit on the data pin. -
get_sdata(self)
: Used to read one data bit on the data pin. -
txrx(self, data, n_bits)
: Transmits or readsn_bits
. If data is set to zero, this function just reads what is on the data line.
Trigger
Controls the hardware trigger inside the FPGA.
Functions:*
-
__init__(self, mem, addr)
: The builder. The different addresses are : 0x1000 for channel 0, 0x3000 for channel 1, 0x5000 for channel 2 and 0x7000 for channel 3. -
configure(self, edge, threshold_lo, threshold_hi, mask)
: Set the trigger independently. Set the triggering edge (Rising edge if 0 and Falling edge if 1). The two threshold levels are set here. Finally, the mask is used to link the trigger to the other channels. If the trigger mask is set to 0xF, this means that any event on any channel will trigger the record on all the channels. Any other combination of trigger can be configured.
Chan3 | Chan2 | Chan1 | Chan0 | |
---|---|---|---|---|
Trig0 | 1 | 1 | 1 | 1 |
Trig1 | 0 | 1 | 1 | 0 |
Trig2 | 0 | 0 | 0 | 0 |
Trig3 | 1 | 0 | 0 | 0 |
The table 1 shows an example of configuration. Each corresponds to a trigger mask. On this example, with its mask set to 0xF, an event on trigger0 will trigger the recording on all the channels. Trigger1, with a 0x6 mask, will trigger the recording on channel 2 and 1. An event on channel 2 will not trigger any recording as the mask is 0x0. And finally, an event occurring on channel 3 will only trigger the recording on its channel.
-
force(self)
: Used to force the trigger. Mainly used for debugging. -
triggered(self)
: Return True if an eligible event occurred on this channel. -
arm(self)
: Arm the trigger
Buffer
This cyclical buffer is used to record the data. It is continuously recording the data. Once the pointer arrives at the end of the buffer, it starts recording since the very beginning. If an event is detected by a trigger, the pointer continues to write data but stops after looping avoiding erasing data arrived after the event. By stopping the pointer earlier, it is possible to save pretrigger samples.
Functions:*
-
__init__(self, mem, addr)
: The builder. The possible addresses are : 0x2000 for channel 0, 0x4000 for channel 1, 0x6000 for channel 2 and 0x8000 for channel 3. -
set_pretrigger(self, pretrigger)
: Number of samples kept before the event. -
start(self)
: Launch the continuous recording in the buffer. -
ready(self)
: Returns True when the buffer data is ready to be transferred to the Ps. -
read(self)
: Returns the formatted data out of the buffer. This needs thesign_extend(value, bits)
global function used to format raw data.
AD9253
This class is set for the ADC. It is basically represented by its SPI communication.
Functions:*
-
__init__(self, spi)
: The builder. It needs the SPI interface to be set. -
write_reg(self, reg, value)
: It is used to write in the ADC registers. -
read_reg(self, ref)
: Used to read a specific ADC register.
17th of January 2017 - Nicolas Boucquey