from python model to FPGA: building a fourier-inspired AMC classifier
Machine learning models are really good at fitting patterns. The annoying part is that they are not always good at understanding why those patterns exist.
One clean example is periodicity.
An MLP or Transformer can learn a repeating pattern if it sees enough examples during training. But if the input moves outside the range it was trained on, the model can start to fall apart. It learned the shape of the training data, but not necessarily the rule behind the repetition.
That matters a lot for signals.
Wireless signals are built around waves, frequency, phase, and periodic structure. So if we are trying to classify wireless signals, it feels a little wasteful to force a generic neural network to rediscover all of that from scratch.
That is what made Fourier Analysis Networks, or FANs, interesting to me.
FANs are neural networks that build Fourier-style periodic structure directly into the architecture. Instead of only using standard linear layers and nonlinear activations, they use sine and cosine branches so the model has a built-in bias toward periodic behavior. When I first saw that idea, my immediate thought was automatic modulation classification.
What Is AMC?
Automatic modulation classification, or AMC, is basically the "what kind of signal am I looking at?" stage of a radio receiver.
Before a receiver can correctly demodulate a signal, it helps to know what modulation scheme was used. Is it BPSK? QPSK? 16-QAM? 64-QAM? AM? FSK?
That decision matters because each modulation type needs to be handled differently. If the receiver sends the signal into the wrong demodulator, the rest of the chain can fail.
AMC is useful in places like cognitive radio, adaptive radios, spectrum monitoring, and electronic surveillance, where the receiver might not have a fixed standard to follow. It has to look at the received IQ samples and make a fast call.
The hard part is that real wireless signals are messy.
In a perfect textbook world, every modulation has a clean and obvious structure. In practice, the received signal gets hit by noise, fading, phase offset, carrier-frequency offset, Doppler shift, gain variation, and hardware imperfections. At high SNR, many classes are easy to separate. At lower SNR, or when the channel gets ugly, similar classes can start looking very similar.
The Software vs Hardware Gap
Most modern AMC research is deep-learning heavy. People use CNNs, LSTMs, ResNets, Transformers, CLDNNs, and all kinds of hybrid models on IQ samples. And to be fair, those models can perform really well.
But a model that works well in PyTorch is not automatically a good receiver block.
Real radio hardware does not have unlimited compute. It has resource limits, timing constraints, power limits, memory limits, and latency requirements. A giant neural net might be nice on a GPU, but that does not mean it belongs on an FPGA sitting inside a real-time signal chain.
There is also a signal-processing angle here. A lot of deep AMC models learn useful patterns, but they learn them in a black-box way. They rely on the dataset to teach them what matters. That can work well when the test data looks like the training data. But wireless systems are full of distribution shifts. Channel conditions change. SNR changes. Oscillators drift. The signal gets distorted.
So my goal was not just:
Can we train an AMC model?
The better question was:
Can we build an AMC model that uses signal structure directly and still survives the trip into FPGA hardware?
Why Fourier Ideas Make Sense Here
Fourier analysis is one of the most natural tools in signal processing. The basic idea is that a complicated signal can be represented as a combination of simpler sine and cosine waves.
A regular Fourier series looks like this:
The coefficients tell you how much each frequency component contributes to the signal.
FAN takes this kind of idea and turns it into a neural network layer. Instead of manually choosing every frequency and coefficient, the network learns Fourier-like transformations:
In that expression, W_in controls what goes into the sine and cosine functions, and W_out controls how strongly those periodic components contribute. That is cool for AMC because IQ signals are naturally tied to frequency, phase, and periodic behavior.
But here is the important clarification: I did not implement a full FAN directly on FPGA.
A full FAN layer would require learned sine and cosine transforms in hardware. That means dynamic angle calculations, sine/cosine approximation, range reduction, fixed-point trig accuracy, and a lot of multiply-accumulate work. On a small FPGA, that gets expensive very quickly.
So instead, this project takes a hardware-friendly version of the idea:
Use fixed Fourier/DFT features to expose frequency structure, then use a learned gate and fixed-point MLP to classify the modulation.
So the design is FAN-inspired, not a full FAN implementation. That distinction matters. The point was not to perfectly recreate the software FAN paper. The point was to ask whether Fourier-style structure could be used in a compact FPGA-friendly AMC pipeline.
The Actual FPGA Pipeline
The final pipeline looks like this:
Each input window contains 128 complex IQ samples. Since each complex sample has I and Q, that means the raw input is really:
I did not feed those 256 values directly into the neural network. Instead, the FPGA computes 32 selected DFT-bin magnitude-squared features.
The selected bins are:
So instead of using every frequency bin, the design keeps every fourth bin.
The DFT turns the waveform into a frequency-energy fingerprint.
That gives the classifier a compact view of where the signal energy lives in frequency. It also makes the neural network much smaller.
If we used raw IQ directly, a 256 -> 128 dense layer would need:
With the current DFT feature vector, a 32 -> 128 dense layer needs:
That is an 8x reduction for that layer.
On FPGA, that matters a lot. Fewer inputs means fewer weights, fewer multiplications, fewer memory reads, and an easier path to timing closure.
After the DFT feature engine, the model applies a learned hard-sigmoid gate. The gate learns which frequency features matter more for classification. Then the gated features go into a 128-neuron hidden layer and finally into an 11-class output layer. Everything is fixed-point. The IQ samples use signed Q1.15 format, and the network weights/activations are quantized so the model can run in RTL instead of floating point.
Testing the Model
The model was evaluated on RadioML2016.10A-style data from archive\RML2016.10a_dict.dat. The evaluation set had 11,000 samples.
Floating-point gated model
62.35%
Fixed-point model
61.32%
That small drop is actually one of the best signs in the project. It means the model mostly survived quantization. The fixed-point version kept most of the behavior of the floating-point model.
The fixed-point predictions also agreed with the floating-point predictions 81.71% of the time. That matters because the goal was never just to train something in Python. The goal was to build something that could make it into hardware without completely changing personality.
Did the Gate Actually Help?
Yes.
Gate disabled
55.70%
Gate enabled
61.32%
So the learned gate added about 5.6 percentage points. That means the gate is not just decorative. It is actually helping the model decide which DFT features matter.
Accuracy Across SNR
The fixed-point model was fairly stable across the high-SNR test points:
| SNR | Fixed-point accuracy |
|---|---|
| 10 dB | 59.10% |
| 12 dB | 62.49% |
| 14 dB | 60.68% |
| 16 dB | 62.43% |
| 18 dB | 61.89% |
It is not state-of-the-art AMC accuracy, but the stability is useful. The model is not only working at one cherry-picked SNR point.
What Classes Worked Best?
The strongest classes were mostly AM and FSK-style modulations:
| Class | Accuracy |
|---|---|
| AM-DSB | 100.00% |
| AM-SSB | 99.51% |
| CPFSK | 96.43% |
| GFSK | 94.96% |
| BPSK | 84.90% |
This makes sense. These classes have frequency-domain energy patterns that the DFT magnitude features can capture pretty well.
What Classes Failed?
The weakest classes were:
| Class | Accuracy |
|---|---|
| QPSK | 5.76% |
| QAM64 | 10.31% |
| QAM16 | 18.37% |
| WBFM | 26.47% |
At first, those numbers look ugly.
But they are also useful because they tell us exactly what the current feature representation is missing.
The model uses DFT magnitude-squared features. These are good for capturing frequency-domain energy, but they throw away phase. That is a huge limitation for PSK and QAM classes, where phase and constellation structure are the whole game.
The evaluation set was class-balanced, so this was not just a dataset imbalance problem. The issue was that the model over-predicted stronger spectral classes. For example, many QPSK, QAM16, and QAM64 samples were absorbed into 8PSK. WBFM was often absorbed into AM-DSB.
So the model was not randomly bad. It was making a very specific kind of mistake:
Magnitude-only Fourier features collapse several phase-sensitive classes into stronger frequency-energy classes.
That gives a very clear direction for the next version: preserve more phase or constellation information without blowing up FPGA cost.
The FPGA Board Test
The most important test was running the model on the actual DE1-SoC FPGA board.
Streamed IQ windows
957
Correct
598
Measured FPGA accuracy
62.55%
This is close to the offline fixed-point result, which is exactly what we wanted to see.
The model did not just work in Python. It survived:
- training
- quantization
- fixed-point modeling
- RTL implementation
- Quartus compilation
- timing closure
- real FPGA board execution
That is the actual win.
The board was not doing live RF capture yet. It was classifying host-streamed IQ windows through a virtual-JTAG test path. So this is not a complete RF receiver.
But that is fine for this stage. The project is focused on the post-front-end AMC classifier: assuming a receiver front-end gives us normalized IQ windows, can this classifier run efficiently on FPGA? The answer is yes, with limitations.
Hardware Resource Results
The final design compiled successfully and closed timing at 50 MHz. Resource usage on the DE1-SoC was:
| Resource | Usage |
|---|---|
| ALMs | 6,963 / 32,070, about 22% |
| Registers | 7,347 |
| Block memory bits | 4,096 / 4,065,280, less than 1% |
| RAM blocks | 1 / 397, less than 1% |
| DSP blocks | 80 / 87, about 92% |
This result is important.
The design is not memory-heavy at all. It barely touches the available block RAM.
The bottleneck is arithmetic.
Using 80 out of 87 DSP blocks means the design is already close to the FPGA's multiplier limit. So future improvements probably should not just add more dense layers or more parallel features without thinking carefully.
The next optimization path is probably:
- share multipliers across cycles
- reduce DSP usage in the DFT engine
- prune weights
- reduce dense-layer cost
- add phase-aware features carefully
- explore hybrid raw-IQ plus Fourier features
So What Did This Prove?
The headline result is not just 62% accuracy.
The more important result is:
A compact Fourier-inspired AMC classifier was trained, quantized, implemented in RTL, compiled, closed timing, and tested on a real FPGA board.
That is a much harder bar than a software-only accuracy number.
Compared to large deep AMC models, this is not state-of-the-art accuracy. But compared to lightweight hardware-aware AMC, it is a useful starting point.
The project shows that fixed Fourier features plus a learned gate can produce a real FPGA-verifiable AMC classifier. It also shows exactly where the design breaks: phase-sensitive modulations. That is honestly the best kind of prototype. It works enough to prove the pipeline, and it fails in a way that tells you what to build next.
Next Steps
The next version should focus on preserving more information without turning the design into a giant neural net. The most obvious directions are:
- Add phase-aware DFT features.
- Use DFT real/imag components instead of only magnitude-squared.
- Add lightweight constellation statistics.
- Try a hybrid model with some raw IQ features plus Fourier features.
- Train with hardware-aware quantization earlier.
- Reduce DSP pressure through serialization or multiplier sharing.
- Compare against a raw-IQ MLP baseline to quantify the exact tradeoff.
The big challenge is not "can we get higher accuracy in Python?" Of course we can, if we throw a bigger model at it.
The more interesting challenge is:
Can we improve the weak modulation classes while still fitting inside the FPGA resource budget?
That is the real hardware-aware AMC problem. And that is what makes this project interesting.