Whitebox Handheld SDR

Low power, embeddable framework;
inspired by cell phone and SDR architectures

Keep Up To Date on early access to development kits

Features

Hardware

  • Quadrature receiver from 100MHz to 1000MHz
  • Quadrature transmitter from 100MHz to 1000MHz
  • Half-duplex
  • 1MHz bandwidth
  • 10 MS/s, 10 bit ADC & DAC
  • 16MB SDRAM, 16MB Flash
  • 3.3V low power operation
  • 100mA sleep state, 500mA transceiving

Interfaces

  • UART over USB
  • 10/100 Ethernet
  • ARM & FlashPro JTAG
  • 1 I2C, 1 SPI, 1 UART, 1 ADC, 16 GPIO

Software

  • Host PC Generation and Simulation
  • uClinux 2.6.xx + Busybox from Emcraft Systems
  • Digital Signal Processing Chain for RF
  • Platform and Character Drivers for RF Board
  • Userspace control libraries and examples
  • GNURadio drivers

Applications

  • Analog/digital multimode radio
  • Software deifined radio
  • Battery powered baseband modems
  • Portable, mobile and base station terminals
  • Satellite communications
  • Amateur radio
  • Police and commercial radio

General Description

The Whitebox Framework is a suite of tools to design, simulate, and generate efficient, low power, signal processing systems for embedded Linux computers based on SoC FPGA’s. It includes an embeddable software radio with a small footprint and power profile, like a smartphone cellular radio architecture, but aim’ed at VHF/UHF operation.

At the core is a customizable System on a Chip with both an ARM Cortex-M3 running uClinux / Flash based FPGA; paired with a frequency agile radio frontend. Drivers handle transporting data from userspace Linux to antenna with zero memory copy. Supports GNURadio over UDP in Peripheral mode for rapid development.

The module is targeted at battery operated instrumentation. In particular is has low static power dissapation (<100mA). Augment the embedded system with a full suite of embedded communication controllers including I2C, SPI, UART, and GPIOs. Attach it to an Application SoC and make a Smart Software Radio with Android.

Functional Block Diagram

_images/functional_block_diagram.png

Signals

  1. Input Voltage (VIN). Battery, car, or USB powered.
  2. Serial I/O (USB). Busybox shell accessable over USB. I2C, SPI, UART, GPIOs available for expansion.
  3. JTAG Debugging (JTAG). Debug both the ARM core and FPGA via JTAG.
  4. 10/100 Ethernet (ETH). Supports TFTP boot, NFS mount, and GNURadio over UDP.
  5. Receiver (RX SMA). Receive antenna SMA.
  6. Transmitter (TX SMA). Transmit antenna SMA.
  7. 10MHz Reference (REFIN). External 10MHz reference input SMA.

Datasheet

DC Specifications

Power Supply
Parameter Min Typ Max Units
Supply Voltage       V
I/O Logic
Parameter Min Typ Max Units
Logic 0 Voltage       V
Logic 1 Voltage       V

AC Specifications

Transmitter
Parameter Min Typ Max Units
Frequency 100   1000 MHz
Bandwidth     1 MHz
RF Power       dBm
Impedance   50   Ohm
Receiver
Parameter Min Typ Max Units
Frequency 50   1000 MHz
Bandwidth     1 MHz
RF Power       dBm
Impedance   50   Ohm

Absolute Maximum Ratings

Parameter Min Max Units
Supply Voltage 3.7 9 V
Input Current 100 500 mA
Operating Temperature 35 80 C

Pins

The main expansion pins are availble on the BP7 - BP14 headers. These headers are designed to mesh onto the Emcraft Extended Baseboard. They are 24-pin, 2mm spaced headers. The headers are backwards compatible with the non-extended Baseboard, too.

BP7
Pins Connection
1 VCC3BSB
2, 8, 10, 12, 14, 16, 18, 20, 21, 22, 23, 24 DGND
3, 4, 5, 6, 7, 9, 11, 13, 15, 17, 19, 21, 23 No Connection
BP8
Pins Connection
1 VCC3BSB
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 22, 23, 24 DGND
3, 5, 7, 9, 11, 13, 15, 17, 19 No Connection
BP9
Pins Connection
1 VCC3BSB
4, 12, 21, 22, 23, 24 DGND
2, 3, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20 No Connection
BP10
Pins Connection
1 VCC3BSB
2 5VIN
3, 21, 22, 23, 24 DGND
4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 No Connection
BP11
Pins Connection
1 VCC3BSB
2, 21, 22, 23, 24 DGND
3 DAC[0]
5 DAC[1]
7 DAC[2]
9 DAC[3]
11 DAC[4]
13 DAC[5]
15 DAC[6]
17 DAC[7]
19 DAC[8]
4, 6, 8, 10, 12, 14, 16, 18, 20 No Connection
BP12
Pins Connection
1 VCC3BSB
2, 21, 22, 23, 24 DGND
3 DAC_EN
4 RADIO_RESETN
5 DAC_PD
6 RADIO_CDATA
7 DAC_CS
8 RADIO_SCLK
9 DAC_CLK
10 RADIO_RDATA
12 RADIO_CSN
14 VCO_CLK
16 VCO_DATA
18 VCO_LE
20 VCO_CE
11, 13, 15, 17, 19 No Connection
BP13
Pins Connection
1 VCC3BSB
2, 21, 22, 23, 24 DGND
3 VCO_PDB
4 ADC_CLK
5 VCO_LD
6 ADC_S2
7 DAC[9]
8 ADC_S1
10 ADC_DFS
12 ADC_AD[9]
14 ADC_BD[0]
9, 11, 13, 15, 16, 17, 18, 19, 20 No Connection
BP14
Pins Connection
1 VCC3BSB
2, 21, 22, 23, 24 DGND
3 ADC_AD[8]
4 ADC_BD[1]
5 ADC_AD[7]
6 ADC_BD[2]
7 ADC_AD[6]
8 ADC_BD[3]
9 ADC_AD[5]
10 ADC_BD[4]
11 ADC_AD[4]
12 ADC_BD[5]
13 ADC_AD[3]
14 ADC_BD[6]
15 ADC_AD[2]
16 ADC_BD[7]
17 ADC_AD[1]
18 ADC_BD[8]
19 ADC_AD[0]
20 ADC_BD[9]

