32MHz spectrum + SDR + FT8 in an FPGA

A 0 - 32MHz FPGA based Software Defined Radio (AM SSB FT8) by ready modules->cheap and easy
Last add: Oct 6th FT8 VHDL GFSK modulator

Public Chat
Similar projects worth following
Get ready for some electronics wizardry on a budget! 🧙 I'm aiming to construct an incredible 0-32 MHz, 12-bit FPGA-based SDR receiver and FT8 transceiver for less than 100 Euros. 🚀

This project is packed with awesomeness:

Wide Spectrum Wonder: We're covering the entire frequency range from 0 to 32 MHz. It's like having the entire radio universe at your fingertips!

Zoom In, Zoom Out: Need to get up close and personal with signals? You can zoom in from 16 MHz down to 2 KHz, giving you unprecedented control over your reception.

Multi-Mode Magic: We've got it all - AM, Lower Side Band (LSB), and Upper Side Band (USB) reception. Plus, there's audio output and you can narrow the bandwidth down to just 2 KHz for pristine the weakest signals.

FT8 Sorcery: Don't forget, this beauty also boasts an FT8 decoder and encoder.

Well, here I'm joking a little, but the project is very serious!


At the beginning the antenna is directly connected to the AD9226 Analog to digital converter, sampling at 64MHz. The 64MHz clock is generated by the FPGA, so that it can sample it well (more on this later)

Please note there's no external PC because the Zynq 7010 embeds a Dual Core Cortex A9 running Linux (Xilinx PetaLinux) in addition to its powerful FPGA.

  • AM / SSB demodulated audio can be listened by headphone or loudspeaker
  • Wideband or zoomed radio spectrum can be viewed on the screen
  • demodulated FT8 can be viewed on screen and decoded using a WSJT-X adapted version
  • modulated FT8 is sent to the Antenna

Similar projects

KiwiSDR is a very good example of a wideband 0-30 MHz FPGA based SDR receiver but it costs 450 Euro and it needs a BeagleBone computer. 

Red Pitaya is a wonderful project with a lot of possibilities and it is open source but the board costs around 600 Euro.

