Thursday, May 25, 2017

An FPGA SDR HF Transceiver, Part 6 -- Schematics, Main Board

In this sixth blog post in my FPGA SDR Transceiver series, I will begin describing the radio's Hardware, specifically (in this post), the Main Board, which contains the FPGA, ADC, DAC, Audio Codec, and support circuitry.

(Part 5 of this series is here: Part 5)

But before I begin, let me again acknowledge Dick Benson, W1QG.  Dick is the father of this design, and although I've made some modifications to the FPGA logic, the underlying FPGA architecture and the vast majority of the Simulink implementation is Dick's.

A note regarding the schematics...

These schematics were drawn using the Lite version of Cadence's Orcad Capture.  This is the free version of the program, and it limits a schematic's number of nets to 75 and the number of parts to 60 (limitations which apply if you want to save the design, which I always do).

Because this radio design has many more nets and parts than the 75/60 limit specified by Cadence, I have broken the overall design into smaller "bite size" schematics, each  independent of the others and each drawn on a single A-size sheet.

But because I've broken up the design into smaller independent pieces, I can not use Capture's Design Rule Checker to check the overall design for design flaws.  Therefore, there is the possibility that errors have crept into the schematics.  So be aware!

A Note on Logic Levels:

A number of different logic families and devices are used in this radio.  I want to ensure that input logic thresholds and output logic drive levels are compatible with each other.  The spreadsheet below summarizes the different specifications:

(Click on image to enlarge)

Notes on Logic Levels:
  1. The Xilinx output voltages aren't high enough to meet the worst-case Arduino Input Level specification, so their levels will need to be shifted if driving Arduino inputs (this level-shifting is done in the Front Panel circuitry).
  2. 2N7000 devices make for simple I/O drivers (e.g. relay drivers, etc.)  But their worst-case Vgs is 3V.  This value is sufficient when driven with output levels from 5V devices (e.g. Arduino), but it is above worst-case Xilinx drive levels.  For this reason I will use bipolar transistors (rather than 2N7000 transistors) for I/O interfacing, when needed.
  3. The 74VCX245 transceiver is the output driver for the LTC DC918C ADC demo board.  It is powered by 2.5 VDC, and its outputs drive the Xilinx FPGA.

Now, let's look at the overall Block Diagram of the FPGA SDR radio...

FPGA SDR Block Diagram:

(Click on image to enlarge)

The Main Board, which is the subject of this blog post, incorporates the FPGA, RF ADC, RF DAC, Audio Codec, Speaker Amplifier, and various miscellaneous support and interface circuits.  It is the processing heart of the radio.

Notes on this block diagram:
  1. The T/R relay, when un-energized (OFF), shorts the receiver's input to ground, protecting the receiver input circuitry when the radio is off.
  2. Various interfaces are shown to external devices.  These interfaces will be explained further, below.
  3. All other circuitry not ON the Main Board will be described in future blog posts.
  4. Schematics and circuitry might change, as determined by design checkout.

Block Diagram, Main Board:

(Click on image to enlarge)

And below are some pictures of the Main Board in its Rev. A implementation:

Top Side

Bottom Side

And now, let's discuss the Main Board's hardware....

Hardware Design, Main Board:

Schematic, ADC Input:

(Click on image to enlarge)

Notes on ADC Input schematic:
  1. This radio uses a Linear Technology Demo Board (the DC918C, version C, incorporating an 80 MHz, 16-bit LTC2206 ADC) for the RF ADC.
  2. The DC918C's output drivers are 74LVX245 Buffers whose VCC is 2.46 volts (via an LT1763 LDO regulator).  Therefore, the maximum high-level voltage from the board to the Xilinx inputs is about 2.26 volts (assuming 100uA worse-case output current -- this voltage falls as output current increases).  With drive current at about 6 mA, the output voltage level is just within Xilinx input threshold specification, with the margin vanishingly small if using the Xilinx LVCMOS33 spec, but better with the LVCMOS25 spec.
  3. 12 VDC power comes in to the radio on this page.  It is converted down to 5V using a simple LM2576 buck regulator, and it is this 5V power buss which sources power for the 3.3V LDO regulators used throughout this design.
For reference, here is the schematic of the LTC DC918C ADC Demo board that Dick and I use in our radios.  The table in the lower left-hand corner specifies component values for the different board versions, including the -C version used by us:

(Click on image to enlarge)

Notes on the DC918C ADC Demo Board:
  1. Dick and I use the DC918C-C version of the DC918C Demo Board (80 MHz LTC2206).  No modifications were made to this board.
  2. Schematics and other design files for the DC918C-C board can be found at the Linear Technology site:
  3. LTC2206 Documentation can be found here:
  4. The DC918C-C's on-board jumpers are set as follows:
    • JP1:  Set to GND, not OVP.
    • JP2 (SENSE):  Set to VDD, not OPEN.
    • JP3 (PGA):  Set to VDD, not GND.
    • JP4 (RAND):  Set to GND, not VDD.
    • JP5 (SHDN):  Set to GND, not VDD.
    • JP6 (DITH):  Set to GND, not VDD.

Schematic, Clock and Misc.:

(Click on image to enlarge)

Notes on Clock and Misc.:
  1. The 80 MHz clock source is a Conner-Winfield TCXO (available from Digikey), part number TB522-080.0M.  (LVCMOS outputs, 3.3Vdc).
  2. This TCXO drives a 74AC04 inverter whose input has been biased to mid-scale DC (1.65V) and which is AC-coupled to the TXCO output.  This inverter, in turn drives other 'AC04 inverters which act as clock drivers, so that the FPGA clock, the ADC clock, and the DAC clock are driven independently.  Thus, there is no clock daisy-chaining.
  3. R11 and R10 slightly roll off the clock edges to reduce ringing.
  4. J1 is the 26-pin header to connect the radio's Front Panel to the Main Board.
  5. Note that because the Front Panel's Arduino's output levels are referenced to the Arduino's 5V power, R5 and R6 drop the Arduino output voltages down to a level more compatible with the Xilinx input levels.
  6. R9 and C14 filter (and scale) the S-Meter PWM signal.
  7. Q1 (open-collector inverter) can be used by the Front Panel (Arduino) to Reset the FPGA.  For example, this is used if the Command Interface gets out of sync.
  8. The oscillator and 74AC04 are powered by the same 3.3V regulator used to power the LTC 918C-C ADC board.  (This regulator also powers the Audio Codec).

Schematic, Xilinx FPGA Board:

(Click on image to enlarge)

Notes on Xilinx FPGA Board:
  1. Dick and I use a Waveshare Core3S500E Xilinx Development Board to simplify the FPGA hardware implementation (no need to solder an FPGA!).
  2. This schematic page defines which pins of the Waveshare Core3S500E board are used in this radio.
  3. I disabled the on-board 50 MHz oscillator by grounding its output-enable pin (pin 1), thus allowing the external 80 MHz clock to be connected to this oscillator's pin 3.  (Note that pin 1 in the Waveshore partial-schematic, below, is simply called "NC".  It actually is the oscillator's OE pin.)
  4. FPGA outputs TX_ATTEN_0 and TX_ATTEN_1 are not yet used in the design.  I've included them in case I would like some way to change TX gain apart from adjusting the TX_Level within the FPGA.  For example, these two bits could select different values for the DAC's full-scale current resistor.
  5. My modifications to this board are described below:
Mods to Waveshare Core3S500E Xilinx Core Board:

(Click on image to enlarge)

Notes on Mods to the Waveshare Core3S500E:
  1. Additional information on the Waveshare Core3S500E board can be found here:

Schematic, DAC Out:

(Click on image to enlarge)

Notes on DAC Output:
  1. The DAC is an AD9744 14-bid ADC
  2. I use a separate 3.3V regulator for the DAC (to minimize possible noise injection into the DAC via its VCC pin if this regulator were also powering other devices).
  3. R1 sets the full-scale output current.  With 2K ohms, full-scale current is 19.2 mA +/-5%, assuming R1 is 1%, given the spec'd range of VREFIO.
  4. IFS = 32*VREFIO/R1 = 32*1.2/R1.