Timing Diagrams

TODO

Test Circuit

Testing the system is accomplished via the test_* commands.

The first test to run is test_driver. This will make sure that the driver is loaded correctly and that the character device interface conforms to POSIX standards.

The next tests make sure that the library routines to manipulate the register files work correctly. They’re test_adf4351 and test_cmx991.

The next test verifys that the Whitebox HDL and RF card are functioning correctly together. It does this by first locking all of the PLLs at all of the Amateur bands between 100MHz and 1000MHz. Next, it streams reads and writes from the character device to the HDL. A loopback is used to test both transmit and receive DSP chain interfaces between the driver and the HDL, including DMA.

Transmitter Calibration

Since the device uses an Analog Quadrature Modulator, a number of real world parasitics can be compensated for by a calibration routine. The transmitter is calibrated by connecting the TX port of the daughter card to a specturm analyzer, or the receiver of another radio.

First, a static DC value of 0 is sent out on the DAC, and you will observe carrier leakage. You can adjust the values of the I and Q DC offset by using the arrow keys. When you’ve minimized the carrier leakage, press Enter.

Next, a lower sideband singal will be sent through the radio. Again, manipulate the DC offset to minimize the carrier. Press Enter.

Next, the lower sideband signal will still be applied, but the goal is to minimize the upper (undesired) sideband power. We will first adjust the phase offset between the I and Q signal components.

Finally, we will take one more stab at reducing undesired sideband power. To do this, we will adjust the gain between the I and Q components using the arrow keys. Adjust so that way the undesired sideband power is at a minimum. Press enter.

You will again be able to adjust the phase, to see if you can minimize it further. When you are finally done, press q or Esc to quit.

Application Notes

TODO

  • Amplifiers: Getting off the bench
  • Transmit Receive Switch: Unity
  • Reference Clocking for better timing accuracy

Dimensions

_images/dimensions.png

Ordering

ordering codes for differing packages and performance criteria

Liability Disclaimer

liability disclaimer regarding device use in certain environments such as nuclear power plants and life support systems

SDK

Installing

Downloading a compiled image is not ready yet. For now, please follow the Building instructions before reaching this point.

NFS

Windows Mac Linux

TFTP

Windows Mac Linux

Custom Kernel Boot

This step depends on you properly setting up TFTP on your machine.

To begin, plug the USB serial into your computer and open up a serial console:

$ screen /dev/ttyUSB0 115200

You should see the Linux kernel boot, and then a shell prompt. If it doesn’t show up try pressing Enter. Issue a reset command.

~# reboot

On reboot, make sure to hit a key when it asks to stop autoboot. You will fall into the U-Boot prompt.

Execute the following commands from U-Boot (your constants will vary.)

A2F500-SOM> setenv ipaddr 192.168.220.3 A2F500-SOM> setenv serverip 192.168.220.1 A2F500-SOM> setenv image whitebox.uImage A2F500-SOM> saveenv

Then, either boot onetime from the network:

A2F500-SOM> run netboot

Or, load this kernel image into flash:

A2F500-SOM> run update A2F500-SOM> reset

Flashing the FPGA

Flashing the FPGA is incredibly easy once you have NFS set up.

Warning

There’s a chance that this process could brick your device; and that you will need a FlashPro JTAG programmer to recover. This has happened to me once in hundreds of flashings, so it is rare, but possible.

To flash, issue the following command from the USB serial console:

~# iap_tool /mnt/whitebox/build/hdl/TOPLEVEL.dat

And you should see it then succesfully flash the FPGA in about one minute.

I’ve found that you need to pull the power from the device before the gate array is guaranteed to be in a new stable state. Pushing the reset button will not do that, and I’ve wasted hours trying to debug HDL issues that only needed a simple reboot to go away.

If it doesn’t exit with a status code 0, or hangs forever, then the best thing to do is try and cycle the power by unplugging the device and then plugging it back in. Every time that I’ve had this happen, when I restarted and attempted the flashing again, everything worked fine. Except for once.

One time, I was not so lucky. On reboot, all I saw was garbage come out on the serial console. If this happens, then you need to get a FlashPro cable and flash an original U-Boot image over JTAG.

Building

Setting up a Linux VM (optional)

Cloning the source code

~$ git clone https://github.com/testaco/whitebox.git

Bootstrapping

~/whitebox$ sh bootstrap.sh

This will take a bit of time, as it is going to install the SmartFusion gcc toolchain, as well as the Emcraft uClinux distribution.

Compiling the Kernel

After the bootstrap, add the ARM toolchain to your PATH by running the final commands or adding the appopriate statement to your .profile.

~/whitebox/build$ cd build && cmake .. ~/whitebox/build$ make

This will compile the kernel and leave the bootable image as whitebox.uImage.