Panoradio ( is much more than I'm trying to build (100 MHz spectrum, 16 bits samples) but its components (FPGA board, A/D board) are much more expensive as well. 

Block Diagram

At this time (Aug 2023) the whole design is pretty consolidated.


  • low pass 32 MHz
  • RF Switch
  • RF linear power amplifier


All the number crunching in the fabric logic is perfomed with two's complement. 

When I write xx(yy), e.g. 32(28), I mean that the numbers are 32 bit two's complement but the maximum expected value is 

                                                  - 2^27 <= value <= 2^27-1

ADC - Analog to Digital Converter - AD9226

The board has no clock reference (see the AD9226 board schematic) and therefore the 64MHz clock must be supplied by the FPGA. In this way:

  • the AD9226 outputs data on the dropping edge of the clock. 
  • the FPGA samples on the clock rising edge

See the picture below and for further details see the AD9226 datasheet from Analog Devices.

A concern is about the clock jitter coming from the FPGA. Will it add noise to the received signal?

For example, in this project ADC to DMA to Ethernet with a ZYNQ 7000, the sampling clock is generated outside the FPGA 


The functions operating in the FPGA are the following.

  • It supplies a 64MHz clock to the AD9226 and receive 12 bit data + 1 bit "Over The Range" from it.
  • An internal signal generator 0.1 - 30 MHz AM modulated at 1KHz can be be used for testing purposes.
  • One "capture block" will capture 16384 samples (at 64MHz) at a time. These samples are sent to the FPGA PS and from there to the sdr-app (a QT5 C++ app running on the Zynq PS) to show the full 32 MHz radio spectrum and waterfall.
  • One DDC (Digital Down Converters) with selectable bandwidth (10 MHz, 3 MHz, 1 MHz, 300KHz, 100 KHz, 30 KHz, 10 KHz,)  will zoom the radio spectrum and waterfall with increased resolution.
  • Another DDC can be tuned on a specific frequency to demodulate and decode e.g. the FT8 frequencies on 80 40 20 15 and 10 meters, with selectable (100KHz, 30KHz, 15KHz, 8KHz, 4KHz and 2KHz)
  • It generates the  TMDS signals to drive a DVI interface (HDMI without audio and control)
  • It generates I2S signals to drive an external I2S DAC to listen to AM broadcasts and SSB radio hams
  • It interfaces a PS2 mouse

Getting started with the EBAZ4205 board

The whole project is quite challenging, especially for those people who have never worked with Xilinx Zynq development environment (Vivado, Vitis, Petalinux). Therefore I strongly suggest to start with a "getting started" project.  See my Hackaday project: EBAZ4205 development environment

The Analog Input

I didn't like that AD9226 12-BIT 65MSPS  board attenuates the input signal by -8,4 dB. So I decided to modify it to get some amplification, using far more 12 bits ADC range. 

I designed a circuit like this: ...

Read more »

AXI PS2.pdf

AXI PS/2 1.0 IP Core User Guide from Digilent

Adobe Portable Document Format - 590.56 kB - 01/16/2023 at 21:24


ebaz4205 schematic.pdf

The electrical schematic of the EBAZ4205. Very useful to choose I/O pins.

Adobe Portable Document Format - 223.98 kB - 07/18/2022 at 13:23


  • AD9851 IP - second attempt.

    Guido09/11/2023 at 08:53 0 comments

    A FIFO component receives via AXI4-Lite the 79 symbols (each symbol is 3 bits each) composing the FT8 message. In this way the Linux software (MYJTDX) and the Cortex A9 CPU have plenty of time to send these 21 x 32 bits every 15 seconds. Besides, the carrier frequency (e.g. 7075500 Hz) must be sent to the AD9851.

    Therefore the message from PS to PL could be composed by the following 11  x 32 bits words.

                     MSB                                                                                                      LSB             

    • R0    :  Phase shift for the carrier frequency (when symbol value=0): 32 bits
    • R1     :  Symbol   8 (4 bits)  Symbol 7 (4 bits)       ...                       Symbol  1 (4 bits)            
    • ...
    • R10  :  Symbol 73 (4 bits)   .....    Symbol 79 (4 bits)                     Symbol 73(4 bits)   
    • R11   :   0 ...                                                               ... 0     PWM max value (8 bits) TODO

    NOTE Please note that an FT8 symbol (0...7) could be encoded with 3 bits but for AXI4-Lite convenience (8 bits alignment) I decided to encode a symbol in 4 bits leaving the MSB at 0.

    TODO: PWM Maximum Value

    Lately I added the R11 register where I program the maximum DC value that the PWM can reach.

    E.g. If I set PWM max value to 153, PWM can go from 0 Volts (duty cycle 0%) to 3.3 x 153 / 255 = 1,98 Volts.

    This parameter, programmable by myjtdx (PS side) allows to be sure that when the PWM DC level is:

    •  at its maximum value, the AD9851 outputs nothing
    • near the maximum value, the AD9851 outputs a minimal signal.

    As I already built the AD9851 PWM and AD9851 serializer, it's now time to design the GFSK IP.

  • Vivado AD9851 first attempt

    Guido09/11/2023 at 08:31 0 comments

    This solution would work (with a faster CPU) but the Zynq7010 Cortex A9 CPU isn't able to send a new word (32 bits phase shift) every 1 or 2 milliseconds with enough timing precision. I'm now  trrying to implement an all PL solution. See next log. 

    The AD9851_0 VHDL IP is controlled by a GPIO IP (axi_gpio_AD9851) where the two 32 bits output ports act as:

    • Port 0: 32 bits DDS phase increment to the AD9851 according to the formula:

          Out frequency = phase_increment * 180 MHz / 2^32  

    • Port 1:
      • bits 31 ... 17 : not used
      • bit 16          : valid_in signal to the AD9851_0 IP
      • bits 15 ... 8 : pwm duty cycle (0 - 255) to AM modulate the output
      • bits 7 ... 3  : output phase (always 0)
      • bits 2        :  power down (0=power out     1=power down)
      • bit 1           : always 0 
      • bit 0          : AD9851 6x (ask the AD9851 to multiply by 6 the 30MHz reference to obtain a 180 MHz clock reference)

  • FT8 message software implementation

    Guido09/02/2023 at 11:09 0 comments

    JTDX and WSJT-X prebuild the FT8 message audio samples (at 48KHz) and send them altogether to the PC audio card whose output modulates the SSB (USB) transmitter. In this case, the task to precisely send out the 81 FT8 symbols is in charge of the audio card.

    Of course I cannot follow this method because, as I explained in the log FT8 QSO's, I chose to use an external sinusoidal DDS synthesizer that I need to modulate in amplitude (for the 1st an 81st symbols only) and frequency for the whole 81 symbols. Therefore I need to change amplitude (8 bits encoded) and frequency (32 bits encoded) at a proper speed.

    For the amplitude

    The first symbol has a 20 milliseconds ramp-up (shaped as a raised cosine) and a remaining 140 milliseconds at constant amplitude

    The last (81st symbol) has a 140 milliseconds duration at constant ampltude and a remaining 20 milliseconds ramp-down (shaped as a raised cosine)  

    I suppose that I can approximate the raised cosine ramp-up o ramp-down with 20 samples (1 sample every millisecond). E.g. 0 0.01 0.02 0.03 0.05 0.07 0.10 0.15 0.20 0.40 0.60 0.80 0.85 0.90 0.93 0.95 0.97 0.98 0.99 1

    For the Gaussian Frequency Shift Keying

    The frequency change is every 160 milliseconds (1 symbol duration) but it is smoothed with a Gaussian function, to reduce band occupation. MYJTDX produces the frequency shift samples at 48KHz so that, for every 160 milliseconds symbol we have 7680 samples. The maximum shift (from value 0 to value 7) is 7 * 6,25 Hz=43,75 Hz and can happen in about 32 milliseconds (0.2 * 160 milliseconds)

  • Creating a Vivado IP to program the AD9851

    Guido08/16/2023 at 20:14 0 comments

    According to the previous evaluations (see previous logs), I should build something like this:

    • PL 8 bits PWM to set amplitude (ramp up of 1st symbol and ramp down of 81st symbol)
    • PL 40 bits serializer to set frequency

    are two Vivado IPs receiving the 8 + 48 bits from a GPIO standard Vivado IP

  • How to use the frequency shift array pre calculated by JTDX

    Guido08/16/2023 at 19:52 0 comments

    The C code of JTDX is written to create the 81 samples (sampled at 48 KHz - S16LE) of the 8FSK message lasting 12,96 seconds. However, one of its intermediate products is the phase shift (dphi) of the 8FSK modulated signal,  sampled at 48KHz. 

    For each FT8 transmission, dphi contains 81*48000*0,16=622.080 samples of the transmitted frequency, for a total last of 12,96 seconds.

  • FT8 protocol

    Guido08/11/2023 at 18:04 0 comments

    To build an FT8 modulator I need to resume the FT8 message format. All the details come from FT4_FT8

    The 174 bits message

    A single 174 bits TX message is composed by:

    • 77 bits the message content
    • 14 bits Cyclic Redundancy Code 
    • 83 bits Forward Error Correction
    77 bits14 bits83 bits

    The 81 symbols

    • The 174 bits are grouped 3 by 3 obtaining 58 symbols
    • Each symbol lasts 0,16 seconds
    • The 58 symbols are interposed (beginning, middle and end) by 7 grey coded symbols to ease synchronization
    • a single initial symbol (3) increases the amplitude with a ramp up to max amplitude
    • a single final symbol (2) decreases the amplitude with a ramp down to 0
    ramp up
    1 sym
    grey code
    7 syms    
    First half
    29 syms
    grey code 
    7 middle syms   
    Second half 29 symsgrey code 
    queue 7 symbols
    ramp down
    1 sym
    33 1 4 0 6 5 2S0....S283 1 4 0 6 5 2S29.....S573 1 4 0 6 5 22

    The total message lasts 160 milliseconds x 81 symbols = 12,96 seconds

    8FSK modulation

    Each symbol, which lasts 160 milliseconds, generates 1 of 8 frequencies shifted by 6,25 Hz i.e.

    • sym: frequency shift 
    • 0    : 0 Hz
    • 1     : 6,25 Hz
    • 2    : 12,50 Hz
    • 3    : 18,75 Hz
    • 4    : 25 Hz
    • 5    : 31,25 Hz
    • 6    : 37,50 Hz
    • 7    : 43,75 Hz 

    The frequency doesn't shift immediately between a symbol and the next, but it is smoothed with a Gaussian function (see Figure 2 in the article FT4_FT8).

  • FT8 QSO's

    Guido07/21/2023 at 07:52 0 comments


    Now that the whole project works pretty well as a receiver, including FT8 decoding, it's time to transmit anything.

    To understand the FT8 protocol, see FT4_FT8

    MYJTDX software (forked from JTDX forked from WSJT-X) decodes well and it is ready to transmit and manage QSO's. Its way to transmit is to send an 8FSK modulated tone at 1500Hz out to the audio out card (to the PL of Zynq7000). This tone would SSB modulate an HF transmitter.

    E.g. 8FSK 1500Hz modulates in USB a 7074000 Hz transmitter, obtaining a 7075500 Hz 8FSK modulated carrier.

    Now I see two options

    1) Follow the JTDX way - external DAC, sending the 1500Hz 8FSK modulated tone to a USB Weaver modulator.

    1500 Hz 8 FSK audio samples from PS to PL -> Digital Weaver USB modulator -> hardware DAC -> hardware HF amplifier ->  Low Pass Filter -> Antenna

    2) Use an external DDS, programming its output frequency to produce 8FSK and amplitude to generate the ramps (raised cosine initial amplitude increase and final amplitude decrease) of FT8.

    174 bits encoded sequence  from PS to PL -> external AD9851 DDS -> hardware HF amplifier ->  Low Pass Filter -> Antenna

    For AD9851 Amplitude Modulation see this

    PRO'S and CON'S

    Follow the JTDX wayUse an external DDS
    Audio stream / Amount of data+++ The audio stream is produced by JTDX!
    The audio stream must be transferred from PS to PL via DMA or similar
    --- About 300 KBytes must be transferred from PS to PL every 15 seconds
    There's no audio stream

    +++ Only 174 bits (produced by JTDX) must be transferred from PS to PL every 15 seconds
    Available HW boards--- I didn't find any high speed DAC board +++ I found AD9851 (DDS + DAC) board for around 30 Euros or AD9850 for 20 Euros
    JTDX addition+++ None--- 3 bits FT8 symbol to 40 bits AD9851 programming word 
    Vivado design--- Weaver modulator (2 x DDS compiler + 2 audio FIR filters + 4 multipliers)+++ 40 bits serial output with clock, word write and reset
    --- PWM amplitude modulator for FT8 ramp up and down. 
    Pins Usage--- Zynq7010 12 pins output (10 bits data + clock + strobe) +++ Zynq7010 4 pins (serial data, clock, word write, PWM amplitude) 

  • FT8 decoding

    Guido05/08/2023 at 07:32 0 comments

    It's now time to decode some radio ham digital modes, starting from FT8, which I'm very fond of!

    The idea is to transfer the PCM audio stream from the PL to the PS, where a "standard" linux audio driver lets consume the stream using the FT8 software WSJT-X. See Overview block diagram

    Regarding the WSJT-X specific implementation on PetaLinux, I modified JTDX adapting it to the different software environment (without a windows manager) obtaining myjtdx

    The Linux Audio Driver is described here Xilinx ALSA audio I2S driver

    A Vivado project with an I2S Receiver and Transmitter, DMA and Linux Device Driver is very well described here:

    In part 1 and 2 of Audio Linux Device Driver for (*) Zedboard - hardware implementation you can read the Vivado hardware implementation, while In part 3  you can read the PetaLinux building.

    (*) Zedboard is an AVNET board based on Xilinx Zynq-7000

  • Keyboard and Mouse control

    Guido01/16/2023 at 21:21 0 comments

    After obtaining an HDMI video out from the EBAZ4205 board and being able to use it from Linux by /dev/fb0, now I need to interact with Linux and the C / Python app (showing the spectrum, tuning and so on) with standard input devices keyboard and mouse.

    For this need, I examined two options:

    • USB
    • PS/2


    Unfortunately EBAZ4205 has no USB interface, but I found the way to implement it using a cheap external module USB3300

    I'm now looking forward to arriving it from China.


    • It's a bit obsolete but it is very promising because it doesn't need any hardware addition.
    • I found an AXI PS2 IP controller from DIGILENT: AXI PS/2 1.0 IP Core User Guide from DIGILENT,. I tried to use it but I didn't succeed! 
    • So I decided to use some VHDL code I found here PS/2 Mouse Interface (VHDL). Unfortunately this VHDL code manage the two buttons PS/2 mouse only and not the middle button and the wheel (so called IntelliMouse), so I had to integrate the VHDL code. 
    • Then I also found a linux device driver  xilinx_ps2.c with a similar memory mapped interface that I hope to be able to adapt.

    You can follow the full Hackaday project EBAZ4205 PS/2 Mouse

  • Video Out

    Guido01/04/2023 at 09:19 0 comments

    As I explained before, I would like to embed the User Interface App into the PS side of Zynq.

    The App, actually written in Python (Matplotlib + pyQTgraph), allows to see the spectrum, the waterfall and to tune radio stations.

    For this reason the EBAZ4205 should have implemented with any kind of video out.

    I examined a few options.

    • VNC server / X11 forwarding
      • Not difficult to do in SW
      • No need to change hardware
      • It needs an external PC 
    • VGA
      • It needs a simple resistive external ladder
      • a bit old fashioned
      • it's analogic
      • It needs an external VGA display or a VGA/HDMI external adapter
    • HDMI with HW converter
      • It uses an external device (ADV7511 or similar) to convert the Zynq parallel output to the HDMI differential digital signals to handle the highest resolutions 
      • It's digital
      • you can use any HDMI display / TV