Schematic, Audio Codec:

(Click on image to enlarge)

Notes on Audio Codec:
  1. The Audio Codec is a TI PCM3008 16-bit stereo audio codec.
  2. FPGA outputs driving this Codec are lightly filtered (e.g. R4/C17), to round off edges and thus lower harmonic energy (just in case they might be picked up by the receiver).  Note -- strictly speaking, these 1-pole filters might not be needed in this design, but I got into the habit of using them long ago to help ensure that products I designed met EN61000 EMC standards.  You'll see me use this technique throughout this design.
  3. All inputs (or outputs) to/from the outside world (e.g. Mic In, Line In, and Codec Right Out) are protected with TVS devices and series-resistors at the inputs (the latter to limit transient current).
  4. R1 (33K) sets the voltage gain of U2 (the Speaker Amplifier, a TPA0211 device) to 3.8 (= 125K/33K), which ensures that at the maximum Codec output (1.98 Vpp), the amplifier's drive into a 4-ohm speaker load will be about 1.8 watts RMS -- just below 2 watts, above which amplifier distortion increases significantly, per the TPA0211 datasheet.
  5. J4 may or may not be used in the final design.  If not used, I jumper J5 pin 3 to J5 pin 4.
  6. Note that the PCM3008 does have high-frequency noise above the Nyquist frequency.  This noise is not an issue with the speaker I use -- its frequency response sufficiently attenuates this noise, but it can be an issue if the codec were to drive a hi-fi type system.  In the past I used an AK4554 (pin compatible with the PCM3008) in a product with a higher audio bandwidth and where the PCM3008's output noise was an issue.  (Note that C11, the 510 pF cap, is more for RF shunting (i.e. RF susceptibility prevention) than for high-frequency noise attenuation).

Schematic, Interconnects 1:

(Click on image to enlarge)

Notes on Interconnects 1:
  1. A separate 3.3V regulator is used to power the board's various interface circuitry (on this schematic page and the next 3 "Interconnects" pages).
  2. External I/O: EXT. IN and EXT. OUT usage is yet to-be-defined.  I've included this I/O in the design "just in case" I find a need for it later.
  3. EXT_IN should not be driven higher than 3.3V -- TVS1 will protect it from over-voltage (current-limited by R7).  And in the event of a large, positive-going transient event, D7 should prevent Q2's base-emitter junction from being damaged by an accidentally high reverse-bias Vbe voltage. (And note that EXT. IN will source power (3.3V dropped by Q2's Vbe, D2, and R7, so must ensure that devices attached to this pin will not be harmed by this current when they are powered off).
  4. Controls for an external Receive Attenuator are included.  This attenuator might be a Mini-Circuits DAT-31A-SP+ Digital Attenuator, but it could be something else, as long as I can set attenuation with 3 bits.
  5. I've included an EXT. PA T/R relay-closure output for controlling external power amplifiers such as my SB-220 or AL-811.

Schematic, Interconnects 2:

(Click on image to enlarge)

Notes on Interconnects 2:
  1. This page contains an interface for communicating with my Automatic Antenna Tuner.  Thbis is a bit like putting the cart before the horse, because I don't have a corresponding interface in the Tuner.  Never the less, someday there might be one!  And in anticipation of that event I thought I'd add the circuitry here.
  2. The Interface is pretty much self explanatory.  The signals to the outside world have TVS with current-limiting protection.  And RF bypassing with small caps.
  3. Also, output filtering for three FPGA outputs is shown on this page -- these three filters were originally on the FPGA schematic page, but their inclusion there meant that the schematic exceeded the Orcad Lite's limits on nets/parts, so I moved them here.

Schematic, Interconnects 3:

(Click on image to enlarge)

Notes on Interconnects 3:
  1. I hope to incorporate a Flex-radio SDR-1000 PA module that I have into this radio to give it 100 watts out.  Details are sketchy at the moment -- this PA might be mounted inside this radio's chassis, or it might be mounted in a separate case (so that it could be used for other purposes when not used for this radio.  The important point is -- nothing is yet finalized!  If the PA ends up in a separate case, I will probably use a 25-pin D-connector to connect that chassis to this radio  -- using ribbon cable between the two units with ground between each signal will help reduce EMI susceptibility.  Thus, the reason for J3.
  2. Assuming the SDR-1000 PA performs as I hope it will, I will describe in a later post how it connects to this radio.
  3. Open-collector drivers communicating between this radio and the PA provide a very simple interface between boards with different logic voltages (e.g. the radio's 3.3V digital power versus the PA's 5V digital power) -- no level-shifting required.
  4. ESD protection of the signals driven by the ULN2003A is provided, for positive-going pulses, by TVS2, connected to U1's internal clamping diodes.  Although I've never tried ESD protection this way, it seems to me it should work, and it saves parts, as I don't need a TVS device for each line. Negative-going pulses are clamped by internal clamp diodes (within the ULN2003) between the output pins and ground.

Schematic, Interconnects 4:

(Click on image to enlarge)

Notes on Interconnects 4:
  1. A future project of mine is a 500 watt external PA.  This interface would allow the FPGA SDR radio to set automatically the PA's output filters, etc.  Note this interface's similarity to the SDR-1000 interface, above.
  2. I've added an extra PA Filter-selection signal (nFilt_F3), in case I need to specify separate, rather than combined, filters (e.g. the SDR-1000 PA combines 10 and 12 meters into a common filter, but I might find that I need to separate these filters into two separate filters, rather than one combined filter, in the 500 watt PA).
  3. Due to Orcad Lite's net and part limitations, the SW1 and SW2 are shown on this page rather than on the FPGA page.

Construction Notes:

This board was built on a piece of scrap double-sided FR4 PCB stock.  Very handy, and it provides an excellent ground plane.  I cut holes in it to mount the FPGA board so that I could easily access both sides of this board, as well as to mount an Electroboard prototyping board with the AD9744 DAC soldered to it.  (I also use one of their prototyping boards to mount the PCM3008 codec, but I did not cut a hole into the main board -- instead, this prototyping board is soldered to the top ground plane of the main board).

For mounting small parts, I often used small rectangles I had cut from a larger BusBoard Prototyping Systems prototyping board.  These are great for mounting small SMD devices,  relays, and even ULN2003A DIP ICs.

And at times I also carved pads out of the copper plane on either side of the main PCB (depending upon which side it made sense to mount the components), using a Dremel tool and a 30 degree, 0.1mm Tip Diameter Conical Engravers Cutter V-bit.  You can see an example of this, above, for the 3.3V LDO regulator used to power the interface circuitry.

That's it for this blog post!

Background Notes:

SDR Notes:  Weaver Modulation and Demodulation
SDR Notes:  The Mixer Mathematics of Digital Down Conversion

Posts in this Series:

Part 1: Overview
Part 2: FPGA Modulation and Demodulation
Part 3: Interpolation and Decimation Filters
Part 5: Control Interface, Etc.

Standard Caveat:

I or Dick might have made a mistake in our designs, equations, schematics, models, etc.  If anything looks confusing or wrong to you, please feel free to comment below or send me an email.

Also, I will note:

This design and any associated information is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Thursday, May 4, 2017

An FPGA SDR HF Transceiver, Part 5 -- Control Interface, Etc.

In this fifth blog post in my FPGA SDR Transceiver series, I will conclude the discussion of the FPGA logic with a description of the Control Interface and any other miscellaneous circuitry I have not yet mentioned.

(Part 4 of this series is here: Part 4)

But before I begin, let me again acknowledge Dick Benson, W1QG.  Dick is the father of this design, and although I've made some modifications to the FPGA logic, the underlying architecture and the vast majority of the Simulink implementation is Dick's.

Control Interface:

The Control Interface circuitry (also known as the Serial Interface, and sometimes even called the Command Interface by me) is the serial interface through which the FPGA can be controlled by an external processor, for example a PIC or an Arduino.