You can then load this image by setting up NFS and doing a Custom Kernel Booat as described in Installing.

Building the FPGA Image

First step is to build the hdl files:

~/whitebox/build$ make hdl

Next, copy the manifest of Verilog files into the Libero project. These files include whitebox_toplevel.v and whitebox.v and should be put in the folder libero/hdl.

Run the Libero compilation procedure. It will take 20-40 minutes to complete.

When it’s done, open up FlashPro on the project, and go to File -> Export -> Single Programming File. This will create the TOPLEVEL.dat file which you can then install by following the Flashing the FPGA guide in Installing.

Application Programming Interface

The development system broken up into three distinct parts.

Register Transfer Logic

This code is written completely in Python, and then transverted into Verilog. An entire simulation system has been built to verify the correctness of the generated Verilog. With numpy, scipy, matplotlib, and sympy, Python is capable of being an end-to-end tool for building complex embedded systems.

Digital Signal Processing

Digital Signal Processing is

The block diagram representation of the signal uses symbols to represent the interconnections of the core parts of a signal processing flow including mathematical operations and memory. In the case of RTL, the memory is Registers, and the Transfer Logic implements the flow control and mathematical operations.

The input and output of the signal is represented by a Signature, and this class represents a a stage of the flow diagram. The signature tracks the minimum and maximum values allowed, the number of bits required to store the signal, and methods to transform the signal into both MyHDL and numpy types.

class dsp.Signature(name, signed, **kwargs)

Represents a small register file of a handshake for the DSP signal flow.

Parameters:
  • name – Human readable
  • bits – Bit precision of each channel
  • min – The minimum value allowed
  • max – The maximum value allowed
  • valid – An already existing boolean Signal
  • last – An already existing boolean Signal
  • i – An already existing intbv Signal
  • q – An already existing intbv Signal
record(clearn, clock)

A myhdl stimulus what will record the valid dsp stream and save it to self.samples_i and self.samples_q.

Parameters:
  • clearn – A myhdl Signal.
  • clock – A myhdl Signal.
Returns:

A simulation capable MyHDL object.

A chain of DSP blocks can be simulated with MyHDL. You apply an input series of complex samples, and then record the output series. Convenience functions are offered to show the phase and frequency response of the system.

class test_dsp.DSPSim(in_sign, out_sign)

Run a simulation with input going into in_sign and output going to out_sign.

Records the input, output, and stages of the internal flow during the simulation. They can then be plotted later with the figure_* methods.

Parameters:
  • in_sign – The input.
  • out_sign – The output.
simulate(in_c, dspflow, **kwargs)

Actually run the simulation with complex numbers.

Parameters:
  • in_c – The complex input sequence.
  • dspflow – The MyHDL module representing the dsp flow.
  • interp – How many samples to zero-stuff.
Returns:

The valid-output complex sequence.

FIFO Buffer

The FIFO buffer is the building block that produces samples into, and consumes samples out of the DSP chain.

This implementation cannot be synthesized into Verilog, and is the only module that has a dependency on the Vendor specific toolset. That fact could be changed by instead abstracting the SRAM model and build a synthesizable FIFO on top of that.

FIFO

This is the fifo

fifo.fifo(resetn, re, rclk, Q, we, wclk, data, full, afull, empty, aempty, afval, aeval, wack, dvld, overflow, underflow, rdcnt, wrcnt, **kwargs)

Main Fifo object.

This only works as a simulation; when it is transpiled into Verilog, an auto-generated IP Core is used instead. To see how to build the the IP core yourself with Libero IDE, check out this video (TODO).

Synthesizable Blocks

This is a list of synthesizable blocks.

dsp.offset_corrector(clearn, clock, correct_i, correct_q, in_sign, out_sign)

Analog quadrature offset corrector.

Lets you add a DC offset to the incoming signal.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • correct_i – An intbv to add to each sample’s i channel.
  • correct_q – An intbv to add to each sample’s q channel.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
Returns:

A synthesizable MyHDL instance.

dsp.gain_corrector(clearn, clock, gain_i, gain_q, in_sign, out_sign)

Analog quadrature gain corrector.

Lets you correct for gain imbalance with an AQM.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • correct_i – An intbv to add to multiply each sample’s i channel by.
  • correct_q – An intbv to add to multiply each sample’s q channel by.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
Returns:

A synthesizable MyHDL instance.

dsp.binary_offseter(clearn, clock, in_sign, out_sign)

Converts from two’s complement to binary offset.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
Returns:

A synthesizable MyHDL instance.

dsp.iqmux(clearn, clock, channel, in0_sign, in1_sign, out_sign)

Multiplex between two incoming signals.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • channel – The selected channel.
  • in1_sign – The first incomming signature.
  • in2_sign – The second incomming signature.
  • out_sign – The outgoing signature.
Returns:

A synthesizable MyHDL instance.

dsp.iqdemux(clearn, clock, channel, in_sign, out0_sign, out1_sign)

Decoder two outgoing ports.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • channel – The selected channel.
  • in_sign – The incomming signature.
  • out1_sign – The first outgoing signature.
  • out2_sign – The second outgoing signature.
Returns:

A synthesizable MyHDL instance.

duc.truncator(clearn, in_clock, in_sign, out_sign)

Truncates in_sign to out_sign.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
Returns:

A synthesizable MyHDL instance.

duc.upsampler(clearn, clock, in_sign, out_sign, interp)

Upsamples the signal by repeating each one interp - 1 times.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
  • interp – How many times to inerpolate.
Returns:

A synthesizable MyHDL instance.

ddc.downsampler(clearn, clock, in_sign, out_sign, decim)