View all 27 project logs

Enjoy this project?



Jean-Pierre Mallet wrote 02/01/2023 at 15:48 point

 EBAZ4205_SDR is always 0404 Sorry..

  Are you sure? yes | no

Guido wrote 02/01/2023 at 17:26 point

I'm so sorry! I set the repository as "private" during last modifications but I forgot to restore it to "public"! Now it's OK!

  Are you sure? yes | no

Guido wrote 11/12/2022 at 14:30 point

Thank you Gordon, I immediately corrected that mistake!

  Are you sure? yes | no

Gordon wrote 11/12/2022 at 13:48 point

It should be "an FPGA" not "a FPGA"

  Are you sure? yes | no

crun wrote 11/16/2022 at 01:48 point

Hi Guido, as I love pedulumtry as much as the next commentard, I thought I would explain at great length why this is so. The reason for this is that F is spoken as "eff". The spoken sound begins with a vowel, so we use "an" not "a". (spoken as "uh" or "ay") I'm not sure how it feels to your mouth, but for a native english speaker, it feels awkward to say "uh eff" due to the hard stop between sounds, and so we soften the "a" to "an", so it feels easier to speak it a bit run together as "an-eff". (I guess like "al" or "del" in spanish)

It is not really another strange english rule of grammar, but just verbal laziness in action. So the rule is really: if it begins with a vowel _sound_, use "an". This often applies where the letters are spoken eg FPGA, but also in some words where the letter is being  like a vowel e.g. "yvette"