Let's call this external processor the Control Processor.

This Control Interface consists of 4 signals (plus a fifth, the Interrupt/Overload signal, which will be discussed later in this blog):
  • Cntrl_Data (Serial Data, Input to FPGA)
  • Serial_Out (Serial Data, Output from FPGA)
  • Cntrl_Clk (Serial Clock, Input to FPGA)
  • RFD (Request for Data, Output from FPGA)
Data is transferred serially between the FPGA and the Control Processor, and the Control Processor is the master of this data transfer, clocking the data synchronously via Cntrl_Clk.

The serial data words are 32 bits long, and so 32 clocks are required to complete a word-transfer to or from the FPGA.

RFD identifies when the FPGA's Serial Interface circuitry is ready to receive (or send) the first bit of a thirty-two bit data word.  It ensures that the FPGA's internal Control Interface circuitry and the external Control Processor remain in sync.  (If they should get out of sync, the Control Processor can force the interface to reset using the FPGA's external nRST signal).

The thirty-two bit Cntrl_Data word sent to the FPGA is divided into two fields:
  • Address Field, 6 bits -- these are the 6 LSB's of the Cntrl_Data word.
  • Data Field, 26 bits -- these are the 26 MSB's of the Cntrl_Data word.

The 32-bit Cntrl_Data word is clocked into the FPGA MSB first.  Therefore, the address bits are the last bits to arrive into the FPGA.

Upon reception of all 32 bits, the 26 bits representing the Data field will be automatically clocked into the FPGA register addressed by the 6-bit Address field.

A thirty-two bit data word, Serial_Out, is also sent from the FPGA to the Control Processor.  This word contains information such as the revision of the FPGA's configuration code, Overload status, and the state of FPGA General-Purpose inputs.

Because there is only one 32-bit Serial_Out data word, then strictly speaking, no address is required.  In fact, this word is clocked out simultaneously whenever any Cntrl_Data word is written to the FPGA.  However, to allow the Control Processor to read the Serial_Out data word without also having to simultaneously write to an existing register, address 63 (0x3F) has been defined to be a dummy address with no writable register.

Therefore, to receive the Serial_Out data word without worry of updating an FPGA register, the Control Processor can simply write to address 63.

Unlike the Cntrl_Data word, which is clocked into the FPGA MSB first, the Serial_Out word is clocked out of the FPGA LSB first.

Both Cntrl_Clk and Cntrl_Data, as inputs into the FPGA, are sampled and synchronized within the FPGA to an internal clock of 10 MHz (80 MHz divided by 8).  Therefore, the Control Processor should ensure that Cntrl_Clk runs at a slower clock rate so that its transitions can be detected by the FPGA.

One final note:  Cntrl_Data is clocked into the FPGA on the rising edge of Cntrl_Clk.  And Serial_Out output data will also change on the rising edge of Cntrl_Clk.

Let's look more closely at the Control Interface circuitry.  It is contained within the "Serial_Interface1" subsystem of the Simulink Model.  I have highlighted it below, in yellow.

(Click on image to enlarge)

Opening up the Serial_Interface1 subsystem, we see:

(Click on image to enlarge)

Let's look more closely at these subsystems.  I will highlight each subsystem in turn...


The Serial_In_Parallel_Out subsystem is highlighted below.

(Click on image to enlarge)

This subsystem receives the incoming Cntrl_Data serial data stream and converts it into parallel format.  It also counts the serial bits as they arrive, and this same counter selects which bit of the 32-bit Serial_Out output word to present (as serial data) at the FPGA's Serial_Out pin.

Opening up Serial_In_Parallel_Out subsystem and the subsystems contained within it:

(Click on image to enlarge)

(Click on image to enlarge)

(Click on image to enlarge)

(Click on image to enlarge)

  • The Valid bit goes high for one clock after all 32 bits of a data word have been clocked into (or out of) the FPGA.  It is used to create the appropriate Write Enable to load the addressed FPGA register (see Address Decode block, below).
  • Par_26 is the 26-bit Data field (in parallel format).
  • Address_6 is the 6-bit address field.
  • Index is a 5-bit value to select which bit of the 32-bit Serial_Out word should be inserted into the output serial data stream.

Address Decode:

The Address Decode block is highlighted in yellow, below.

(Click on image to enlarge)

The Address Decode block decodes the 6 address bits contained within the 32 bit Cntrl_Data word and generates a Write Enable signal for each address.

(Click on image to enlarge)

General-Purpose Outputs:

The General Purpose Outputs block is highlighted in yellow, below.

(Click on image to enlarge)

The register in this block is loaded when its Write Enable, WR_GP_REG, is high.  The 26-bit data field is divided into the following subfields:
  • Bits 11-0:  DOUT_0to11:  Output bits sent to FPGA General Purpose output pins.  They can be used for driving external relays, etc.
  • Bits 16-12:  IRPT_EN0to4:  Interrupt Enables which enable any of five possible interrupt inputs to generate an Interrupt to the Control Processor.
  • Bit 17:  P23_SEL:  Selects if FPGA Pin 23 (an FPGA output, sent to the Control Processor) represents an Interrupt or an Overload condition.
(Click on image to enlarge)

Interrupt Generator:

The Interrupt Generator is highlighted in yellow, below:

(Click on image to enlarge)

The Interrupt Generator can generate an interrupt signal to interrupt the external Control Processor.

There are five possible interrupt sources.  Four are the General Purpose FPGA inputs, and the fifth interrupt goes active upon a transition of any of five Overload conditions.

The five interrupts  can be individually enabled or disabled.  If enabled, any change in a signal's state (from high to low or low to high) will generate an interrupt.

If the Control Processor receives an interrupt, it should read the Interrupt Status via address 63 (data format is described later, below).  At the completion of this read any interrupt that is set will be reset.

(Click on image to enlarge)

(Click on image to enlarge)

Parallel-to-Serial Output:

The Parallel-to-Serial block is highlighted below, in yellow:

(Click on image to enlarge)

This block creates the 32-bit Serial_Out serial data stream that is sent from the FPGA to the Control Processor.

This serial-data stream is sent to the Control Processor whenever the Control Processor writes to an FPGA register (note that a write to address 63 sends this stream to the Control Processor without an actual register being written to).

The 32-bit field of the Serial_Out word has the following subfields:

o  Bits 7-0:  FPGA Code Version (set by the user in the Top Level of the Simulink Model, and it is used to identify the revision of the FPGA code).

o  Bits 11-8:  Four General Purpose Inputs, to allow the Control Processor to monitor external signals via the FPGA.

o  Bits 16-12:  Five Interrupt-Status bits.  Four Interrupts are generated by the four General Purpose Inputs, and the fifth is generated by any of the Overload bits within the FPGA.

o  Bits 19-17:  Not used (set to 0).

o  Bits 24-20:  State of the five Overload bits.  These Overload bits are:
  • Bit 20:  ADC/DAC Overload
  • Bit 21:  Codec Overload
  • Bit 22:  AM Overmod
  • Bit 23:  CIC Overload
  • Bit 24:  DA Overload
(Note that the five Overload bits are automatically cleared after the Control Processor receives the Serial_Out data.)

o  Bits 31-25:  Not used (set to 0)

(Click on image to enlarge)

Pin 23:

The FPGA's pin 23 (called "P23 in the Simulink Model) is an output pin that can be considered a "fifth" signal of the Control Interface.  Its circuitry is highlighted in yellow, below:

(Click on image to enlarge)

This pin can have two meanings:

1.  If the P23_SEL signal from the GP_Outputs block is low, then Pin 23 simply represents an "NOR'ing" of the 5 Overload bits -- that is, if Pin 23 is low, then one (or more) of the five overload bits is high (i.e. in the state of overload).

2.  If P23_SEL is high, then Pin 23 represents the Interrupt signal from the Interrupt_Generator block, and it is used to interrupt the Control Panel's processor.

Clock Divider:

The final Simulink FPGA SDR logic block to discuss is the Clock Divider, highlighted below, in yellow:

(Click on image to enlarge)

The Clock Divider simply divides down the 80 MHz clock to create three clocks that, driving FPGA output pins, can be used to clock external circuity, should the need arise.  These three clocks are:  20 MHz, 10 MHz, and 5 MHz.

(Click on image to enlarge)

Dick originally added this circuitry so that he could create an external phase-locked loop to lock the radio's clock to an external frequency reference.  Per Dick...

Since day 1, I have used the divide-by-sixteen that produces the 5MHz. The 80 MHz Crystal Oscillator that generates the FPGA clock is locked to a 5MHz TCXO using this signal.

The only thing I have not done is put in way to switch out the internal 5 MHz TCXO with an external 5 or 10 MHz Frequency Reference input from the back panel.

The TCXO is so good  (a few Hz +/- at 28 MHz) the external reference is just not justified.   

That's it for the FPGA description!  In my next post, I will begin discussing the hardware design of the radio.

Background Notes:

SDR Notes:  Weaver Modulation and Demodulation
SDR Notes:  The Mixer Mathematics of Digital Down Conversion

Posts in this Series:

Part 1: Overview
Part 2: FPGA Modulation and Demodulation
Part 3: Interpolation and Decimation Filters
Part 5: Control Interface, Etc.

Standard Caveat:

I or Dick might have made a mistake in our designs, equations, schematics, models, etc.  If anything looks confusing or wrong to you, please feel free to comment below or send me an email.

Also, I will note:

This design and any associated information is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Tuesday, April 18, 2017

An FPGA SDR HF Transceiver, Part 4 -- TX and RX Audio Paths

In this fourth blog post in my FPGA SDR Transceiver series, I will discuss the FPGA implementation of the Receiver and Transmitter Audio paths.

(Part 3 of this series is here:  Part 3)

But before I begin, let me again acknowledge Dick Benson, W1QG.  Dick is the father of this design, and although I've made some modifications to the FPGA logic, the underlying architecture and the vast majority of the Simulink implementation is Dick's.

By the way, Dick has already finished his version of the radio, which he built into an old HP chassis.  I will describe it in a future blog post, but for now, here's a teaser...

In the last image, above, the box at the left side of the chassis contains the receiver's Broadcast-Band Reject filter, the Anti-Aliasing filter, and the RF preamp(s).   The PIC controller is on the board that spans the chassis from left-to-right, just behind the front panel at the bottom of the image.  And the FPGA board with ADC, DAC, and Audio Codec (and miscellaneous circuitry) is the copper-clad board taking up the majority of the chassis area.

Please note that Dick's transmitter's PA (and an Antenna Tuner) are in a separate box.  The PA amplifies the transceiver's 10 dBm output (10 milliwatts) up to 100 watts, and is based upon Motorola's app notes AN-762 and AN-779.  The tuner is of Dick's own design:

OK -- back to the FPGA!

Audio and Mode Control:

Let's return to the Simulink Xilinx model for the radio.  In this blog post I will focus on the Audio and Mode Control subsystem, highlighted below with the yellow circle.

(Click on image to enlarge)

If we open this block, we see blocks for audio processing (the "Audio_Processor" and the "AGC_and_More" blocks), and blocks for control ("Reg_A," "Reg_B," and "T_R_Delay").  Let's first look at these control blocks, which I've highlighted, below:

(Click on image to enlarge)

Audio Control:

Registers A and B are 26-bit wide write-only registers to control some of the transceiver's functions.  Their subsystem models are shown below and should be self-explanatory.

(Click on image to enlarge)

The T_R_Delay subsystem is shown below.

(Click on image to enlarge)

Notes on the T_R_Delay subsystem:
  1. This subsystem runs at the Audio sample rate of 9,765.625 KHz (period = 0.1024 ms).
  2. The Tx/Rx output mimics the input Tx_Rx_bit, but it is delayed by 70 clocks (about 7.2 ms) to compensate for the clearing of the Information Filter when going from Rx to Tx (This clearing takes about 13 ms, so about 6 ms will be clipped from the leading edge of the first CW character.  Without this delay 13 ms would be clipped, instead.)
  3. The Tx_Rx_Delayed output goes high when the Tx_Rx_bit input goes high (i.e. upon entering Tx mode), but, after Tx_Rx_bit goes low, it holds its high state for a length of time determined by its Delay input (Hold Time = Delay * 0.1024 ms).  This signal is meant to be used for controlling, for example, the antenna relay.
  4. The Tx_Rx_D40 output is simply Tx_Rx_Delayed with its leading (high-going) edge delayed by 40 clocks.  This allows the RX EQ filter (whose depth is 64 samples) to (mostly) clear out before being used for sidetone.  Otherwise, an audio artifact can be heard on the first CW leading edge when Tx_Rx_Delayed goes high.
  5. Ideally, Tx/Rx, when high, should be bracketed within Tx_Rx_Delayed, so that, for example, the antenna relay switches to the transmitter before actual RF is generated, and switches back to the receiver after transmission has ended.  Therefore, if Tx/Rx has a delay of 7 ms, then Delay should be set to be at least 7 ms (i.e. at least a count of 70).

Now let's look at the Receive Audio path...

Receive Audio Path:

Let's return to the Audio_and_Mode_Control subsystem:

(Click on image to enlarge)

Receive audio comes into this subsystem from the demodulator, passes through the "AGC_and_More" subsystem, and then is serialized and sent to the external Audio Codec.

I've highlighted this receive audio path, below:

(Click on image to enlarge)

Let's open up the AGC_and_More subsystem and see what is going on inside it.

AGC_and_More Subsystem:

This subsystem contains the AGC circuitry (used for both Tx and Rx gain control), an Rx Audio Filter (which I might from time to time also call the RX EQ filter), a multiplier block, "Time_Share_MPY" for controlling receive volume and Tx power, and various blocks for audio sources and destinations.

(Click on image to enlarge)

Let's first look more closely at the "AGC Xilinx" block, which I've highlighted, below:

(Click on image to enlarge)

AGC_and_More -- AGC Xilinx Subsystem:

The "AGC Xilinx" block contains the AGC subsystem.  This AGC subsystem is used both for Tx (to bring up mic audio) and for Rx.

As Dick, W1QG points out:

   The "traditional" analog radio AGC has always (to my knowledge) been a feedBACK scheme.  
   The analog RX has lots of gain before the detector and this gain is reduced as a function of the signal strength. 
   The sensing of signal strength can occur before, after, or in the detector stage.  

   This radio uses a feedFORWARD  scheme where the gain before the detector is fixed, and in fact, limited to essentially 1 (selectable 0,6,12,18dB gains at the CIC Filter output not withstanding).   
   The signal after the detector is increased  as a function of the inverse of signal strength.  

For background, I would recommend reading Phil Harmon's excellent paper on AGC (written for the FlexRadio SDR-1000), which can be found here.  The design in this FPGA radio doesn't follow all of Phil's recommendations, but his paper will give you a good background for understanding our approach.

(Click on image to enlarge)

But before getting into the details of  the AGC functions, let's first take a quick look at the control register, "AGC_Reg", in the lower left-hand corner.

AGC_and_More -- AGC Xilinx -- AGC_Reg:

(Click on image to enlarge)

This register's function should be fairly obvious, but I do want to point out the multiplexer at the top.  This circuit ensures that the Max_Rx_Gain field can never be set (by software) to zero.  If it were to be zero, then there is the possibility that the AGC's divider could divide by zero.  This circuit prevents that possibility from occurring.

Now let's look at some of the subsystems within the "AGC Xilinx" block.  The first is "Xabs", and its output is the absolute value of its input.

AGC_and_More -- AGC Xilinx -- Xabs Subsystem:

The next subsystem within the AGC Xilinx block is the "Attack Filter" subsystem.

AGC_and_More -- AGC Xilinx -- Attack Filter Subsystem:

The "Attack Filter" subsystem is simply a one-pole low-pass IIR filter that has two different attack values.  Dick's 15/16 coefficient equates to an attack time of 1.6 ms, but I found that this attack time was too slow in high QRN environments -- it did not track the peaks of the QRN noise and I would hear the QRN pulses, after AGC attempted to limit them, banging up against the full-scale values of the data path (+/- 1).

So I fixed this problem by adding an optional attack time of 0 ms -- in other words, the second low-pass filter option for attack-time low pass filter.  This option has the benefit of ensuring that AGC'ed audio, be it Tx or Rx audio, never can be driven hard enough to "saturate" at the data path's maximum limits.  And its drawback is that random noise spikes can cause the AGC to artificially depress receive audio, and it can take some time to recover if the AGC decay rate is set to be a long period.

But so far this drawback has not been much of an issue, whereas, for me, the benefit of zero-attack is obvious.  (and if it does become an issue, I have some thoughts as to how it might be mitigated).

Notes on the values of CMult1 and CMult3...

Before I discuss CMult1 and CMult3, I should explain that the goal of the AGC is to amplify an audio sample (whose value, in the case of receiver audio, will be typically very small) up to, but not to exceed, +/-1 -- the Full Scale limits of the signals.

This amplification is done by a division process in which the received sample is divided by a number, in this design called the divisor (for obvious reasons).  If this divisor equals the received sample's absolute value, the output will be 1 (or -1, if the sample is negative).  And in which case we have just snugged up to, but not exceeded, our limits.

And if the divisor is larger than the sample's absolute value, then the result will be less than +/- 1, which is OK, too, depending upon our goals.

But if the divisor is smaller than the sample's absolute value, we will have problems, because then the result will exceed +/- 1 and the signal will be clipped, resulting in distortion.

So one main goal of the AGC is to try to ensure that the divisor is never smaller than the absolute value of the sample it is being divided into.

My AGC circuit simplifies this process, because I simply pass the absolute value of the received sample along as the divisor.  So the sample will, worst case, be divided by itself and the result can never exceed +/- 1, in theory.

But that's in theory.  Because I'm always a bit worried about errors within fixed point arithmetic and other unknown unknowns, I thought it better to limit the divider's maximum output to be slightly less than +/- 1, which is why the divisor's gain, set by CMult3, is 1.1875 (1 + 3/16), rather than being equal to 1.  So the maximum range of the divider's output should be at most +/- 0.84, rather than +/- 1.0.  In other words, about 1.5 dB below Full Scale.

Dick's CMult1 also has a gain factor.  If there were no gain, the value of his b0 coefficient should be 1/16 (that is, equal to 1 - 15/16, or 0.0625).  But b0 is actually 0.1875 -- three times the value of 1/16.

In other words, Dick has essentially followed his attack filter with a gain-stage of 3.  

In large part, I believe this additional gain is to compensate for the delayed (low-pass filtered) response of the attack filter and its inability to track fast-attack signals (e.g. noise), thus allowing them to drive into saturation unless compensated with additional divisor gain, in this case, 3.

(For more on single pole IIR filters, please see the section, Single-Pole Low-Pass IIR Digital Filter notes..., at the end of this blog post.)

AGC_and_More -- AGC Xilinx -- Leaky Peak Detector Subsystem:

The Attack Filter then feeds into a Leaky Peak Detector, shown below:

(Click on image to enlarge)

The Leaky Peak Detector's goal is to detect signal peaks from the Attack Filter and then to hold these peaks, using their values to set the AGC gain.

But we do not want the peak detector to be a perfect detector -- we want the level to slowly decay so that, as band conditions change (e.g. QSB) or signals change, the AGC circuit will adjust to new, perhaps lower, signal peaks.

Currently, the Leaky Peak Detector has four rates of decay:
  1. Slow:  840 ms.
  2. Fast:   210 ms.
  3. Very Fast:  26 ms.
  4. Ultra Fast:  6.5 ms.
(The latter two Dick uses for TX audio.  I haven't experimented with them much, myself, to know if they help, or not.)

Note that the Peak Detector is designed to maintain the last peak value of the receive audio during transmit, and the last peak value of the transmit audio during receive.

AGC_and_More -- AGC Xilinx -- Hang Time Subsystem:

The AGC circuit also includes a Hang Time provision:

(Click on image to enlarge)

If a new peak is detected by the Leaky Peak Detector, rather than have the Leaky Peak Detector immediately begin to leak, the peak value can be held for some amount of time (the Hang Time), and then, after the Hang Time period has elapsed, allowed to decay.

Other Hang Time notes:
  • Hang time can be set to be between 0 and 1.6 seconds, adjustable in 52.4 ms increments.
  • Currently, a single value of Hang Time is stored in the hardware, and if a user desires it to be different for Tx versus Rx, it must be loaded by the Host (i.e. UI) Processor (Arduino in my case, PIC in Dick's) with the appropriate value when transitioning between Tx and Rx. 
  • The Hang Time counter is automatically reset when transitioning between Rx and Tx states.

AGC_and_More -- AGC Xilinx -- Sample Delay Subsystem:

There is delay through the AGC divisor's path (consisting of the attack filter and the leaky-peak detector), and the Sample Delay subsystem attempts to apply a corresponding delay to the dividend's path so that the dividend (the audio sample to be amplified) arrives at the divider with an appropriate divisor.

(Click on image to enlarge)

In the case of my version of the AGC (no attack filter), this is straightforward -- the divisor has a delay of 2 clocks through the attack and leaky-peak detector, and so I delay the audio-sample-to-be-divided by the same amount: 2 clocks.

Compensation is  a bit more complicated for Dick's AGC with the 16 sample time constant.  He adds a delay of 18 clocks, but is this enough?  For example, given a step-function input, it is quite possible that the divisor has not risen to its full value, given the 16-sample time constant, after 18 clocks (although this will depend in some part upon the step-function's rise time, itself, when it arrives at the AGC block as audio -- the leading edge will have some slope and not be a zero rise-time).

So, if the Divisor is smaller than the Dividend when they both arrive at the AGC divider, there is the very real possibility of driving the signal into the rails.  And this is probably one of the reasons why Dick has a gain of 3 after his Attack Filter.

AGC_and_More -- AGC Xilinx -- Sequential Divide Subsystem:

And now we come to the AGC divider circuit!

Within the block above is a circuit which limits the maximum gain that can be applied to a signal.  Let's look at it, first.

(Click on image to enlarge)

This circuit allows the user to limit the amount of gain that AGC can apply, and it does this by limiting how small the Divisor can become (the smaller the divisor is, the more gain the AGC applies).  This level is set by the user via the Max_RX_Gain word.

So if the divisor value entering this block is less than Max_RX_Gain, this circuit forces the divisor value to be limited to Max_RX_Gain, thus limiting the maximum gain in RX mode.

Note that in the AGC_Reg subsystem there is a circuit which forces the Max_RX_Gain value to be at least 1.  I added this to prevent the Divisor from having an unintentional "Divide-by-zero" situation.

But this circuit is not strictly needed -- the Host Processor's software, which must load this value, could guarantee that it is never zero.  And I'm not convinced that there is a divide-by-zero issue with Dick's "Sequential Divide" circuit (to be discussed in just a paragraph or two).

(I will also point out that there is an adder circuit after this limiter, but a value of 0 is added, so it has no effect.  Dick's AGC circuitry doesn't have the same limiter, but he does have this adder which he uses to add a value of 2^-16 to the Divisor -- that is, he adds 0x0001 to the Divisor).

Let's take a quick look at the theory behind the AGC Divider...

Sequential Divide Theory:

As Dick mentioned earlier in this post, the AGC system is a FeedForward system in which the "signal after the detector is increased  as a function of the inverse of signal strength." 

This increase is performed by a division function, where the signal (the Dividend) is divided by a Divisor to create an amplified signal.  The lower the signal strength, the smaller will be the value of the Divisor.  If the Dividend and Divisor are equal in amplitude, we want the output to be +1 (the maximum for a positive signal) or -1 (for a negative signal).  Both of these values represent the AGC output after maximum amplification is applied to the Dividend, prior to clipping distortion (and actually, +1 is clipping by just a hair's width).

Below is the Simulink model of the Divider circuit.  But note, there's a multiplier in the heart of it!

(Click on image to enlarge)

There are no hardware dividers in the Xilinx part, so Dick instead uses a multiplier to multiply the Dividend by the inverse of the Divisor, so that the Multiplier's output is Dividend * (1/Divisor).  (The latter term is the "1/x" output from the "Shift and Lookup" subsystem).

You are probably thinking, "but isn't calculating 1/x just as difficult as dividing?"

Dick takes a clever approach to this calculation, recognizing that we do not need a precise number for 1/x -- after all, this is an AGC circuit and we just need to ensure that the output value from the AGC circuit is close to one if the Dividend and the Divisor are equal.  The result does not need to always be exactly 1.

So, why not find 1/x with a simple ROM lookup table?

So, being a binary system with the Divisor's data format being an Unsigned 16 bit word whose decimal point is at bit 16 (i.e. the maximum value of this word is ever so slightly less than +1.0), Dick does the following:

1.  Shift the Divisor left until the MSB of the shifted divisor is 1.  (And remember the amount the Divisor had to be shifted -- this number will be used, later)

2.  Recognizing that the value of the shifted divisor now lies between 0.5 and 0.999... (note that, with the MSB set, the value represented at the low end by 0x8000 is 0.5 and and at the high end  by 0xFFFF is 0.999...).  The reciprocal of this value must lie between 1,000015... and 2.0.

3. But there are 32,768 values between 0xFFFF and 0x8000.  Rather than calculate the exact reciprocal of each one, Dick instead takes an approximation approach -- he uses the top seven bits of the shifted divisor (converted to be a 7-bit number per a mapping described further below) to look up a reciprocal in a ROM.  And the ROM's output is a 10 bit word (in Unsigned format, with its decimal point at bit 9).

And thus, the 32,768 possible reciprocal values are mapped into a set of 128 10-bit words, whose values lie between 1 and 2.

4.  This approximated-value of the shifted-divisor's reciprocal is then multiplied with the Dividend.

5.  But, because the Divisor had been shifted left until its MSB is 1, we cannot simply multiply this approximated value of the shifted-divisor's reciprocal by the Dividend and call it a day.  We must compensate for the left-shift of the Divisor by performing another shift-left operation on the multiplier's result.

For example, if the Divisor, prior to shifting, were 0.5, no compensating final left-shift would be required and we would simply multiply the Dividend by 2.0 (the  latter being the ROM's output value when the Divisor is 0.5 (i.e. an address of 0)), giving us a result of 1.0.

But suppose the Divisor and Dividend were both 0.125?  Note that the reciprocal of 0.125 is 8.0.

In this case, we would first shift the Divisor left by one twice so that its shifted-value is now 0.5, giving us a ROM output of 2,0, which we would then multiply the Dividend by to give us a result of 0.25 (i.e. 2*0.125).

But because the actual reciprocal of 0.125 is 8, not 2, we are not finished.  We still need to multiply by a factor of 4.  And so the Sequential Divide circuit shifts the multiplier's result left by 2, thus completing the operation.

Let's look at the circuitry which does this...

...Sequential Divide, Shift and Lookup:

Here is the circuit that determines the Divisor's reciprocal and also determines the amount that the multiplier's result will need to be "left-shifted" by:

(Click on image to enlarge)

Several notes:

1.  This block also includes circuitry to generate an S-Meter value as indication of the received-signal in dB format.  I will describe this S-meter circuitry later, below, but not now.

2.  The circuitry which shifts the 16-bit Divisor left until an MSB of 1 is detected is on the left side of the diagram, and includes the "1st 1 Detect" block.

3,  When an MSB of 1 is detected, the amount of "shift-left" is captured in the "Capture Shift" block.

4.  The "Capture Fraction and Compute Address" calculates an address for the ROM lookup table (which is also shown in the diagram above, by the way).  It does this as follows:

o  Subtract 0.5 from the shifted-divisor (whose shifted-value lies between 0.5 and 0.9999).  The result will lie between 0 and 0.49999.

o  Multiply this value by 2 (shift left by 1).  The result is now between 0 and 0.9999.

o  Convert this value into a 7 bit number, now between 0 and 0.99... and then reinterpret this seven-bit number to be an unsigned integer between 0 and 127.

o  Use this value to address the ROM.

Therefore, address 0's data word will be 2.0, the reciprocal of 0.5, and address 127's data word will be 1.0039 (the approximated reciprocal of 0.99609375).

Below is the Matlab code that Dick uses to create the ROM lookup table of divisor-reciprocals:

    % AGC related

Note that this table will often actually result in  the AGC multiplier's output will be slightly greater than 1.0.

For example, if the divisor is, say, slightly greater than 0.5, then, because of the quantization of divisor values into a table of 128 values, its reciprocal will also be 2.  But this means that whenever the Dividend is equal to the Divisor, the multiplier's output will be slightly greater than 1.0.

In fact, if we were to look at all 65,536 possible values where the Dividend and Divisor are equal, for the vast majority of these pairs the multiplier's output would be greater than +1.0 (when the Dividend is positive), or less than -1.0 (when the Dividend is negative).  (It turns out, the multiplier's result can be up to  +/-1.008).

This isn't a big deal, but it does mean that the Multiplier's output must be defined to have 2 bits to the left of the decimal point, rather than 1 (so that we can represent positive values of 1.0 to 1.008).  And so, in this case, the Multiplier's output is defined as Unsigned, 18 bits wide, decimal point at bit 16.

I'll point out that the table could be modified slightly to keep the Multiplier's maximum positive value less than 1 and its minimum negative value equal to 1 by changing Dick's Matlab code line of




But in my case this isn't really necessary (unless I wanted to clean up the design), given that there is a bit of gain applied to the Divisor at the output of the AGC Attack filter.  Thus, with my AGC, the Dividend and the Divisor can never be equal.  This isn't quite true for Dick's circuit, and, in fact, the Dividend could sometimes be greater than the Divisor (in which case the output would clip).

To finish up this discussion, the Sequence Divide's subsystem blocks (within the "Shift and Lookup" block) that I discuss above are shown below:

...the "1st 1 Detect:"

(Click on image to enlarge)

...the "Capture Shift:"

(Click on image to enlarge)

The "Capture Fraction and Compute Address:"

(Click on image to enlarge)

And finally, here's the block that performs the right-shift of the Multiplier's output to give us our final result:

...Sequential Divide, Variable Shifter:

The circuit below performs the final shift-left of the multiplier's output.  The amount of shift is equal to the amount of shift that was required to originally shift the divisor left to make its MSB equal to 1.

(Click on image to enlarge)

And as I mentioned above, the "Shift and Lookup" block also contains the Rx S-meter circuit, which I've highlighted, below.  Let's take a look at this...

RX S-Meter:

(Click on image to enlarge)

Initially, Dick's receive S-meter circuitry simply used the captured shift to generate a PWM value.  Because of this, the original S-meter only moved in 6-dB steps, which was rather clunky.  He has added better resolution, but the 6-dB step circuit still forms the basis of this circuit.  So let's discuss it first.

When the Rx signal is small, the captured-shift will be a large value, potentially 15 at its maximum (although note: in simulations I do not see this value becoming larger than 14, except under some initialization conditions).  Dick multiplies this captured-shift value by 16 and then adds 15 to it, so that for the weakest received signals the resulting value is 255, and for the absolute strongest signals the value is 0.

Let me repeat that:  weak signals will result in a large value, while strong signals will result in a small value.

This value is compared against an 8-bit up-counter that is counting at 32*Faudio (so it will take a period equivalent to 8 audio samples to complete its count).

To drive the meter, strong signals will require the PWM signal to be high for a longer fraction of the total PWM period than will weak signals.  To achieve this Dick compares the captured-shift value (after it has been modified by multiplying it by 16 and then adding 15), and compares this to the 8-bit up-counter's count.  If this counter's count is greater than the modified captured-shift value, the PWM signal goes high to drive the meter.

The modified captured-shift value for a weak signal is a large number (because its divisor had to be left-shifted quite a few times), and thus, because this value is large, the comparator's output will only go high for a brief period of time (when the up-counter is greater than the modified captured-shift value), and thus there will not be much meter deflection.

As I mentioned above, having the meter only move in 6 dB steps is a bit clunky, because the meter will only move to one of sixteen positions.  In other words, it's grossly quantized.

So Dick improved the S-meter's resolution by interpolating between the 6 dB steps.

Interpolation is accomplished by subtracting from the modified captured-shift value a second value that is equal to the ROM address divided by 8.  (It isn't clear to me if this second value maintains Dick's log format, or not, but it really isn't too important if one is using the meter simply to get an idea of signal strength, rather than as a calibrated instrument).

Because the ROM address spans 0 to 127, when this address is divided by 8 the resulting value spans from 0 to 15, where 0 corresponds to a weak signal and 15 corresponds to a strong signal.

This ROM address-based value is then subtracted from our modified captured-shift value.  The subtraction (rather than addition) maintains the relationship that, for weak signals, the final result that drives the PWM comparator should be large, and as signals become stronger this final value becomes smaller.

At a higher level subsystem in the Simulink Model this S-meter PWM signal then passes through a routing circuit, which I will discuss next...

AGC_and_More -- AGC Xilinx -- S-Meter:

In RX mode, this circuit simply routes the Receive S-Meter PWM signal (generated by the Sequential Divide circuit) onward, where it will eventually terminate at an external analog S-Meter.  (The circuitry also generates a PWM signal to drive the meter during Transmit, but I will discuss this later, below, when I describe the Tx Audio path).

(Click on image to enlarge)

We've now completed the discussion of the AGC Xilinx block.  Let's return to the AGC and More subsystem and look at the remaining RX audio path...

(Click on image to enlarge)

We've just discussed the AGC Xilinx block.  The next subsystem on the RX Audio path is the RX_Audio_to_Filters_Select subsystem.

AGC_and_More -- RX_Audio_to_Filters_Select Subsystem:

(Click on image to enlarge)

This subsystem simply routes the appropriate audio signal to the RX Audio Filter.  In RX mode, this audio signal is simply the RX audio from the AGC Xilinx subsystem.

In TX mode, the audio is either set to zero (for voice modes), or it is the DDS Low Frequency Oscillator, which is used for sidetone during CW operation -- I use the RX Audio Filter to filter the sidetone (which at this point is hard-keyed) to remove key clicks on its rising and falling edges when I listen to it through the speaker.

(Click on image to enlarge)

Tx_Rx tracks the keying, and so in CW mode it is used to key the sidetone on and off (via the mux) to the RX Audio Filter.

AGC_and_More -- RX_Audio_Filter Subsystem:

The next block in the RX audio path is the RX_Audio_Filter.

(Click on image to enlarge)

You are probably wondering, why is there an audio filter?  Didn't we already filter the signal in the Weaver Modulator?

Yes we did, but there is an audio artifact in this radio due, I believe, to the fact that we maintain a 16-bit data path rather than growing it as the signal passes through successive stages of decimation.

Specifically, there is a low-level wide-band noise floor in the background when listening to signals.  This noise floor sounds like a low level "hiss" outside the Information Filter's passband.

You can see the noise in the image, below (which is a audio spectrum capture from the audio codec's output).  The receiver is tuned to 40 meters.  Random HF noise (QRN) is passing through the Information Filter's passband (set to 2.8 KHz -- and there is also a very slight carrier visible (and audible at the time), too), so you can see the HF pedestal created by the received noise below, in the range from 200 to 3000 Hz.

The broadband noise is visible from 3 KHz to about 4.5 KHz,  Yes, it is down about 25 dB from the received noise passed through the Information Filter, but it is, to me, very noticeable and thus very annoying.

Dick added the RX Audio Filter to remove this artifact.  Here's the same spectrum, but with the RX Audio filter set to a 2.8 KHz bandwidth:

Audibly, I can assure your that this filter is a significant improvement!

This filter can be loaded with various audio filter bandwidths that mimic the Information Filter's bandwidth, and thus removing the wide-band noise when its presence is objectionable.

Here's the RX_Audio_Filter subsystem:

(Click on image to enlarge)

There are two filters, selected the "bank" bit -- one filter is the RX filter, the other is the TX filter (used when transmitting to remove keyclicks from the local sidetone audio).

Each "bank" is a 64-tap FIR filter.  The TX filter is selected automatically when in TX mode.

Looking "under the mask" of the "n-Tap MAC FIR Filter":

(Click on image to enlarge)

The output of the RX Audio Filter goes to the RX_Audio_Select subsystem...

AGC_and_More -- Rx_Audio_Select Subsystem:

(Click on image to enlarge)

This block selects which audio signal is sent to the speaker:
  1. Receive Audio from RX Audio Filter.
  2. Sidetone (CW Mode, and also from the RX Audio Filter)
  3. TX Audio (to locally monitor how the TX audio chain, including the Information Filter, affects the TX audio).
(Click on image to enlarge)

Looking at this circuit again, I can see that there are one or two things that are unnecessary and the logic could be simplified.  For example, at one time I had a separate Sidetone filter (rather than using the RX Audio Filter), and so I had added an input for that filter, but it is no longer needed.

AGC_and_More -- Time_Share_MPY Subsystem:

The final stage of audio processing is the Time_Shar_MPY subsystem.

(Click on image to enlarge)

This multiplier serves two purposes in the Receive path -- it is used as the receive audio "volume" control, and it also controls the Sidetone gain during TX.

It also controls the audio level of the TX Monitor audio, if this feature is enabled by the operator.  (And which is the reason why a "1" now feeds the AND gate in the highlighted circuit, above, rather than the CW_nSSB signal).

Here's the Time-Share Multiplier.  Note that it is used both for RX (as I've just discussed) as well as for TX (when it is used to control TX power out).

(Click on image to enlarge)

RX Codec Interface:

The last stage of the audio path is the conversion of the parallel-format digital audio data into a serial stream for the Audio Codec, and this occurs in the two blocks I've highlighted in yellow, below:

(Click on image to enlarge)

The codec's right channel is always defined to be TX Monitor Audio, but it does not go to the transceiver's speaker.  It's available for monitoring, should I so choose.

The transceiver's speaker receives its audio via the codec's left channel.  This audio includes Receive Audio, Transmit CW sidetone, and, when enabled, Transmit Monitor Audio.

The block below simply concatenates the left and right channels' 16-bit parallel data word and then converts them into a serial data stream.

(Click on image to enlarge)

And the block below creates the timing signals required by the Codec.

(Click on image to enlarge)

And that's it for the Receiver Audio Path!

Transmitter Audio Path:

The TX audio path is very similar to the RX audio path, except the TX audio comes from the Audio Codec, not the demodulator, and it goes to the modulator, not the audio codec.

(Click on image to enlarge)

Let's examine the first subsystem block in the TX audio path: the Audio Processor Subsystem, highlighted below.

(Click on image to enlarge)


Here's the Audio Processor block:

(Click on image to enlarge)

Audio comes in from the Audio Codec as stereo audio in a serial data stream.  The left channel is the Mic input, and the right channel can be, say, a Line input.

This serial data is transformed into left and right channel 16-bit parallel data by the Serial_to_Parallel subsystem:

Audio_Processor -- Serial_to_Parallel:

(Click on image to enlarge)

Audio_Processor -- TX Equalizer:

The next block in the TX audio chain is the TX Equalizer.  Dick added it because he wasn't satisfied with how his EV-664 mic sounded through the radio -- he thought his Astatic D-104 sounded much better, and so he decided to add an EQ stage to "perk up" his EV-664 microphone's response.

(This radio has a useful feature that allows the operator to monitor their TX audio and thus discover how a mic sounds and how for example, the information filter affects audio quality.)

The TX Equalizer is shown, below:

(Click on image to enlarge)

This is a 64-tap FIR filter, with coefficients downloadable via the radio's Command Interface from the user's control panel.

The 26-bit words from the Command Interface also contain a bit to select Line or Mic input, and another bit to Activate (or bypass) this equalizer.

Here is a look "under the mask" of the n-tap FIR filter that is the heart of this equalizer.

(Click on image to enlarge)

Audio_Processor -- OVL_Detect:

There is an Overload Detector which monitors the input to, and the output from, the TX Equalizer filter.  If either are greater than half of full scale, an overload (potential overload, actually), is flagged.

(Click on image to enlarge)


Next the TX Audio goes to the AGC block.  The purpose here is to take a mic signal that might be low and, with the AGC circuit, amplify it up so that it approaches the full-scale range of the system.

You've seen this block already, above, in the receiver description.  I've highlighted it, below:

(Click on image to enlarge)

Within this block we see the usual suspects:

(Click on image to enlarge)

And here is the path that the TX audio takes:

(Click on image to enlarge)

AGC Xilinx:

Let's take a quick look at the AGC block, highlighted below:

(Click on image to enlarge)

Below is the TX audio path through this AGC block:

(Click on image to enlarge)

I've also highlighted (in orange) a control signal which limits the maximum gain that can be applied to the mic signal (to prevent, say, background noise picked up by the mic from being amplified up to an annoying level).

AGC Xilinx -- Sequential Divide:

The highlighted circuit below controls how much gain can be applied to the TX Audio signal.

(Click on image to enlarge)

The smaller the divisor is, the greater the gain will be.  The circuit above limits this gain by checking the divisor's value and, if the divisor is less than a value set by the operator, the divisor is limited to that value set by the operator.  That is, this circuit essentially acts as a floor to the divisor value, preventing it from going below a  level selected by the user.

There are four levels selectable by the user, each representing a change of gain by a power of 2 (6 dB) from the next.

AGC Xilinx --S-Meter:

Here's the S-Meter circuit that creates a PWM signal to drive an external analog meter.

(Click on image to enlarge)

In CW mode the PWM represents the TX Level (which controls the output TX power) of the CW signal.

In SSB/AM modes the PWM represents the level of the TX audio, and it does this by using the AGC divisor value (which, in the case of my AGC, is essentially just a leaky-peak detected copy of the audio coming into the AGC block).

If this audio is too high (i.e. greater than 0.5, identified by either bet 24 or 23 of the divisor being high), the PWM signal will be its highest value (31) and the external analog meter should point to its highest level, e.g. full scale.

Divisor values (and thus audio levels) below this peak value will be directly translated into one of 31 PWM levels using the next lower 5 bits of the divisor (bits 18-22)

TX Audio Select:

TX Audio next moves from the AGC block to the TX Audio Select subsystem block, highlighted below.

(Click on image to enlarge)

This block selects the audio source.

(Click on image to enlarge)

Specifically, the sources are:

  • Zero (in Receive mode)
  • Tx_Audio (SSB Transmit)
  • CW_Oscillator (CW Transmit)
  • TX_Audio summed with TX_Carrier_Level (AM Transmit)
  • Two-Tone Generator (to test linearity of TX chain).

Note that TX_Carrier_Level adds a DC offset to the TX_Audio signal for AM modulation, and its value should be at least equal to the peak value of the TX_Audio.  (If it is under then the AM signal will be over-modulated).  In other words, this circuit generates the (DC + x(t)) term in the equation, below:

AM Modulation Equation

In the circuit, TX_Carrier_Level represents the equation's DC term, and Tx_Audio represents the equation's x(t) term.

And here's the Two-Tone source:

(Click on image to enlarge)


The Time_Share_MPY block controls the level of the TX signal sent to the modulator and thence onward to the DAC.  I've highlighted it, below:

(Click on image to enlarge)

And here is its circuitry:

(Click on image to enlarge)


The final subsystem we will look at is the Over_Modulation_Detector subsystem, shown below:

(Click on image to enlarge)

This circuit is used in AM mode to detect if AM is being over-modulated.

(Click on image to enlarge)

Its operation is straightforward.  In AM mode, the Transmit Audio signal from the TX Audio Select block represents the  (DC + x(t)) term in the AM Modulation equation, listed here earlier, above.  This term should never go below zero and should always be positive.  If the Transmit Audio signal, in AM mode, does hit zero, then it is assumed to be over-modulating.

That's the end of this post!  Notes follow...

Single-Pole Low-Pass IIR Digital Filter notes...

Realization of single-pole IIR filters:

I was curious about Dick's one-pole low-pass IIR filter (it wasn't in a form with which I was familiar), so I decided to dig a bit deeper...

Dick's low-pass IIR filter can be thought of as a variant of the Direct Form II digital filter, but in which the y[n-1] tap is used as an output, rather than y[n], as I've shown in the drawing, below (lower right-hand corner filter).

(Click on image to enlarge)

Many readers might be familiar with the common Direct Form I realization of a single-pole low-pass filter that uses the y[n-1] output, shown in the lower left-hand corner of the drawing above.  This was the form that I was familiar with and had seen (and used) many times over the years, and Dick's use of the Direct Form II realization initially confused me, until I realized it was equivalent to the Direct Form I.

Another point to note: with this type of single-pole low-pass filter, a1 and b0 often have the following relationship:

 a1 = 1 - b0

and where a1 defines the decay-rate (or time-constant), which is the number of samples it takes to reach 36.8% of the original value according to the relationship:

n (samples) = -1/ln(a1)

So, for example, an a1 value of 15/16 will have a time-constant of about 16 samples.  Therefore, if a unit step-function were applied as an input signal, it would take about 16 samples for the output to reach 63.2% of its final value.

Derivation of Time Constant:

I was curious as to the derivation of the Time-Constant formula: n = -1/ln(a1).

With some poking around the internet (e.g. here) and some pencil pushing, I came up with the following derivation:

Continuing on, let's now solve for n...

The Relationship of a1 and b0:

Another question I had:  must a1 always equal 1 - b0 ?

The short answer is: no, a1 does not need to be equal to 1 - b0.  If this equation is not satisfied it just means that gain (or attenuation) has been applied to the filter, but the filter's time-constant and low-pass response cut-off frequency remain determined by a1.

For example, let's take a low pass filter whose coefficient are:  a1 = 15/16 and b0 = 1/16 (thus satisfying the equation a1 = 1 - b0)...

Now, suppose that prior to this low-pass filter we inserted a gain-stage whose gain was, say, 32:

Being a linear system, this additional gain of 32 can be combined with the  b0 of 1/16 for an overall gain of 2.  In other words, the circuit now appears to be an IIR filter with a b0 of 2.

Our equation a1 = 1 - b0 is no longer satisfied, but clearly the filter's low-pass filtering characteristic should not have changed, because applying gain in the signal chain prior to (or after) the filter should not affect the filter's filtering.

The same principle applies to Dick's use of the Direct Form II realization, in which the b0 coefficient is at the output, rather than the input, of the filter.  Changing b0 essentially just adds gain (or attenuation) to the signal path.

Time Constants:

Some useful Time Constants for this radio (whose audio sample-clock period is 0.1024 ms), calculated using n = -1/ln(a1):

(Some internet digital IIR references:)

Background Notes:

SDR Notes:  Weaver Modulation and Demodulation
SDR Notes:  The Mixer Mathematics of Digital Down Conversion

Posts in this Series:

Part 1: Overview
Part 2: FPGA Modulation and Demodulation
Part 3: Interpolation and Decimation Filters
Part 5: Control Interface, Etc.


A Discussion on the Automatic Gain Control (AGC) Requirements of the SDR-1000, Phil Harmon, VK6APH.  (Available for download from the FlexRadio website: here).

Standard Caveat:

I or Dick might have made a mistake in our designs, equations, schematics, models, etc.  If anything looks confusing or wrong to you, please feel free to comment below or send me an email.

Also, I will note:

This design and any associated information is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.