Takes every decim samples.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
  • decim – The decimation factor.
Returns:

A synthesizable MyHDL instance.

duc.interpolator(clearn, clock, in_sign, out_sign, interp)

Interpolates the signal by stuffing interp - 1 zeroes between samples.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • in_sign – The incomming signature.
  • out_sign – The outgoing signature.
  • interp – How many times to inerpolate.
Returns:

A synthesizable MyHDL instance.

duc.delay_n(n, clearn, clock, sign, x, y)

Delay a signal n cycles.

Parameters:
  • n – A constant, how many cycles to delay.
  • clearn – The reset signal.
  • clock – The clock.
  • sign – The signature.
  • x – Input.
  • y – Delayed outpt.
Returns:

A synthesizable MyHDL instance.

duc.comb(clearn, clock, delay, in_sign, out_sign)

A comber with delay.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • delay – A constant, how many cycles to delay.
  • in_sign – The input signature.
  • out_sign – The output signature.
Returns:

A synthesizable MyHDL instance.

duc.accumulator(clearn, clock, in_sign, out_sign)

A simple accumulator.

Parameters:
  • clearn – The reset the accumulator.
  • clock – The clock.
  • in_sign – The input signature.
  • out_sign – The output signature.
Returns:

A synthesizable MyHDL instance.

duc.cic(clearn, clock, in_sign, out_sign, interp, cic_order=4, cic_delay=2, **kwargs)

A cic filter with given order, delay, and interpolation.

Parameters:
  • clearn – The reset the accumulator.
  • clock – The clock.
  • in_sign – The input signature.
  • out_sign – The output signature.
  • interp – The interpolation of the cic filter.
  • cic_order – The order of the CIC filter.
  • cic_delay – The delay of the CIC comb elements.
Returns:

A synthesizable MyHDL instance.

duc.interleaver(clearn, clock, clock2x, in_sign, out_valid, out_data, out_last)

Interleaves the input signature to a interleaved single channel signal.

Parameters:
  • clearn – The reset signal.
  • clock – The clock.
  • clock2x – The double clock rate.
  • in_sign – The incomming signature.
  • out_valid – The outgoing valid flag.
  • out_data – The output data.
  • out_last – The outgoing last flag.
Returns:

A synthesizable MyHDL instance.

Digital Up Converter

The Digital Up Converter takes a low-sample rate stream and up-converts it to a high sample rate, while conditioning it for an Analog Quadrature Modulator.

It consists of a synchronizer, a FIFO consumer, and a DSP chain. The synchronizer safely transfers flags from the system clock domain.

The consumer reads samples off the FIFO queue and feeds them into the DSP chain.

The DSP chain can work in both rectangular and polar coordinates. This means that you can:

  1. Send i and q samples directly and have them digitally up-converted, or
  2. Send phase offset and frequency changes to modulate a Direct Digital Synthesizer
duc.duc(clearn, dac_clock, dac2x_clock, loopen, loopback, fifo_empty, fifo_re, fifo_dvld, fifo_rdata, fifo_underflow, system_txen, system_txstop, system_ddsen, system_filteren, system_interp, system_fcw, system_correct_i, system_correct_q, system_gain_i, system_gain_q, underrun, sample, dac_en, dac_data, dac_last, **kwargs)

The Digital Up Converter.

Parameters:
  • clearn – Reset signal, completely resets the dsp chain.
  • dac_clock – The sampling clock.
  • dac2x_clock – Twice the sampling clock.
  • loopen – Enable the loopback device.
  • loopback – The loopback signature to the digital down converter.
  • fifo_empty – Input signal that the fifo is empty.
  • fifo_re – Output signal to enable a sample read.
  • fifo_dvld – Input signal that FIFO data is valid.
  • fifo_rdata – Input sample data.
  • fifo_underflow – Input signal that fifo underflowed.
  • system_txen – Enable transmit.
  • system_txstop – Stop the transmitter.
  • system_ddsen – Enable the DDS.
  • system_filteren – Enable the CIC filter.
  • system_interp – Interpolation rate.
  • system_fcw – Set the frequency control word of the DDS.
  • system_correct_i – Set the i-Channel AQM DC Offset Correction.
  • system_correct_q – Set the q-Channel AQM DC Offset Correction.
  • system_gain_i – Set the i-channel AQM gain correction.
  • system_gain_q – Set the q-channel AQM gain correction.
  • underrun – Output of number of underruns to RFE.
  • sample – The sample.
  • dac_en – Enable DAC on valid data signal.
  • dac_data – The interleaved DAC data.
  • dac_last – The last sample going out on the DAC, stops the transmit.
Returns:

A MyHDL synthesizable module.

Digital Down Converter
ddc.ddc(clearn, dac_clock, loopen, loopback, fifo_full, fifo_we, fifo_wdata, system_rxen, system_rxstop, system_filteren, system_decim, system_correct_i, system_correct_q, overrun, adc_idata, adc_qdata, adc_last, **kwargs)

Direct Down Converter.

Parameters:
  • clearn – The reset signal.
  • dac_clock – The sampling clock.
  • loopen – Loopback enable.
  • loopback – Loopback signature.
  • fifo_full – Is the receive FIFO full.
  • fifo_we – Output to signal to FIFO to write.
  • fifo_wdata – The sample to write to the FIFO.
  • system_rxen – Enable the receiver.
  • system_rxstop – Stop the receiver.
  • system_filteren – Enable the receiver decimation filter.
  • system_decim – Decimation factor.
  • system_correct_i – i channel DC offset correction for the AQM.
  • system_correct_q – q channel DC offset correction for the AQM.
  • overrun – How many overruns have happened, signal to RFE.
  • adc_idata – The input I data from the ADC.
  • adc_qdata – The input Q data from the ADC.
  • adc_last – Signifies that this is the last sample from the ADC.