"An A/E/F/H/I/L/M/N/O/R/S/U/X" - some begin with e or a sounds

"a uniform" "an underwater camera"

  Are you sure? yes | no

Guido wrote 11/12/2022 at 07:36 point

Hello Crun, thank you for your suggestions. I would try to build a 9th order elliptic passive filter with a stop band around 70 dB., tuning one of its traps to the FM band, see the log for details.

  Are you sure? yes | no

crun wrote 11/16/2022 at 01:55 point

You also need a high pass to keep AM broadcast band out. They are insanely strong for anyone within several miles of a transmitter. Here is the very front of a Kenwood TS130 radio. C1-3,L1,2 are a high pass filter. L3,4 form a trap for the IF frequency (8.9MHz). T1 ups the voltage, but probably also has a highpass function. Even though radios have the highpass filter to cut it out, AM broadcast is so strong, that this has no effect on the receivers ability to hear it.

OK, looks like I can't put the picture in here - sent to you on the chat...

  Are you sure? yes | no

crun wrote 11/10/2022 at 20:19 point

Great to see you making progress!.

Commercial radios put traps at broadcast frequencies, and don't just rely on lpf/bpf filters. i.e. you have a parallel resonant circuit in series andseries resonant circuit to ground. FM is only a bit over one octave past your passband, so you have to put nulls in to get significant attenuation. Alternatively you can customise an epileptic filter, by making some of the zeros occur exactly where you need to null signals out.

  Are you sure? yes | no

Guido wrote 08/29/2022 at 17:42 point

Thank you Alperen, I hope to be successful!

  Are you sure? yes | no

Alperen wrote 08/29/2022 at 09:50 point

Great project!

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates