Simulink Design Overview
If you’re reading this, then you’ve already managed to find all the tutorial files. By now, I presume you can open the model file and have a vague idea of what’s happening.
The best way to understand fully is to follow the arrows, go through what each block is doing and make sure you know why each step is done. To help you through, there’s some “blockumentation” in the appendix, which should (hopefully) answer all questions you may have. A brief rundown before you get down and dirty:
- The all important Xilinx token is placed to allow System Generator to be called to compile the design.
- In the MSSGE block, the hardware type is set to “RED_PITAYA_10:xc7z010” and clock rate is specified as 125MHz.
- In this tutorial, we use the 10-bit Red Pitaya. Make sure that the platform and ADC yellow blocks are configured for 10 bits.
- The input signal is digitised by the ADC. The ADC runs at 125MHz, which gives a 62.5MHz nyquist sampled spectrum. The RF inputs have a slightly narrow bandpass of 50MHz. The output range is a signed number in the range 0 to 1023 (ie 0 bits after the decimal point). This is expressed as fix_10_0. This is a little non-standard. You’ll notice that tutorials for other platforms have an ADC output between -1 and +1 and a binary point to match.
- The Xilinx FFT is configured for 256 channels.
- You may notice delay blocks dotted in places of the design. It’s common practice to add these into the design as it makes it easier to fit the design into the logic of the FPGA. It consumes more resources, but eases signal timing-induced placement restrictions.
- The real and imaginary (sine and cosine value) components of the FFT are plugged into power blocks, to convert from complex values to real power values by squaring.
- The requantized signals then enter the vector accumulators, which are simple_bram_vacc 32 bit vector accumulators. Accumulation length is controlled by the acc_cntrl block.
- The accumulated signal for each channel is then fed into a 32-bit snap block, accum0_snap and accum1_snap.
Without further ado, open up the model file and start clicking on things, referring the blockumentation as you go.
The first step to creating a frequency spectrum is to digitize the signal. This is done with an ADC – an Analogue to Digital Converter. In Simulink, the ADC is represented by a yellow block.
The ADC block converts analog inputs to digital outputs. Every clock cycle, the inputs are sampled and digitized to 10 bit binary point numbers in the range of -1024 to 1023 and are then output by the ADC. This is achieved through the use of two’s-complement representation with the binary point set to 0. Simulink represents such numbers with a fix_10_0 moniker.
ADCs often internally bias themselves to halfway between 0 and -1. This means that you’d typically see the output of an ADC toggling between zero and -1 when there’s no input. It also means that unless otherwise calibrated, an ADC will have a negative DC offset.
In this design the ADC is clocked to 125MHz, generated from the Red Pitaya’s system clock. This gives us a bandwidth of 62.5MHz, as Nyquist sampling requires two samples (or more) each second.
||Reset port for ADC. This signal will be activated by our script upon startup. This is a boolean signal.
The ADC outputs are a data valid flag and two signals: i and q, which correspond to the coaxial inputs of the Red Pitaya (inputs 0 and 1).
The power block computes the power of a complex number. Underneath the subsystem blocks, you see that the power block will compute the power of its input by taking the sum of the squares of its real and imaginary components.
The output of the block is 31.0 bits.
||2*BitWidth Fixed point
||A complex number whose higher BitWidth bits are its real part and lower BitWidth bits are its imaginary part.
||The computed power of the input complex number.
The simple_bram_vacc block is used in this design for vector accumulation. If you wanted a really long accumulation (say a few hours), you’d have to use a block such as the qdr_vacc or dram_vacc. As the name suggests, the simple_bram_vacc is simpler so it is fine for this demo spectrometer.
The FFT block outputs 256 frequency bins in total. We have two of these bram vacc’s in the design, one for each port on the Red Pitaya. The vector length is set to 256 on both.
||The length of the input/output vector. The FFT block produces a stream of 256 length, so we set this to 256.
|no. output bits
||The input is 31.0 from the FFT block, we have set this to 31 bits. Note: As there is bit growth due to accumulation, we really ought to set this higher than the input bits to avoid overflows. Other CASPER spectrometer tutorials handle this by requantizing the data stream between the FFT and the accumulator.
|Binary point (output)
||Since we are accumulating 31.0 values there should be 0 bits below the binary point of the output, so set this to 0.
||A boolean pulse should be sent to this port to signal a new accumulation. We can't directly use the sync pulse, otherwise this would reset after each spectrum. So, the sync is first connected to the acc_cntrl block, a block which allows us to set the accumulation period.
||Data input and output. The output depends on the no. output bits parameter. The input/output is ufix_31_0 in this case.
||The output of this block will only be valid when it has finished accumulating (signalled by a boolean pulse sent to new_acc). This will output a boolean 1 while the vector is being output, and 0 otherwise.
The final blocks, accum0_snap and accum1_snap, capture the data coming from the accumulators, which we will read out the values of using the tut_spec.py script.
||These are the names of the variables captured -- in this case, power and data valid.
||Bitwidths corresponding to the named variables. Presently, the snap blocks for the Red Pitaya support a maximum bitwidth of 32.
||The data from the accumulators is ufix_31_0, so set this to zero.
||We have one unsigned fixed point and one boolean data type.
|in_P_acc0 / in_P_acc1
||Input port for power. This is ufix_31_0.
|in_val_acc0 / in_val_acc1
||Data valid signal, which is a boolean. The last accumulation is the valid spectrum and is flagged by this port.
||We tie the write enable port high so we can see if there are any invalids in the in_val_acc0 and in_val_acc1 port. This is a boolean signal.
||Trigger for the snap block. This is a boolean signal. Note, the block diagram is configured so that the trigger happens following a reset, but we can override this trigger in the casperfpga controls of tut_spec.py by setting man_trig=True in the snapblock call.
There are a few control registers, led blinkers, and snap block dotted around the design too:
- reg_cntrl: Counter reset control. Pulse this high to reset the ADC and ADC counter.
- acc_len: Sets the accumulation length. Have a look in tut_spec.py for usage.
- sync_reg: Synchronizes the FFTs. Pulse this high to start/restart the FFT output.
- sync_cnt: Logs the number of syncs into the FFTs.
- fft_sync_inc: Logs the number of syncs leaving each FFT.
- acc_cnt: Accumulation counter. Keeps track of how many accumulations have been done.
- gpio_led: LED flashes while the bitcode is running.
There are a few additional software registers for debug purposes only.
If you’ve made it to here, congratulations, go and get yourself a cup of tea and a biscuit, then come back for part two, which explains the second part of the tutorial – actually getting the spectrometer running, and having a look at some spectra.