Returns:

A synthesizable MyHDL instance.

Direct Digital Synthesizer
dds.dds(resetn, clock, enable, output, frequency_control_word, **kwargs)

Synthesizable DDS using a LUT.

Parameters:
  • resetn – Reset.
  • clock – Driving clock for the DDS.
  • output – The output digital control word.
  • frequency_control_word – The fcw.

Here is the LUT generated for 10-bit resolution, 256 samples, and 80% scale.

(Source code)

./dsp-1.png

Whitebox SoC Peripheral

whitebox.whitebox(resetn, pclk, paddr, psel, penable, pwrite, pwdata, pready, prdata, clearn, clear_enable, dac_clock, dac2x_clock, dac_en, dac_data, adc_idata, adc_qdata, tx_status_led, tx_dmaready, rx_status_led, rx_dmaready, tx_fifo_re, tx_fifo_rdata, tx_fifo_we, tx_fifo_wdata, tx_fifo_full, tx_fifo_afull, tx_fifo_empty, tx_fifo_aempty, tx_fifo_afval, tx_fifo_aeval, tx_fifo_wack, tx_fifo_dvld, tx_fifo_overflow, tx_fifo_underflow, tx_fifo_rdcnt, tx_fifo_wrcnt, rx_fifo_re, rx_fifo_rdata, rx_fifo_we, rx_fifo_wdata, rx_fifo_full, rx_fifo_afull, rx_fifo_empty, rx_fifo_aempty, rx_fifo_afval, rx_fifo_aeval, rx_fifo_wack, rx_fifo_dvld, rx_fifo_overflow, rx_fifo_underflow, rx_fifo_rdcnt, rx_fifo_wrcnt, **kwargs)

The whitebox.

Parameters:
  • resetn – Reset the whole radio front end.
  • clearn – Clear the DSP Chain
  • dac_clock – Clock running at DAC rate
  • dac2x_clock – Clock running at double DAC rate
  • pclk – The system bus clock
  • paddr – The bus assdress
  • psel – The bus slave select
  • penable – The bus slave enable line
  • pwrite – The bus read/write flag
  • pwdata – The bus write data
  • pready – The bus slave ready signal
  • prdata – The bus read data
  • pslverr – The bus slave error flag
  • dac_clock – The DAC clock
  • dac_data – The DAC data
  • dac_en – Enable DAC output
  • status_led – Output pin for whitebox status
  • dmaready – Ready signal to DMA controller
  • txirq – Almost empty interrupt to CPU
  • clear_enable – To reset controller, set this high for reset

Simulating the Whitebox SoC Peripheral

class test_whitebox.WhiteboxSim(bus)

Simulate Whitebox Peripheral and control it with a bus.

Parameters:bus – The APB3Bus to connect the peripheral to.
cosim_dut(cosim_name, fifo_args, whitebox_args)

Get the Cosimulation object.

Parameters:
  • cosim_name – The name of the cosimulation.
  • fifo_args – A dictionary of args to pass to the FIFOs.
  • whitebox_args – A dictionary of args to pass to the Whitebox Peripheral.
Returns:

A myhdl.Cosimulation object.

fft_tx(decim=1)

Compute the FFT of the transmitted signal.

plot_tx(name)

Plot the transmitter’s output.

simulate(stimulus, whitebox, **kwargs)

Acturally run the cosimulation with iverilog.

Parameters:
  • stimulus – A callable that returns the cosim object.
  • whitebox – A whitebox peripheral object.
  • record_tx – Record the passed in number of valid samples.
  • auto_stop – Raise StopSimulation when the correct number of samples have been recorded.
  • sample_rate – Samples per second.

APB3 Bus Functional Model

A bus functional model is a way of using prodecural code to stimulate a HDL module, as though there were a real processor connected to it over the system bus. You can execute read and write operations over the interface in a sequence, and assert the proper function.

This module implements the APB3 bus functional model, which is the common peripheral model for the ARM Cortex series of processors. It’s not the fastest one, but it’s more than capable of saturating the bandwidth of the CMX991 (1MHz).

class apb3_utils.Apb3TimeoutError

Raised when a bus transaction times out.

class apb3_utils.Apb3Bus(*args, **kwargs)
__init__(*args, **kwargs)

Initialize the bus.

Parameters:
  • verbose – Add verbose logging
  • duration – Clock period in ns
  • timeout – Period to wait before timeout in ns
delay(cycles)

Delay the bus a number of cycles.

receive(addr, assert_equals=None)

Receive from slave to master.

Parameters:addr – The address to read from
Returns:Nothing, but sets self.rdata to the received data.
Raises Apb3TimeoutError:
 If slave doesn’t set pready in time
reset()

Reset the bus.

transmit(addr, data)

Transmit from master to slave.

Parameters:
  • addr – The address to write to
  • data – The data to write
Raises Apb3TimeoutError:
 

If slave doesn’t set pready in time

Radio Front End

rfe.rfe(resetn, pclk, paddr, psel, penable, pwrite, pwdata, pready, prdata, clearn, clear_enable, loopen, tx_status_led, tx_dmaready, rx_status_led, rx_dmaready, tx_fifo_we, tx_fifo_wdata, tx_fifo_empty, tx_fifo_full, tx_fifo_afval, tx_fifo_aeval, tx_fifo_afull, tx_fifo_aempty, tx_fifo_wack, tx_fifo_dvld, tx_fifo_overflow, tx_fifo_underflow, tx_fifo_rdcnt, tx_fifo_wrcnt, rx_fifo_re, rx_fifo_rdata, rx_fifo_empty, rx_fifo_full, rx_fifo_afval, rx_fifo_aeval, rx_fifo_afull, rx_fifo_aempty, rx_fifo_wack, rx_fifo_dvld, rx_fifo_overflow, rx_fifo_underflow, rx_fifo_rdcnt, rx_fifo_wrcnt, interp, fcw, tx_correct_i, tx_correct_q, tx_gain_i, tx_gain_q, txen, txstop, ddsen, txfilteren, decim, rx_correct_i, rx_correct_q, rxen, rxstop, rxfilteren, duc_underrun, dac_last, ddc_overrun, adc_last)

The Radio Front End glues together the APB3 interface and the DSP chains. It consists of a synchronizer and a controller.

The synchronizer safely transfers register data between the system and sample clock domains.

The controller responds to read and write requests to the register file on the APB3 bus.

Returns:A MyHDL synthesizable module
rfe.WHITEBOX_REGISTER_FILE = {'WR_STATUS_ADDR': 132, 'WE_DEBUG_ADDR': 32, 'WE_STATUS_ADDR': 4, 'WR_CORRECTION_ADDR': 152, 'WR_AVAILABLE_ADDR': 156, 'WE_INTERP_ADDR': 8, 'WE_THRESHOLD_ADDR': 20, 'WE_AVAILABLE_ADDR': 28, 'WE_GAIN_ADDR': 36, 'WE_RUNS_ADDR': 16, 'WE_FCW_ADDR': 12, 'WR_SAMPLE_ADDR': 128, 'WR_RUNS_ADDR': 144, 'WE_SAMPLE_ADDR': 0, 'WR_DECIM_ADDR': 136, 'WR_DEBUG_ADDR': 160, 'WE_CORRECTION_ADDR': 24, 'WR_THRESHOLD_ADDR': 148}

Peripheral register file addresses.

rfe.WHITEBOX_STATUS_REGISTER = {'WRS_RXEN': 16, 'WRS_AEMPTY': 20, 'WES_FILTEREN': 9, 'WRS_FILTEREN': 17, 'WRS_AFULL': 21, 'WRS_SPACE': 22, 'WES_AEMPTY': 12, 'WS_LOOPEN': 1, 'WS_CLEAR': 0, 'WES_SPACE': 14, 'WRS_RXSTOP': 19, 'WES_DDSEN': 10, 'WES_TXEN': 8, 'WES_TXSTOP': 11, 'WES_AFULL': 13, 'WRS_DATA': 23, 'WES_DATA': 15}

Peripheral status register bit flags.

Linux Device Driver

The device driver is the Kernel level code that interfaces between a user’s application and the RTL. It uses standard Linux interfaces to expose an easy to use character device, and plenty of knobs to control the system in real time.

Platform

The platform exposes the Digital Up Converter and Digital Down Converter streams with an Object Oriented, Kernel friendly interface.

struct whitebox_device

Main bookkeeping for the device, memory addresses, pin locations, the flow graph, and statistics.

struct whitebox_platform_data

Contains all of the constants for pins and DMA channel allocations.

struct whitebox_stats

Statistics can be tracked and exposed on the debugfs interface in the directory /sys/kernel/debug/whitebox/.

Flow Graph

The Digital Singal Processing Flow Graph idiom is used throughout the driver. On write operations, the userspace application writes to the User Source block, which gets the data from userspace and notifies the DSP chain that data is available. The corresponding sink block is called the RF Sink, and it uses DMA to copy the data from kernelspace to the Whitebox Peripheral.

Similarly, for read, the userspace application is the final sink in the DSP chain from the Whitebox Peripheral and through the RF Source. Again, DMA is used to get data from the peripheral to the processor.

Character Device

The character device is registered at /dev/whitebox. It exposes the following Unix file commands:

open()
close()
read()
write()
fsync()
ioctl()

C User Space API

Ultimately, userspace programs drive the system. These can be written in C or C++ and compiled for the ARM Cortex-M3 core. These API’s provide a simple means of manipulating the device driver, and ultimately the entire radio signal chain.

CMX991 Quadrature Radio Transceiver

The CMX991 is the core radio transceiver IC on the Whitebox Bravo board. It is a combination of an Analog Quadrature Modulator, Transmit Image-Reject Up Converter, Analog Quadrature Demodulator, Receiver Mixer, and a Phase Lock Loop.

The Phase Lock Loop, and thus the Intermediate Frequency of the radio system is fixed at 45MHz. A hardware change in parts is required to set a different IF. Refer to the CMX991’s datasheet for possible values for different IFs.

The IC is conrolled via a 4-wire serial interface called the C-BUS which is actually a SPI bus. It has a read/write register file which controls the various features of the transmit and receive chains including variable filters, variable gain amplifiers. There’s also a Signal Level Indicator analog pin.

typedef struct cmx991_t

Register file of the CMX991 chip.

void cmx991_init(cmx991_t* rf)

Initializes the cmx991 chip, including GPIO pins.

void cmx991_destroy(cmx991_t* rf)
void cmx991_copy(cmx991_t* src, cmx991_t* dst)
void cmx991_print_to_file(cmx991_t* rf, FILE* f)
void cmx991_reset(cmx991_t* rf)
int cmx991_pll_enable_m_n(cmx991_t* rf, float fref, int m, int n)
float cmx991_pll_actual_frequency(cmx991_t* rf, float fref)
void cmx991_pll_disable(cmx991_t* rf)
int cmx991_pll_locked(cmx991_t* rf)
Returns:1 if the PLL is locked, 0 if it is not
void cmx991_tx_tune(cmx991_t* rf, float fdes, if_filter_t if_filter, hi_lo_t hi_lo, tx_rf_div_t tx_rf_div, tx_if_div_t tx_if_div, gain_t gain)
void cmx991_rx_tune(cmx991_t* rf, div_t div, mix_out_t mix_out,
if_in_t if_in, iq_filter_t iq_filter, vga_t vga)
void cmx991_rx_calibrate(cmx991_t* rf)

Example

This example sets up the CMX991 to transmit by the frequency set for the ADF4351, by the following formula:

F_{vco} = 2 ( F_{des} - 45 )

Where F_{des} and F_{vco} are both in MHz.

#include <assert.h>
#include <cmx991.h>

void main() {
    cmx991_t cmx991;
    cmx991_init(&cmx991);
    cmx991_resume(&cmx991);
    if (cmx991_pll_enable_m_n(&cmx991, 19.2e6, 192, 1800) < 0) {
        fprintf(stderr, "Error setting the pll\n");
    }
    cmx991_tx_tune(&cmx991, 198.00e6, IF_FILTER_BW_120MHZ, HI_LO_HIGHER,
        TX_RF_DIV_BY_2, TX_IF_DIV_BY_4, GAIN_P6DB);

    assert(cmx991_pll_locked(&cmx991));
}

ADF4351 VCO with Integrated PLL

The ADF4351 is an IC that provides the variable frequency local oscillator. It is a Phase Lock Loop, with an integrated Voltage Controlled Oscillator. These are for the RF transceiver’s transmit image-reject-up-converter and receiver mixer.

The IC is controlled via a 4-wire SPI-like serial interface. It has a write only register file which controls features of the IC. For reading, there is a digital lock detect pin, and a multiplexed analog pin connected to test points in the Phase Lock Loop.

typedef struct adf4351_t

The register file of the ADF4351 IC.

void adf4351_init(adf4351_t* rf)

Initializes the ADF4351 chip, including GPIO pins.

void adf4351_destroy(adf4351_t* rf)
void adf4351_copy(adf4351_t* src, adf4351_t* dst)
void adf4351_print_to_file(adf4351_t* rf, FILE* f)
float adf4351_tune(adf4351_t* rf, float target_freq)
float adf4351_actual_frequency(adf4351_t* rf)

Compute the actual fequency tuned to by the supplied register file.

float adf4351_pll_enable(adf4351_t* rf, float ref_freq, float res_freq, float target_freq);

Update register file to enable the PLL and the VCO.

Examples

Application examples:

Headless CW Transceiver

In this example you’ll setup your Whitebox kit to behave as a CW transceiver. You’ll connect a potentiometer to an ADC to change the frequency, a 4-digit seven segment display over I2C to show the frequency, a speaker via a PWM, and a keyer using a button.

Stream samples captured with GNURadio

Android RIL interfaced GMSK Modem

License

License of each sub directory.

Frequently Asked Questions

Who created the Whitebox?

The Whitebox was conceived, designed, and engineered by Chris Testa, KD2BMH in New York and Los Angles.

Why was it created?

I’ve worked at google & youtube; but I’m an enterpreneur at heart. The startup circuit left me burned out from the Twitter Gold Rush. Being an Eagle Scout, I decided to backpack in the Sierras. With me, my first smartphone had no access from these remote locations.

Tesla’s Wardenclyff Lab inspired a global, universal communication device. A kit that we could all hack on together. To put the power of Internet infrastructure in your pocket.

How long has the project been going?

I first was inspired to make it on December 18, 2011.

Do you have a manifesto for the project by now?

The physical medium that always ends up transporting Internet is radio frequency electronics. As we have painfully come to realize lately, the Internet is not completely distributed as we would like to think. To really distribute the Internet, we need mesh networking.

There have been many attempts at deploying large scale mesh Internet networks, but I’m not talking about the giant Wi-Fi network. One modulation / protocol / frequency band combination is NOT enough for a global communication mesh network solution. If it was, we already could have ditched centralized networks in cities. We need to take our everyday hardware to the next level.

The immediate goal of the project is to distribute software radio technology out to the edeges of the network – the smartphones. When we have an Open Source Hardware femto base station in the smartphone, my hope is we can move to a new plateau for mesh networking.

What’s with the name Whitebox?

Walter Shaw created the Blackbox.

Steve Jobs an Steve Wozniak popularized the Bluebox.

We don’t need another device to mock the telecommunication companies; but we need the power of a femocell base station, free and open. The Whitebox.

How did the project come together?

Once my intention was out there, doors opened up; Software radio comes from a rich community of sharing designs. I’ve been inspired by the USRP architecture, and used some of their parts and code. Ye-Sheng Kuo designed the uSDR framework at the University of Michigan, and his paper Putting the Software Radio on a Low Calorie Diet turned me onto using the SmartFusion.

I am taking some ideas from the HackRF’s amplifier/filter network for the Charlie board.

By mixing these together, getting lots of really helpful reviews and feedback, and putting in many hours learning and designing, here we are!

How did you build the prototype?

I built the prototype at the Wireless and Embedded Systems Lab at the University of Utah, July 2012. It was my first radio design and I did it by using reference boards all strung together. The system was controlled by 2 computers running proprietary software for each reference board. The lab’s $30k oscilloscope / spectrum analyzer made it easy enough to see what was happening.

What were the results of the Utah prototype experiment?

We got a transmit to be received by a cheapy scanner in the lab. I couldn’t build the receiver chain yet since the reference board was out of stock on the ADC that I wanted to use.

How did you assemble the Alpha board?

I built the Alpha @University of Maryland, October 2012. The boards came from PCBExpress and the rest of the parts were sourced via DigiKey. I used a stencil and hot plate to assemble the top of the board, and then soldered the bottom by hand.

What were the results of the Alpha Board?

I got it to boot off of battery power; but had issues with the Phase Lock Loop. I missed a resistor in the Loop Filter (doh!) and as a result, I never got a really solid transmit to come out of the antenna, though I could see modulation happening.

How did you assemble the current Bravo board?

I applied for help from the Tuscon Amateur Packet Radio got funded. With TAPR, Golledge & Emcraft’s help, I got parts donated & I built 12 boards. This time the boards were done on a surface mount assembly line in Southern California.

What are the results for the Bravo Board?

I brought the newly fabricated boards to Hamvention 2013, but the bring-up wasn’t smooth; the clocking subsystem in particular had a number of issues. Luckily they were all resovled, and as of now the second board is a work in progress. The transmit chain is working so lots of projects are ready to be worked on.

How do I use it?

The Whitebox Bravo is a radio development kit and can be used in a few ways depending on your intentions:

  • As a GNURadio Peripheral, where you connect the device to your computer over Ethernet and use the UDP Sink on GNURadio.
  • As an Embedded modem, where you connect the device to your computer over USB and access the Busybox uClinux shell. There’s another UART, SPI, I2C and GPIOs available to hack with, too.

The plan is to embed enough DSP co-processing in the FPGA to support a wide variety of modes in embedded computer mode.

What amateur modes will be supported?

The plan right now is to finish a CORDIC based transmitter and receiver, which will enable many modes to be supported. I personally want to see AM, FM, SSB, and GMSK implemented. This would allow many existing ham C programs to run on top of this headless. This paves the way for digital modes, like APRS, D-STAR, and FreeDV.

How do I get one?

Sign up for the mailing list! I hope to manufacture a version as a development kit, ideally with TAPR members who are tech savvy and willing to help blaze the trail on low power software radio.

Charlie to be released early next year will add an integrated reference clock, PA, LNA, Bandpass filters, and a T/R switch.

What’s the long term road map?

After almost 2 years of hacking, I’m figuring out the most important part of the project, the evoluationary process to get from an embeddable development kit. To a smartphone on the Amateur bands.

The hardware will iterate on a 6 month cycle, evolving with incremental additions and consolidations to achieve the ultimate goal: a HT modeled on a smartphone and a software radio.

Do you have any published videos, papers, or presentations?

Yes please check out my video from the TAPR DCC 2012.

How is this different than the other transceiver SDRs out there?

The closest device to what I’m working on is the USRP E100, and they are quite different in architecture. The E100 has a split ARM processor and Xilinx FPGA IC. The Whitebox uses a SoC with built-in FPGA to reduce IC count as well as provide a very high speed interconnect between the processor and FPGA.

Furthermore, the E100’s FPGA is SRAM based, which has a long startup delay preventing battery saving duty cycling. The Whitebox has an Actel Flash based FPGA. This means that the FPGA portion of the SDR can be put into a low power mode while duty cycling, thus greatly improving battery performance.

Will the radio hardware and drivers work with other embedded systems?

The whole system is built on top of Linux standard libraries and kernel API’s. It should compile for all embedded linux computers, but some work may need to be done to connect your system’s DMA into the mix. That said, to get the most out of the hardware you’ll need the following things:

  • The RF card requires a 10-bit parallel data bus running at 10MHz, so your processor needs to be capable of pushing 100 Mbps. The SoC FPGA does this by interpolating a narrower-bandwidth signal with a Digital Up Converter. Therefore a DSP chip or an FPGA is necessary to reach the required datarates for reasonable efficiency.
  • The HDL Digital Up Converter and Digital Down Converter can be used with any SoC FPGA. Right now it has ARM APB3 bus bindings; but the System Bus is abstracted sufficiently to make it easy to add in Wishbone, faster ARM buses, or whatever else you want. The supplied test harnes and Bus Functional Model can be used to make sure that your particular implemenation meets specifications.
  • Also, RAM’s are sufficiently different between manufacturers that you will have to look at the implementation of the FIFO’s and RAM’s that feed the DSP chain. Again, this is all sufficiently abstracted in the codebase to

Why wasn’t a floating point unit a must have for your design?

I would like a floating point unit, but the only Flash-based FPGA-SoC on the market does not include a FPU. My #1 interest for the project was to push down on power consumption in the RF subsystem, at the cost of other things. Here’s why it’s ultimately okay that there’s no FPU:

  • Without an FPU, the device can still serve as a full fledged GNURadio peripheral, just like any USRP/HackRF/BladeRF.
  • It does have the throughput to do AM/FM/SSB/GMSK, which are all reasonable to implement in fixed point with a Direct Digital Synthesizer. This is everything a good Ham Radio can do.
  • As the last question’s answer states, the codebase is designed to work in a variety of systems. One thing is for sure, these SoC FPGA’s are going to be evoloving targets for some time to come.

How is this different from a $20 USB based SDR?

The $20 USB based SDR’s are receivers only. This is a transceiver and can both transmit and receive.

Eratta

  • 0.2 First release