mirror of
https://github.com/greatscottgadgets/hackrf.git
synced 2026-03-15 11:50:25 +01:00
Merge pull request #1632 from greatscottgadgets/praline
Add support for HackRF Pro (code name: Praline)
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -159,7 +159,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['macos', 'ubuntu', 'windows']
|
||||
board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O']
|
||||
board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE']
|
||||
cmake: ['3.10.0', 'latest']
|
||||
exclude:
|
||||
- os: 'windows'
|
||||
|
||||
BIN
docs/images/block-diagram-pro.png
Normal file
BIN
docs/images/block-diagram-pro.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
5348
docs/images/block-diagram-pro.svg
Normal file
5348
docs/images/block-diagram-pro.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 312 KiB |
BIN
docs/images/hackrf-pro-preliminary-photo.jpg
Normal file
BIN
docs/images/hackrf-pro-preliminary-photo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/images/rad1o_8.jpg
Normal file
BIN
docs/images/rad1o_8.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
@@ -1,8 +1,8 @@
|
||||
Enclosure Options
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The commercial version of HackRF One from Great Scott Gadgets ships with an injection molded plastic enclosure, but it is designed to fit two optional enclosures:
|
||||
Commercial versions of both HackRF Pro and HackRF One from Great Scott Gadgets ship with an injection molded plastic enclosure, but are also designed to fit two optional enclosures:
|
||||
|
||||
* Hammond 1455J1201: HackRF One fits this extruded aluminum enclosure and other similar models from Hammond Manufacturing. In order to use the enclosure's end plates, you will have to drill them. An end plate template can be found in the HackRF One KiCad layout.
|
||||
* Hammond 1455J1201: Both HackRF Pro and HackRF One fit this extruded aluminum enclosure and other similar models from Hammond Manufacturing. In order to use the enclosure's end plates, you will have to drill them. An end plate template can be found in the relevant HackRF KiCad layout.
|
||||
|
||||
* Acrylic sandwich: You can also use a laser cut acrylic enclosure with HackRF One. This is a good option for access to the expansion headers. A design can be found in the HackRF One hardware directory. Use any laser cutting service or purchase from a `reseller <https://greatscottgadgets.com/hackrf/acrylic-case/>`__.
|
||||
* Acrylic sandwich: You can also use a laser cut acrylic enclosure with either HackRF Pro or HackRF One. This is a good option for access to the expansion headers. A design can be found in the HackRF hardware directory. Use any laser cutting service or purchase from a `reseller <https://greatscottgadgets.com/hackrf/acrylic-case/>`__.
|
||||
|
||||
@@ -1,55 +1,7 @@
|
||||
Expansion Interface
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The HackRF One expansion interface consists of headers P9, P20, P22, and P28. These four headers are installed on the commercial HackRF One from Great Scott Gadgets.
|
||||
|
||||
|
||||
|
||||
P9 Baseband
|
||||
^^^^^^^^^^^
|
||||
|
||||
A direct analog interface to the high speed dual ADC and dual DAC.
|
||||
|
||||
.. list-table ::
|
||||
:header-rows: 1
|
||||
:widths: 1 1
|
||||
|
||||
* - Pin
|
||||
- Function
|
||||
* - 1
|
||||
- GND
|
||||
* - 2
|
||||
- GND
|
||||
* - 3
|
||||
- GND
|
||||
* - 4
|
||||
- RXBBQ-
|
||||
* - 5
|
||||
- RXBBI-
|
||||
* - 6
|
||||
- RXBBQ+
|
||||
* - 7
|
||||
- RXBBI+
|
||||
* - 8
|
||||
- GND
|
||||
* - 9
|
||||
- GND
|
||||
* - 10
|
||||
- TXBBI-
|
||||
* - 11
|
||||
- TXBBQ+
|
||||
* - 12
|
||||
- TXBBI+
|
||||
* - 13
|
||||
- TXBBQ-
|
||||
* - 14
|
||||
- GND
|
||||
* - 15
|
||||
- GND
|
||||
* - 16
|
||||
- GND
|
||||
|
||||
|
||||
The common HackRF expansion interface consists of headers P20, P22, and P28. These headers are present on both HackRF Pro and HackRF One, and support hardware add-ons including PortaPack and Opera Cake.
|
||||
|
||||
P20 GPIO
|
||||
^^^^^^^^
|
||||
@@ -65,15 +17,15 @@ Providing access to GPIO, ADC, RTC, and power.
|
||||
* - 1
|
||||
- VBAT
|
||||
* - 2
|
||||
- RTC_ALARM
|
||||
- RTC_ALARM (One) / PB_5 (Pro)
|
||||
* - 3
|
||||
- VCC
|
||||
- VCC (One) / 3V3AUX (Pro)
|
||||
* - 4
|
||||
- WAKEUP
|
||||
* - 5
|
||||
- GPIO3_8
|
||||
* - 6
|
||||
- GPIO3_0
|
||||
- GPIO3_0 (One) / GPIO3_9 (Pro)
|
||||
* - 7
|
||||
- GPIO3_10
|
||||
* - 8
|
||||
@@ -121,7 +73,7 @@ I2S, SPI, I2C, UART, GPIO, and clocks.
|
||||
* - Pin
|
||||
- Function
|
||||
* - 1
|
||||
- CLKOUT
|
||||
- CLKOUT (One) / P2 (Pro)
|
||||
* - 2
|
||||
- CLKIN
|
||||
* - 3
|
||||
@@ -133,21 +85,21 @@ I2S, SPI, I2C, UART, GPIO, and clocks.
|
||||
* - 6
|
||||
- I2C1_SDA
|
||||
* - 7
|
||||
- SPIFI_MISO
|
||||
- SPIFI_MISO (One) / PB_1 (Pro)
|
||||
* - 8
|
||||
- SPIFI_SCK
|
||||
- SPIFI_SCK (One) / PB_3 (Pro)
|
||||
* - 9
|
||||
- SPIFI_MOSI
|
||||
- SPIFI_MOSI (One) / PA_4 (Pro)
|
||||
* - 10
|
||||
- GND
|
||||
* - 11
|
||||
- VCC
|
||||
- VCC (One) / 3V3AUX (Pro)
|
||||
* - 12
|
||||
- I2S0_RX_SCK
|
||||
- I2S0_RX_SCK (One) / PA_3 (Pro)
|
||||
* - 13
|
||||
- I2S_RX_SDA
|
||||
- I2S0_RX_SDA (One) / I2S0_TX_SDA (Pro)
|
||||
* - 14
|
||||
- I2S0_RX_MCLK
|
||||
- I2S0_RX_MCLK (One) / PB_0 (Pro)
|
||||
* - 15
|
||||
- I2S0_RX_WS
|
||||
* - 16
|
||||
@@ -169,7 +121,7 @@ I2S, SPI, I2C, UART, GPIO, and clocks.
|
||||
* - 24
|
||||
- SDA
|
||||
* - 25
|
||||
- CLK6
|
||||
- CLK6 (One) / AUX_CLK2 (Pro)
|
||||
* - 26
|
||||
- SCL
|
||||
|
||||
@@ -187,7 +139,7 @@ SDIO, GPIO, clocks, and CPLD.
|
||||
* - Pin
|
||||
- Function
|
||||
* - 1
|
||||
- VCC
|
||||
- VCC (One) / 3V3AUX (Pro)
|
||||
* - 2
|
||||
- GND
|
||||
* - 3
|
||||
@@ -211,25 +163,69 @@ SDIO, GPIO, clocks, and CPLD.
|
||||
* - 12
|
||||
- GND
|
||||
* - 13
|
||||
- GCK2
|
||||
- GCK2 (One) / P5_6 (Pro)
|
||||
* - 14
|
||||
- GCK1
|
||||
- GCK1 (One) / P5_7 (Pro)
|
||||
* - 15
|
||||
- B1AUX14 (trigger output)
|
||||
- Trigger out: B1AUX14 (One) / TRIGGER.OUT (Pro)
|
||||
* - 16
|
||||
- B1AUX13 (trigger input)
|
||||
- Trigger in: B1AUX13 (One) / TRIGGER.IN (Pro)
|
||||
* - 17
|
||||
- CPLD_TCK
|
||||
* - 18
|
||||
- BANK2F3M2
|
||||
- BANK2F3M2 (One) / PE_0 (Pro)
|
||||
* - 19
|
||||
- CPLD_TDI
|
||||
- CPLD_TDI (One) / I2S0_RX_SDA (Pro)
|
||||
* - 20
|
||||
- BANK2F3M6
|
||||
- BANK2F3M6 (One) / P9_1 (Pro)
|
||||
* - 21
|
||||
- BANK2F3M12
|
||||
- BANK2F3M12 (One) / P5_3 (Pro)
|
||||
* - 22
|
||||
- BANK2F3M4
|
||||
- BANK2F3M4 (One) / P1_7 (Pro)
|
||||
|
||||
P9 Baseband (HackRF One)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A direct analog interface to the high speed dual ADC and dual DAC.
|
||||
|
||||
.. list-table ::
|
||||
:header-rows: 1
|
||||
:widths: 1 1
|
||||
|
||||
* - Pin
|
||||
- Function
|
||||
* - 1
|
||||
- GND
|
||||
* - 2
|
||||
- GND
|
||||
* - 3
|
||||
- GND
|
||||
* - 4
|
||||
- RXBBQ-
|
||||
* - 5
|
||||
- RXBBI-
|
||||
* - 6
|
||||
- RXBBQ+
|
||||
* - 7
|
||||
- RXBBI+
|
||||
* - 8
|
||||
- GND
|
||||
* - 9
|
||||
- GND
|
||||
* - 10
|
||||
- TXBBI-
|
||||
* - 11
|
||||
- TXBBQ+
|
||||
* - 12
|
||||
- TXBBI+
|
||||
* - 13
|
||||
- TXBBQ-
|
||||
* - 14
|
||||
- GND
|
||||
* - 15
|
||||
- GND
|
||||
* - 16
|
||||
- GND
|
||||
|
||||
Additional unpopulated headers and test points are available for test and development, but they may be incompatible with some enclosure or expansion options.
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
===========================================
|
||||
External Clock Interface (CLKIN and CLKOUT)
|
||||
===========================================
|
||||
========================
|
||||
External Clock Interface
|
||||
========================
|
||||
|
||||
.. _external_clock_interface:
|
||||
|
||||
HackRF Pro
|
||||
~~~~~~~~~~
|
||||
|
||||
HackRF Pro has two configurable SMA ports, P1 and P2. By default, P1 is configured as CLKIN and P2 as CLKOUT. The default behaviour of these signals is as described for HackRF One below.
|
||||
|
||||
HackRF One
|
||||
~~~~~~~~~~
|
||||
|
||||
HackRF One produces a 10 MHz clock signal on CLKOUT. The signal is a 3.3 V, 10 MHz square wave intended for a high impedance load.
|
||||
|
||||
The CLKIN port on HackRF One is a high impedance input that expects 3.3 V square wave at 10 MHz. Do not exceed 3.3 V or drop below 0 V on this input. Do not connect a clock signal at a frequency other than 10 MHz (unless you modify the firmware to support this). You may directly connect the CLKOUT port of one HackRF One to the CLKIN port of another HackRF One.
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
Connectors
|
||||
==========
|
||||
|
||||
The connectors on HackRF One are SMA.
|
||||
The connectors on both HackRF Pro and HackRF One are SMA.
|
||||
|
||||
**Note:** SMA connectors and RP-SMA connectors are visually very similar. If you connect an RP-SMA antenna to HackRF One, it will seem to connect snugly but won't function at all because neither the male nor female side has a center pin. RP-SMA connectors are most common on 2.4 GHz antennas and are popular on Wi-Fi equipment. Adapters are available.
|
||||
**Note:** SMA connectors and RP-SMA connectors are visually very similar. If you connect an RP-SMA antenna to a HackRF, it will seem to connect snugly but won't function at all because neither the male nor female side has a center pin. RP-SMA connectors are most common on 2.4 GHz antennas and are popular on Wi-Fi equipment. Adapters are available.
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
HackRF One
|
||||
================================================
|
||||
|
||||
.. _hackrf_one:
|
||||
|
||||
.. image:: ../images/HackRF-One-fd0-0009.jpeg
|
||||
:alt: HackRF One
|
||||
|
||||
HackRF One is the current hardware platform for the HackRF project. It is a Software Defined Radio peripheral capable of transmission or reception of radio signals from 1 MHz to 6 GHz. Designed to enable test and development of modern and next generation radio technologies, HackRF One is an open source hardware platform that can be used as a USB peripheral or programmed for stand-alone operation.
|
||||
HackRF One was the first production hardware platform for the HackRF project. It is a Software Defined Radio peripheral capable of transmission or reception of radio signals from 1 MHz to 6 GHz. Designed to enable test and development of modern and next generation radio technologies, HackRF One is an open source hardware platform that can be used as a USB peripheral or programmed for stand-alone operation.
|
||||
|
||||
| `Product page <https://greatscottgadgets.com/hackrf/one/>`_
|
||||
| `Where to buy <https://greatscottgadgets.com/hackrf/one/#purchasing>`_
|
||||
|
||||
50
docs/source/hackrf_pro.rst
Normal file
50
docs/source/hackrf_pro.rst
Normal file
@@ -0,0 +1,50 @@
|
||||
================================================
|
||||
HackRF Pro
|
||||
================================================
|
||||
|
||||
.. image:: ../images/hackrf-pro-preliminary-photo.jpg
|
||||
:alt: HackRF Pro
|
||||
|
||||
HackRF Pro is the current hardware platform for the HackRF project. It is a Software Defined Radio peripheral capable of transmission or reception of radio signals from 100 kHz to 6 GHz. HackRF Pro is designed to be backwards compatible with software and hardware developed for use with
|
||||
:ref:`HackRF One <hackrf_one>`,
|
||||
whilst introducing many new features and improvements.
|
||||
|
||||
| `Product page <https://greatscottgadgets.com/hackrf/pro/>`_
|
||||
| `Where to buy <https://greatscottgadgets.com/hackrf/pro/#purchasing>`_
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
|
||||
* 100 kHz to 6 GHz operating frequency
|
||||
* Tunable from 0 Hz to 7.1 GHz
|
||||
* Half-duplex transceiver
|
||||
* Up to 20 million samples per second
|
||||
* 8-bit quadrature samples (8-bit I and 8-bit Q)
|
||||
* Compatible with GNU Radio, SDR#, and more
|
||||
* Software-configurable RX and TX gain and baseband filter
|
||||
* Software-controlled RF port power (50 mA at 3.3 V)
|
||||
* SMA RF connector
|
||||
* SMA clock input and output for synchronization and triggering
|
||||
* Convenient buttons for programming
|
||||
* Internal pin headers for expansion
|
||||
* High-Speed USB 2.0 with Type-C connector
|
||||
* USB-powered
|
||||
* Open source hardware
|
||||
|
||||
Compared to HackRF One, HackRF Pro introduces a host of new and updated features, including:
|
||||
|
||||
* Wider operating frequency range
|
||||
* Improved RF performance with flatter frequency response
|
||||
* Modern USB Type-C connector
|
||||
* Built-in TCXO crystal oscillator for superior timing stability
|
||||
* Logic upgrade from a CPLD to a power-efficient FPGA
|
||||
* Elimination of the DC spike
|
||||
* Extended-precision mode with 16-bit samples for low sample rates (typical ENOB: 9-11)
|
||||
* Half-precision mode with 4-bit samples at up to 40 Msps
|
||||
* More RAM and flash memory for custom firmware
|
||||
* Installed shielding around the radio section
|
||||
* Trigger input and output accessible through clock connectors
|
||||
* Cutout in the PCB provides space for future add-ons
|
||||
* Improved power management
|
||||
* Enhanced RF port protection
|
||||
* Facility to hardware-disable transmit mode
|
||||
@@ -2,10 +2,12 @@
|
||||
Buttons
|
||||
=======
|
||||
|
||||
This information is applicable to both HackRF Pro and HackRF One.
|
||||
|
||||
The **RESET button** resets the microcontroller. This is a reboot that should result in a USB re-enumeration.
|
||||
|
||||
The **DFU button** invokes a USB DFU bootloader located in the microcontroller's ROM. This bootloader makes it possible to unbrick a HackRF One with damaged firmware because the ROM cannot be overwritten.
|
||||
The **DFU button** invokes a USB DFU bootloader located in the microcontroller's ROM. This bootloader makes it possible to unbrick a HackRF with damaged firmware because the ROM cannot be overwritten.
|
||||
|
||||
The DFU button only invokes the bootloader during reset. This means that it can be used for other functions by custom firmware.
|
||||
|
||||
To invoke DFU mode: Press and hold the DFU button. While holding the DFU button, reset the HackRF One either by pressing and releasing the RESET button or by powering on the HackRF One. Release the DFU button.
|
||||
To invoke DFU mode: Press and hold the DFU button. While holding the DFU button, reset the HackRF either by pressing and releasing the RESET button or by powering on the HackRF. Release the DFU button.
|
||||
|
||||
@@ -2,20 +2,50 @@
|
||||
Hardware Components
|
||||
================================================
|
||||
|
||||
Major parts used in HackRF One:
|
||||
Block Diagrams
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
HackRF Pro Block Diagram
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: ../images/block-diagram-pro.png
|
||||
:align: center
|
||||
|
||||
HackRF One r1-r8 Block Diagram
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: ../images/block-diagram.png
|
||||
:align: center
|
||||
|
||||
HackRF One r9 Block Diagram
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: ../images/block-diagram-r9.png
|
||||
:align: center
|
||||
|
||||
Key Components
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Major parts used in HackRF:
|
||||
|
||||
* `MAX2831 2.3 to 2.6 GHz transceiver <https://www.analog.com/en/products/max2831.html>`__
|
||||
* Used on HackRF Pro.
|
||||
* `Datasheet <https://www.analog.com/media/en/technical-documentation/data-sheets/MAX2831-MAX2832.pdf>`__
|
||||
|
||||
* `MAX2837 2.3 to 2.7 GHz transceiver <https://www.analog.com/en/products/max2837.html>`__
|
||||
* Used on HackRF One (except revision r9), Jawbreaker and rad1o.
|
||||
* `Datasheet <https://www.analog.com/media/en/technical-documentation/data-sheets/max2837.pdf>`__
|
||||
* `MAX2839 2.3 to 2.7 GHz transceiver <https://www.analog.com/en/products/max2839.html>`__
|
||||
* Substitution for MAX2837, used on HackRF One revision r9.
|
||||
* `Datasheet <https://www.analog.com/media/en/technical-documentation/data-sheets/max2839.pdf>`__
|
||||
* substitution for MAX2837.
|
||||
* `MAX5864 ADC/DAC <https://www.analog.com/en/products/max5864.html>`__
|
||||
* `Datasheet <https://www.analog.com/media/en/technical-documentation/data-sheets/MAX5864.pdf>`__
|
||||
* `Si5351 clock generator <http://www.silabs.com/products/clocksoscillators/clock-generator/Pages/lvcmos-clocks-5-outputs.aspx>`__
|
||||
* `AN619: Manually Generating an Si5351 Register Map <http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf>`__
|
||||
* `Datasheet <http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf>`__ - see AN619 for the complete register map.
|
||||
* `Other Documentation <http://www.silabs.com/products/clocksoscillators/clock-generators-and-buffers/Pages/clock+vcxo.aspx>`__ - includes application notes, user guides, and white papers.
|
||||
* CoolRunner-II CPLD
|
||||
* `ice40 UltraPlus FPGA <https://www.latticesemi.com/en/Products/FPGAandCPLD/iCE40UltraPlus>`__ (HackRF Pro)
|
||||
* CoolRunner-II CPLD (all other platforms)
|
||||
* `LPC43xx ARM Cortex-M4 microcontroller <http://www.nxp.com/products/microcontrollers-and-processors/arm-processors/lpc-arm-cortex-m-mcus/lpc-dual-core-cortex-m0-m4f/lpc4300:MC_1403790133078>`__
|
||||
* `User Manual <http://www.nxp.com/documents/user_manual/UM10503.pdf>`__
|
||||
* `Datasheet <http://www.nxp.com/documents/data_sheet/LPC4350_30_20_10.pdf>`__
|
||||
@@ -25,22 +55,5 @@ Major parts used in HackRF One:
|
||||
* `RFFC5072 mixer/synthesizer <http://www.rfmd.com/store/rffc5072-1.html>`__
|
||||
* `Datasheet <http://www.rfmd.com/CS/Documents/RFFC5071_2DS.pdf>`__
|
||||
* `Other Documentation <http://www.rfmd.com/store/rffc5072-1.html>`__ ; click "Technical Documents" - includes programming guides and application notes.
|
||||
* `W25Q80BV 8M-bit Flash <https://www.winbond.com/resource-files/w25q80bv%20revk%2020151203.pdf>`__
|
||||
|
||||
|
||||
Block Diagrams
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
HackRF One r1-r8 Block Diagram
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: ../images/block-diagram.png
|
||||
:align: center
|
||||
|
||||
|
|
||||
|
||||
HackRF One r9 Block Diagram
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: ../images/block-diagram-r9.png
|
||||
:align: center
|
||||
* `W25Q32 32M-bit Flash <https://www.winbond.com/resource-files/W25Q32JV%20RevJ%2012242024%20Plus.pdf>`__ (HackRF Pro)
|
||||
* `W25Q80BV 8M-bit Flash <https://www.winbond.com/resource-files/w25q80bv%20revk%2020151203.pdf>`__ (all other platforms)
|
||||
|
||||
@@ -4,26 +4,52 @@
|
||||
Hardware Triggering
|
||||
===================
|
||||
|
||||
HackRF One transmit and receive operations can be synchronized with another HackRF One or with other external equipment by using the trigger input and output on pin header P28. Triggering provides time synchronization with error of less than one sample period.
|
||||
HackRF transmit and receive operations can be synchronized with another HackRF or with other external equipment by using the trigger input and output. Triggering provides time synchronization with error of less than one sample period.
|
||||
|
||||
HackRF Pro has two configurable SMA ports, P1 and P2, which can be set up to provide both clock synchronization and triggering.
|
||||
|
||||
HackRF One has CLKIN and CLKOUT ports for clock synchronization, but hardware triggering requires opening the case to access the P28 header.
|
||||
|
||||
Clock Synchronization
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When triggering one HackRF One from another, it is often desirable to first ensure that the two devices share a common frequency reference. This has an added benefit of grounding the HackRFs to each other, eliminating one of the wires required for triggering. See :ref:`External Clock Interface <external_clock_interface>` for instructions.
|
||||
When triggering one HackRF from another, it is often desirable to first ensure that the two devices share a common frequency reference. This has an added benefit of grounding the HackRFs to each other, eliminating one of the wires required for triggering. See :ref:`External Clock Interface <external_clock_interface>` for instructions.
|
||||
|
||||
Either HackRF One may serve as the clock source for the other regardless of which is providing the trigger output.
|
||||
Either HackRF may serve as the clock source for the other regardless of which is providing the trigger output.
|
||||
|
||||
Usage
|
||||
~~~~~
|
||||
|
||||
Use ``hackrf_info`` to discover the serial numbers of both HackRFs. Using the serial number of the HackRF to be triggered, use ``hackrf_transfer -H`` to set up a triggered operation. For example:
|
||||
|
||||
* ``hackrf_transfer -H -d <serial number> -a 0 -l 32 -g 32 -r rx1.cs8``
|
||||
|
||||
The command will print "Waiting for trigger..." until a trigger signal is detected on the device's trigger input.
|
||||
|
||||
In another terminal, use the serial number of the triggering HackRF One to initiate an operation to take place at the same time as the triggered operation. For example:
|
||||
|
||||
* ``hackrf_transfer -d <serial number> -a 0 -l 32 -g 32 -r rx2.cs8``
|
||||
|
||||
Note that no special argument is required to activate the trigger output.
|
||||
|
||||
Both ``hackrf_transfer`` commands will start sampling RF signals at the same time, accurate to less than one sample period.
|
||||
|
||||
|
||||
Requirements
|
||||
~~~~~~~~~~~~
|
||||
Additional Devices
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Multiple HackRFs may be triggered by a single HackRF. Ensure that all the devices share a common ground and then connect one device's trigger output to the trigger inputs of the other devices (with jumpers connected via a breadboard, for example).
|
||||
|
||||
Equipment other than a HackRF may be connected to a HackRF's trigger input or output. The trigger signal is a 3.3 V pulse that triggers on the rising edge.
|
||||
|
||||
HackRF One Triggering Requirements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To connect two HackRF Ones for triggering you will need:
|
||||
|
||||
* a male-to-male jumper wire for 0.1" pin headers
|
||||
* an SMA cable for clock synchronization or a second jumper wire
|
||||
|
||||
|
||||
.. _open_your_hackrf_one:
|
||||
|
||||
Open Your HackRF One
|
||||
@@ -49,32 +75,6 @@ First ensure that the two devices share a common ground. This may be accomplishe
|
||||
Next use a jumper wire to connect P28 pin 15 (trigger output) on one HackRF One to P28 pin 16 (trigger input) on the other HackRF One.
|
||||
|
||||
|
||||
Usage
|
||||
~~~~~
|
||||
|
||||
Use ``hackrf_info`` to discover the serial numbers of both HackRF Ones. Using the serial number of the HackRF One to be triggered, use ``hackrf_transfer -H`` to set up a triggered operation. For example:
|
||||
|
||||
* ``hackrf_transfer -H -d <serial number> -a 0 -l 32 -g 32 -r rx1.cs8``
|
||||
|
||||
The command will print "Waiting for trigger..." until a trigger signal is detected on the device's trigger input.
|
||||
|
||||
In another terminal, use the serial number of the triggering HackRF One to initiate an operation to take place at the same time as the triggered operation. For example:
|
||||
|
||||
* ``hackrf_transfer -d <serial number> -a 0 -l 32 -g 32 -r rx2.cs8``
|
||||
|
||||
Note that no special argument is required to activate the trigger output.
|
||||
|
||||
Both ``hackrf_transfer`` commands will start sampling RF signals at the same time, accurate to less than one sample period.
|
||||
|
||||
|
||||
Additional Devices
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Multiple HackRF Ones may be triggered by a single HackRF One. Ensure that all the devices share a common ground and then connect one device's trigger output to the trigger inputs of the other devices (with jumpers connected via a breadboard, for example).
|
||||
|
||||
Equipment other than a HackRF One may be connected to a HackRF One's trigger input or output. The trigger signal is a 3.3 V pulse that triggers on the rising edge.
|
||||
|
||||
|
||||
References
|
||||
~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -13,9 +13,17 @@ Welcome to HackRF's documentation!
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: HackRF One Hardware
|
||||
:caption: Hardware Platforms
|
||||
|
||||
hackrf_pro
|
||||
hackrf_one
|
||||
rad1o
|
||||
jawbreaker
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Hardware Information
|
||||
|
||||
hackrf_one
|
||||
hackrf_minimum_requirements
|
||||
list_of_hardware_revisions
|
||||
hardware_components
|
||||
@@ -29,13 +37,6 @@ Welcome to HackRF's documentation!
|
||||
usb_cables
|
||||
rf_shield_installation
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Jawbreaker Hardware
|
||||
|
||||
jawbreaker
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Firmware
|
||||
|
||||
@@ -2,8 +2,19 @@
|
||||
LEDs
|
||||
====
|
||||
|
||||
HackRF Pro
|
||||
~~~~~~~~~~
|
||||
|
||||
When HackRF Pro is plugged in to a USB host, four LEDs should turn on: MCU, FPGA, RF, and USB. The MCU LED indicates that the primary internal power supply is working properly. The FPGA and RF LEDs indicate that firmware is running and has switched on additional internal power supplies. The USB LED indicates that the HackRF Pro is communicating with the host over USB.
|
||||
|
||||
HackRF One
|
||||
~~~~~~~~~~
|
||||
|
||||
When HackRF One is plugged in to a USB host, four LEDs should turn on: 3V3, 1V8, RF, and USB. The 3V3 LED indicates that the primary internal power supply is working properly. The 1V8 and RF LEDs indicate that firmware is running and has switched on additional internal power supplies. The USB LED indicates that the HackRF One is communicating with the host over USB.
|
||||
|
||||
Both versions
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The RX and TX LEDs indicate that a receive or transmit operation is currently in progress.
|
||||
|
||||
Each LED is a single color. There are no multi-colored LEDs on HackRF One. Adjacent LEDs are different colors in order to make them easier to distinguish from one another. The colors do not mean anything.
|
||||
Each LED is a single color. There are no multi-colored LEDs on either HackRF One or HackRF Pro. Adjacent LEDs are different colors in order to make them easier to distinguish from one another. The colors do not mean anything.
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
Hardware Revisions
|
||||
================================================
|
||||
|
||||
HackRF One hardware revisions exist mainly to deal with changes in component availability. Each of the revisions meet the same performance specifications that are measured in the factory.
|
||||
Hardware revisions exist mainly to deal with changes in component availability. Each revision of a product meets the same performance specifications that are measured in the factory.
|
||||
|
||||
HackRF Pro
|
||||
~~~~~~~~~~
|
||||
|
||||
List of Hardware Revisions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The initial production revision of HackRF Pro is r1.2.1.
|
||||
|
||||
HackRF One
|
||||
~~~~~~~~~~
|
||||
|
||||
HackRF One r1–r4
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
16
docs/source/rad1o.rst
Normal file
16
docs/source/rad1o.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
=====
|
||||
rad1o
|
||||
=====
|
||||
|
||||
rad1o is the badge from the 2015 Chaos Communication Camp.
|
||||
|
||||
The rad1o badge contains a full-featured SDR (software defined radio) half-duplex transceiver, operating in a frequency range of about 50 MHz - 4000 MHz, and is software compatible to the HackRF.
|
||||
|
||||
.. image:: ../images/rad1o_8.jpg
|
||||
:alt: rad1o
|
||||
|
||||
(rad1o picture provided by Christoph Krichenbauer with Creative Commons License CC-BY-NC_SA)
|
||||
|
||||
More information can be found at the `rad1o badge wiki <https://rad1o.badge.events.ccc.de/start>`__
|
||||
|
||||
Compared to HackRF One, the rad1o badge uses a different mixer (MAX2871) with a reduced frequency range.
|
||||
@@ -2,6 +2,8 @@
|
||||
RF Shield Installation Instructions
|
||||
===============================================
|
||||
|
||||
HackRF Pro ships with an RF shield as standard.
|
||||
|
||||
Official Great Scott Gadgets HackRF Ones do not come from the factory with an RF shield installed around the radio section of the PCB. They do, however, have pads in place so that one may be installed if a user has a reason and an inclination to do so. The reason that they do not come preinstalled is that early testing revealed that the RF shield did little to improve the performance of the HackRF One. The recommended RF shield is the BMI-S-230-F-R (frame) with the BMI-S-230-C (shield). A two part RF shield is recommended because the shield section can be removed to allow access to the RF section of the HackRF One. This can be important if it becomes necessary to probe any part of the RF section, or to replace any parts of the RF section. However, even with a two part RF shield, it can be difficult to access the RF section of the HackRF One in certain situations. The following steps are a basic set of instructions for installing a RF shield on a HackRF One.
|
||||
|
||||
**CAUTION: Soldering a RF shield onto a HackRF One comes with a certain amount of risk. Beyond the inherent risks of soldering itself, this process may damage the HackRF One and no warranty is available to cover damage incurred from this process. If you do choose to install a RF shield on your HackRF One please proceed with caution.**
|
||||
|
||||
@@ -21,7 +21,7 @@ The before and after images were both taken with the preamp on and the LNA and V
|
||||
|
||||
|
||||
|
||||
Why isn't my HackRF One detectable after I plug it into my computer?
|
||||
Why isn't my HackRF detectable after I plug it into my computer?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your HackRF One isn't immediately detectable it is very possible that your Micro USB cable is not meeting HackRF One's requirements. HackRF One requires quite a bit of supply current and solid USB 2.0 high speed communications to operate. It is common for HackRF One to reveal cables with deficiencies such as carrying power but not data, carrying data but not enough power, etc. Please try multiple cables to resolve this issue. More than once people have gotten their HackRF One to work after trying their fifth cable.
|
||||
If your HackRF isn't immediately detectable it is very possible that your USB cable is not meeting HackRF's requirements. HackRF requires quite a bit of supply current and solid USB 2.0 high speed communications to operate. It is common for HackRF to reveal cables with deficiencies such as carrying power but not data, carrying data but not enough power, etc. Please try multiple cables to resolve this issue. More than once people have gotten their HackRF to work after trying their fifth cable.
|
||||
|
||||
@@ -28,6 +28,7 @@ $ cmake ..
|
||||
$ make
|
||||
$ hackrf_spiflash -w hackrf_usb.bin
|
||||
|
||||
If you have a HackRF Pro, add -DBOARD=PRALINE to the cmake command.
|
||||
If you have a Jawbreaker, add -DBOARD=JAWBREAKER to the cmake command.
|
||||
If you have a rad1o, use -DBOARD=RAD1O instead.
|
||||
|
||||
@@ -48,9 +49,9 @@ For loading firmware into RAM with DFU you will need:
|
||||
|
||||
http://dfu-util.sourceforge.net/
|
||||
|
||||
To start up HackRF One in DFU mode, hold down the DFU button while powering it
|
||||
on or while pressing and releasing the RESET button. Release the DFU button
|
||||
after the 3V3 LED illuminates.
|
||||
To start up HackRF One or HackRF Pro in DFU mode, hold down the DFU button
|
||||
while powering it on or while pressing and releasing the RESET button. Release
|
||||
the DFU button after the 3V3 LED illuminates.
|
||||
|
||||
A .dfu file is built by default when building firmware. Alternatively you can
|
||||
use a known good .dfu file from a release package. Load the firmware into RAM
|
||||
|
||||
@@ -27,8 +27,13 @@ int main(void)
|
||||
detect_hardware_platform();
|
||||
pin_setup();
|
||||
|
||||
#ifndef PRALINE
|
||||
/* enable 1V8 power supply so that the 1V8 LED lights up */
|
||||
enable_1v8_power();
|
||||
#else
|
||||
/* enable 1V2 power supply so that the 3V3FPGA LED lights up */
|
||||
enable_1v2_power();
|
||||
#endif
|
||||
|
||||
/* Blink LED1/2/3 on the board. */
|
||||
while (1)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
MEMORY
|
||||
{
|
||||
/* rom is really the shadow region that points to SPI flash or elsewhere */
|
||||
rom (rx) : ORIGIN = 0x00000000, LENGTH = 96K
|
||||
rom (rx) : ORIGIN = 0x00000000, LENGTH = 1M
|
||||
ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 96K
|
||||
ram_local2 (rwx) : ORIGIN = 0x10080000, LENGTH = 32K
|
||||
ram_sleep (rwx) : ORIGIN = 0x10088000, LENGTH = 8K
|
||||
|
||||
31
firmware/common/LPC43xx_M4_memory_rom_only.ld
Normal file
31
firmware/common/LPC43xx_M4_memory_rom_only.ld
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
* Copyright 2012 Jared Boone <jared@sharebrained.com>
|
||||
*
|
||||
* This file is part of HackRF
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* ROM-only section */
|
||||
.rom_only : {
|
||||
. = ALIGN(4);
|
||||
KEEP(*(.rom_only))
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
}
|
||||
45
firmware/common/adc.c
Normal file
45
firmware/common/adc.c
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "adc.h"
|
||||
#include <libopencm3/lpc43xx/adc.h>
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
|
||||
uint16_t adc_read(uint8_t pin)
|
||||
{
|
||||
bool alt_pin = (pin & 0x80);
|
||||
pin &= ~0x80;
|
||||
uint8_t pin_mask = (1 << pin);
|
||||
if (alt_pin) {
|
||||
SCU_ENAIO0 |= pin_mask;
|
||||
} else {
|
||||
SCU_ENAIO0 &= ~pin_mask;
|
||||
}
|
||||
ADC0_CR = ADC_CR_SEL(pin_mask) | ADC_CR_CLKDIV(45) | ADC_CR_PDN | ADC_CR_START(1);
|
||||
while (!(ADC0_GDR & ADC_DR_DONE) || (((ADC0_GDR >> 24) & 0x7) != pin))
|
||||
;
|
||||
return (ADC0_GDR >> 6) & 0x03FF;
|
||||
}
|
||||
|
||||
void adc_off(void)
|
||||
{
|
||||
ADC0_CR = 0;
|
||||
}
|
||||
30
firmware/common/adc.h
Normal file
30
firmware/common/adc.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __ADC_H__
|
||||
#define __ADC_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint16_t adc_read(uint8_t pin);
|
||||
void adc_off(void);
|
||||
|
||||
#endif // __ADC_H__
|
||||
@@ -25,9 +25,11 @@
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef PRALINE
|
||||
static refill_buffer_cb refill_buffer;
|
||||
static uint32_t xsvf_buffer_len, xsvf_pos;
|
||||
static unsigned char* xsvf_buffer;
|
||||
#endif
|
||||
|
||||
void cpld_jtag_take(jtag_t* const jtag)
|
||||
{
|
||||
@@ -36,22 +38,26 @@ void cpld_jtag_take(jtag_t* const jtag)
|
||||
/* Set initial GPIO state to the voltages of the internal or external pull-ups/downs,
|
||||
* to avoid any glitches.
|
||||
*/
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
gpio_set(gpio->gpio_pp_tms);
|
||||
#endif
|
||||
gpio_clear(gpio->gpio_tck);
|
||||
#ifndef PRALINE
|
||||
gpio_set(gpio->gpio_tms);
|
||||
gpio_set(gpio->gpio_tdi);
|
||||
gpio_clear(gpio->gpio_tck);
|
||||
#endif
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
/* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */
|
||||
gpio_input(gpio->gpio_pp_tms);
|
||||
gpio_input(gpio->gpio_pp_tdo);
|
||||
#endif
|
||||
gpio_output(gpio->gpio_tck);
|
||||
#ifndef PRALINE
|
||||
gpio_output(gpio->gpio_tms);
|
||||
gpio_output(gpio->gpio_tdi);
|
||||
gpio_output(gpio->gpio_tck);
|
||||
gpio_input(gpio->gpio_tdo);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cpld_jtag_release(jtag_t* const jtag)
|
||||
@@ -61,17 +67,20 @@ void cpld_jtag_release(jtag_t* const jtag)
|
||||
/* Make all pins inputs when JTAG interface not active.
|
||||
* Let the pull-ups/downs do the work.
|
||||
*/
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
/* Do not drive PortaPack-specific pins, initially, just to be cautious. */
|
||||
gpio_input(gpio->gpio_pp_tms);
|
||||
gpio_input(gpio->gpio_pp_tdo);
|
||||
#endif
|
||||
gpio_input(gpio->gpio_tck);
|
||||
#ifndef PRALINE
|
||||
gpio_input(gpio->gpio_tms);
|
||||
gpio_input(gpio->gpio_tdi);
|
||||
gpio_input(gpio->gpio_tck);
|
||||
gpio_input(gpio->gpio_tdo);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef PRALINE
|
||||
/* return 0 if success else return error code see xsvfExecute() */
|
||||
int cpld_jtag_program(
|
||||
jtag_t* const jtag,
|
||||
@@ -102,3 +111,4 @@ unsigned char cpld_jtag_get_next_byte(void)
|
||||
xsvf_pos++;
|
||||
return byte;
|
||||
}
|
||||
#endif
|
||||
@@ -27,11 +27,13 @@
|
||||
#include "gpio.h"
|
||||
|
||||
typedef struct jtag_gpio_t {
|
||||
gpio_t gpio_tms;
|
||||
gpio_t gpio_tck;
|
||||
#ifndef PRALINE
|
||||
gpio_t gpio_tms;
|
||||
gpio_t gpio_tdi;
|
||||
gpio_t gpio_tdo;
|
||||
#ifdef HACKRF_ONE
|
||||
#endif
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
gpio_t gpio_pp_tms;
|
||||
gpio_t gpio_pp_tdo;
|
||||
#endif
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#define SUPPORTED_PLATFORM (PLATFORM_HACKRF1_OG | PLATFORM_HACKRF1_R9)
|
||||
#elif RAD1O
|
||||
#define SUPPORTED_PLATFORM PLATFORM_RAD1O
|
||||
#elif PRALINE
|
||||
#define SUPPORTED_PLATFORM PLATFORM_PRALINE
|
||||
#else
|
||||
#define SUPPORTED_PLATFORM 0
|
||||
#endif
|
||||
|
||||
138
firmware/common/fpga.c
Normal file
138
firmware/common/fpga.c
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "hackrf_core.h"
|
||||
#include "ice40_spi.h"
|
||||
#include "fpga.h"
|
||||
#include "fpga_regs.def"
|
||||
|
||||
/* Set up all registers according to the loaded bitstream's defaults. */
|
||||
void fpga_init(fpga_driver_t* const drv)
|
||||
{
|
||||
// Standard bitstream default register values.
|
||||
set_FPGA_STANDARD_CTRL_DC_BLOCK(drv, true);
|
||||
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_EN(drv, false);
|
||||
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_UP(drv, false);
|
||||
set_FPGA_STANDARD_CTRL_PRBS(drv, false);
|
||||
set_FPGA_STANDARD_CTRL_TRIGGER_EN(drv, false);
|
||||
set_FPGA_STANDARD_TX_CTRL(drv, 0);
|
||||
|
||||
// TODO support the other bitstreams
|
||||
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_setup(fpga_driver_t* const drv)
|
||||
{
|
||||
// TODO implement fpga_target.c
|
||||
|
||||
fpga_init(drv);
|
||||
}
|
||||
|
||||
uint8_t fpga_reg_read(fpga_driver_t* const drv, uint8_t r)
|
||||
{
|
||||
uint8_t v;
|
||||
ssp1_set_mode_ice40();
|
||||
v = ice40_spi_read(drv->bus, r);
|
||||
ssp1_set_mode_max283x();
|
||||
drv->regs[r] = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
void fpga_reg_write(fpga_driver_t* const drv, uint8_t r, uint8_t v)
|
||||
{
|
||||
drv->regs[r] = v;
|
||||
ssp1_set_mode_ice40();
|
||||
ice40_spi_write(drv->bus, r, v);
|
||||
ssp1_set_mode_max283x();
|
||||
FPGA_REG_SET_CLEAN(drv, r);
|
||||
}
|
||||
|
||||
static inline void fpga_reg_commit(fpga_driver_t* const drv, uint8_t r)
|
||||
{
|
||||
fpga_reg_write(drv, r, drv->regs[r]);
|
||||
}
|
||||
|
||||
void fpga_regs_commit(fpga_driver_t* const drv)
|
||||
{
|
||||
int r;
|
||||
for (r = 1; r < FPGA_NUM_REGS; r++) {
|
||||
if ((drv->regs_dirty >> r) & 0x1) {
|
||||
fpga_reg_commit(drv, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fpga_set_trigger_enable(fpga_driver_t* const drv, const bool enable)
|
||||
{
|
||||
fpga_reg_read(drv, FPGA_STANDARD_CTRL);
|
||||
set_FPGA_STANDARD_CTRL_TRIGGER_EN(drv, enable & 0b1);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_rx_dc_block_enable(fpga_driver_t* const drv, const bool enable)
|
||||
{
|
||||
set_FPGA_STANDARD_CTRL_DC_BLOCK(drv, enable);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_rx_decimation_ratio(fpga_driver_t* const drv, const uint8_t value)
|
||||
{
|
||||
set_FPGA_STANDARD_RX_DECIM(drv, value & 0b111);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_rx_quarter_shift_mode(
|
||||
fpga_driver_t* const drv,
|
||||
const fpga_quarter_shift_mode_t mode)
|
||||
{
|
||||
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_EN(drv, (mode >> 0) & 0b1);
|
||||
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_UP(drv, (mode >> 1) & 0b1);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_tx_interpolation_ratio(fpga_driver_t* const drv, const uint8_t value)
|
||||
{
|
||||
set_FPGA_STANDARD_TX_INTRP(drv, value & 0b111);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_prbs_enable(fpga_driver_t* const drv, const bool enable)
|
||||
{
|
||||
set_FPGA_STANDARD_CTRL(drv, 0);
|
||||
if (enable) {
|
||||
set_FPGA_STANDARD_CTRL_PRBS(drv, enable);
|
||||
}
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_tx_nco_enable(fpga_driver_t* const drv, const bool enable)
|
||||
{
|
||||
set_FPGA_STANDARD_TX_CTRL_NCO_EN(drv, enable);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
|
||||
void fpga_set_tx_nco_pstep(fpga_driver_t* const drv, const uint8_t phase_increment)
|
||||
{
|
||||
set_FPGA_STANDARD_TX_PSTEP(drv, phase_increment);
|
||||
fpga_regs_commit(drv);
|
||||
}
|
||||
83
firmware/common/fpga.h
Normal file
83
firmware/common/fpga.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __FPGA_H
|
||||
#define __FPGA_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "ice40_spi.h"
|
||||
|
||||
/* Up to 6 registers, each containing up to 8 bits of data */
|
||||
#define FPGA_NUM_REGS 6
|
||||
#define FPGA_DATA_REGS_MAX_VALUE 255
|
||||
|
||||
typedef enum {
|
||||
FPGA_QUARTER_SHIFT_MODE_NONE = 0b00,
|
||||
FPGA_QUARTER_SHIFT_MODE_UP = 0b11,
|
||||
FPGA_QUARTER_SHIFT_MODE_DOWN = 0b01,
|
||||
} fpga_quarter_shift_mode_t;
|
||||
|
||||
struct fpga_driver_t;
|
||||
typedef struct fpga_driver_t fpga_driver_t;
|
||||
|
||||
struct fpga_driver_t {
|
||||
ice40_spi_driver_t* bus;
|
||||
uint8_t regs[FPGA_NUM_REGS];
|
||||
uint8_t regs_dirty;
|
||||
};
|
||||
|
||||
/* Initialize the loaded bitstream's registers to their default values. */
|
||||
extern void fpga_init(fpga_driver_t* const drv);
|
||||
|
||||
/* Initialize fpga and gateware. */
|
||||
extern void fpga_setup(fpga_driver_t* const drv);
|
||||
|
||||
/* Read a register via SPI. Save a copy to memory and return
|
||||
* value. Mark clean. */
|
||||
extern uint8_t fpga_reg_read(fpga_driver_t* const drv, uint8_t r);
|
||||
|
||||
/* Write value to register via SPI and save a copy to memory. Mark
|
||||
* clean. */
|
||||
extern void fpga_reg_write(fpga_driver_t* const drv, uint8_t r, uint8_t v);
|
||||
|
||||
/* Write all dirty registers via SPI from memory. Mark all clean. Some
|
||||
* operations require registers to be written in a certain order. Use
|
||||
* provided routines for those operations. */
|
||||
extern void fpga_regs_commit(fpga_driver_t* const drv);
|
||||
|
||||
void fpga_set_trigger_enable(fpga_driver_t* const drv, const bool enable);
|
||||
void fpga_set_rx_dc_block_enable(fpga_driver_t* const drv, const bool enable);
|
||||
void fpga_set_rx_decimation_ratio(fpga_driver_t* const drv, const uint8_t value);
|
||||
void fpga_set_rx_quarter_shift_mode(
|
||||
fpga_driver_t* const drv,
|
||||
const fpga_quarter_shift_mode_t mode);
|
||||
void fpga_set_tx_interpolation_ratio(fpga_driver_t* const drv, const uint8_t value);
|
||||
|
||||
void fpga_set_prbs_enable(fpga_driver_t* const drv, const bool enable);
|
||||
void fpga_set_tx_nco_enable(fpga_driver_t* const drv, const bool enable);
|
||||
void fpga_set_tx_nco_pstep(fpga_driver_t* const drv, const uint8_t phase_increment);
|
||||
|
||||
bool fpga_image_load(unsigned int index);
|
||||
bool fpga_spi_selftest();
|
||||
bool fpga_sgpio_selftest();
|
||||
bool fpga_if_xcvr_selftest();
|
||||
|
||||
#endif // __FPGA_H
|
||||
108
firmware/common/fpga_image.c
Normal file
108
firmware/common/fpga_image.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "hackrf_core.h"
|
||||
#include "lz4_blk.h"
|
||||
#include "selftest.h"
|
||||
|
||||
// FPGA bitstreams blob.
|
||||
extern uint32_t _binary_fpga_bin_start;
|
||||
extern uint32_t _binary_fpga_bin_end;
|
||||
extern uint32_t _binary_fpga_bin_size;
|
||||
|
||||
struct fpga_image_read_ctx {
|
||||
uint32_t addr;
|
||||
size_t next_block_sz;
|
||||
uint8_t init_flag;
|
||||
uint8_t buffer[4096 + 2];
|
||||
};
|
||||
|
||||
static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer)
|
||||
{
|
||||
// Assume out_buffer is 4KB
|
||||
struct fpga_image_read_ctx* ctx = _ctx;
|
||||
size_t block_sz = ctx->next_block_sz;
|
||||
|
||||
// first iteration: read first block size
|
||||
if (ctx->init_flag == 0) {
|
||||
w25q80bv_read(&spi_flash, ctx->addr, 2, ctx->buffer);
|
||||
block_sz = ctx->buffer[0] | (ctx->buffer[1] << 8);
|
||||
ctx->addr += 2;
|
||||
ctx->init_flag = 1;
|
||||
}
|
||||
|
||||
// finish at end marker
|
||||
if (block_sz == 0)
|
||||
return 0;
|
||||
|
||||
// Read compressed block (and the next block size) from flash.
|
||||
w25q80bv_read(&spi_flash, ctx->addr, block_sz + 2, ctx->buffer);
|
||||
ctx->addr += block_sz + 2;
|
||||
ctx->next_block_sz = ctx->buffer[block_sz] | (ctx->buffer[block_sz + 1] << 8);
|
||||
|
||||
// Decompress block.
|
||||
return lz4_blk_decompress(ctx->buffer, out_buffer, block_sz);
|
||||
}
|
||||
|
||||
bool fpga_image_load(unsigned int index)
|
||||
{
|
||||
#if defined(DFU_MODE) || defined(RAM_MODE)
|
||||
selftest.fpga_image_load = SKIPPED;
|
||||
selftest.report.pass = false;
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// TODO: do SPI setup and read number of bitstreams once!
|
||||
|
||||
// Prepare for SPI flash access.
|
||||
spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv);
|
||||
w25q80bv_setup(&spi_flash);
|
||||
|
||||
// Read number of bitstreams from flash.
|
||||
// Check the bitstream exists, and extract its offset.
|
||||
uint32_t addr = (uint32_t) &_binary_fpga_bin_start;
|
||||
uint32_t num_bitstreams, bitstream_offset;
|
||||
w25q80bv_read(&spi_flash, addr, 4, (uint8_t*) &num_bitstreams);
|
||||
if (index >= num_bitstreams)
|
||||
return false;
|
||||
w25q80bv_read(&spi_flash, addr + 4 * (index + 1), 4, (uint8_t*) &bitstream_offset);
|
||||
|
||||
// A callback function is used by the FPGA programmer
|
||||
// to obtain consecutive gateware chunks.
|
||||
ice40_spi_target_init(&ice40);
|
||||
ssp1_set_mode_ice40();
|
||||
struct fpga_image_read_ctx fpga_image_ctx = {
|
||||
.addr = (uint32_t) &_binary_fpga_bin_start + bitstream_offset,
|
||||
};
|
||||
const bool success = ice40_spi_syscfg_program(
|
||||
&ice40,
|
||||
fpga_image_read_block_cb,
|
||||
&fpga_image_ctx);
|
||||
ssp1_set_mode_max283x();
|
||||
|
||||
// Update selftest result.
|
||||
selftest.fpga_image_load = success ? PASSED : FAILED;
|
||||
if (selftest.fpga_image_load != PASSED) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
74
firmware/common/fpga_regs.def
Normal file
74
firmware/common/fpga_regs.def
Normal file
@@ -0,0 +1,74 @@
|
||||
/* -*- mode: c -*-
|
||||
*
|
||||
* Copyright 2012 Michael Ossmann
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __FPGA_REGS_DEF
|
||||
#define __FPGA_REGS_DEF
|
||||
|
||||
#define FPGA_REG_SET_CLEAN(_d, _r) (_d->regs_dirty &= ~(1UL<<_r))
|
||||
#define FPGA_REG_SET_DIRTY(_d, _r) (_d->regs_dirty |= (1UL<<_r))
|
||||
|
||||
/* Generate static inline accessors that operate on the global
|
||||
* regs. Done this way to (1) allow defs to be scraped out and used
|
||||
* elsewhere, e.g. in scripts, (2) to avoid dealing with endian
|
||||
* (structs). This may be used in firmware, or on host predefined
|
||||
* register loads. */
|
||||
|
||||
/* On set_, register is always set dirty, even if nothing
|
||||
* changed. This makes sure that writes that have side effects,
|
||||
* e.g. frequency setting, are not skipped. */
|
||||
|
||||
/* n=name, r=regnum, o=offset (bits from LSB) of LSB of field,
|
||||
* l=length (bits) */
|
||||
#define __MREG__(n,r,o,l) \
|
||||
static inline uint8_t get_##n(fpga_driver_t* const _d) { \
|
||||
return (_d->regs[r] >> o) & ((1L<<l)-1); \
|
||||
} \
|
||||
static inline void set_##n(fpga_driver_t* const _d, uint8_t v) { \
|
||||
_d->regs[r] &= (uint8_t)(~(((1L<<l)-1)<<o)); \
|
||||
_d->regs[r] |= (uint8_t)(((v&((1L<<l)-1))<<o)); \
|
||||
FPGA_REG_SET_DIRTY(_d, r); \
|
||||
} \
|
||||
const uint8_t n = r;
|
||||
|
||||
/* REG 01 (1): CTRL */
|
||||
__MREG__(FPGA_STANDARD_CTRL, 1, 0, 8)
|
||||
__MREG__(FPGA_STANDARD_CTRL_DC_BLOCK, 1, 0, 1)
|
||||
__MREG__(FPGA_STANDARD_CTRL_QUARTER_SHIFT_EN, 1, 1, 1)
|
||||
__MREG__(FPGA_STANDARD_CTRL_QUARTER_SHIFT_UP, 1, 2, 1)
|
||||
__MREG__(FPGA_STANDARD_CTRL_PRBS, 1, 6, 1)
|
||||
__MREG__(FPGA_STANDARD_CTRL_TRIGGER_EN, 1, 7, 1)
|
||||
|
||||
/* REG 02 (2): RX_DECIM */
|
||||
__MREG__(FPGA_STANDARD_RX_DECIM, 2, 0, 3)
|
||||
|
||||
/* REG 03 (3): TX_CTRL */
|
||||
__MREG__(FPGA_STANDARD_TX_CTRL, 3, 0, 1)
|
||||
__MREG__(FPGA_STANDARD_TX_CTRL_NCO_EN, 3, 0, 1)
|
||||
|
||||
/* REG 04 (4): TX_INTRP */
|
||||
__MREG__(FPGA_STANDARD_TX_INTRP, 4, 0, 3)
|
||||
|
||||
/* REG 05 (5): TX_PSTEP */
|
||||
__MREG__(FPGA_STANDARD_TX_PSTEP, 5, 0, 8)
|
||||
|
||||
#endif // __FPGA_REGS_DEF
|
||||
307
firmware/common/fpga_selftest.c
Normal file
307
firmware/common/fpga_selftest.c
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "hackrf_core.h"
|
||||
#include "m0_state.h"
|
||||
#include "streaming.h"
|
||||
#include "selftest.h"
|
||||
#include "fpga.h"
|
||||
|
||||
// USB buffer used during selftests.
|
||||
#define USB_BULK_BUFFER_SIZE 0x8000
|
||||
extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE];
|
||||
|
||||
static int rx_samples(const unsigned int num_samples, uint32_t max_cycles)
|
||||
{
|
||||
uint32_t cycle_count = 0;
|
||||
int rc = 0;
|
||||
|
||||
m0_set_mode(M0_MODE_RX);
|
||||
m0_state.shortfall_limit = 0;
|
||||
baseband_streaming_enable(&sgpio_config);
|
||||
while (m0_state.m0_count < num_samples) {
|
||||
cycle_count++;
|
||||
if ((max_cycles > 0) && (cycle_count >= max_cycles)) {
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
baseband_streaming_disable(&sgpio_config);
|
||||
m0_set_mode(M0_MODE_IDLE);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool fpga_spi_selftest()
|
||||
{
|
||||
// Skip if FPGA configuration failed.
|
||||
if (selftest.fpga_image_load != PASSED) {
|
||||
selftest.fpga_spi = SKIPPED;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test writing a register and reading it back.
|
||||
uint8_t reg = 5;
|
||||
uint8_t write_value = 0xA5;
|
||||
ssp1_set_mode_ice40();
|
||||
ice40_spi_write(&ice40, reg, write_value);
|
||||
uint8_t read_value = ice40_spi_read(&ice40, reg);
|
||||
ssp1_set_mode_max283x();
|
||||
|
||||
// Update selftest result.
|
||||
selftest.fpga_spi = (read_value == write_value) ? PASSED : FAILED;
|
||||
if (selftest.fpga_spi != PASSED) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
return selftest.fpga_spi == PASSED;
|
||||
}
|
||||
|
||||
static uint8_t lfsr_advance(uint8_t v)
|
||||
{
|
||||
const uint8_t feedback = ((v >> 3) ^ (v >> 4) ^ (v >> 5) ^ (v >> 7)) & 1;
|
||||
return (v << 1) | feedback;
|
||||
}
|
||||
|
||||
bool fpga_sgpio_selftest()
|
||||
{
|
||||
bool timeout = false;
|
||||
|
||||
// Skip if FPGA configuration failed or its SPI bus is not working.
|
||||
if ((selftest.fpga_image_load != PASSED) || (selftest.fpga_spi != PASSED)) {
|
||||
selftest.sgpio_rx = SKIPPED;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable PRBS mode.
|
||||
fpga_set_prbs_enable(&fpga, true);
|
||||
|
||||
// Stream 512 samples from the FPGA.
|
||||
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
|
||||
if (rx_samples(512, 10000) == -1) {
|
||||
timeout = true;
|
||||
}
|
||||
|
||||
// Disable PRBS mode.
|
||||
fpga_set_prbs_enable(&fpga, false);
|
||||
|
||||
// Generate sequence from first value and compare.
|
||||
bool seq_in_sync = (usb_bulk_buffer[0] != 0);
|
||||
uint8_t seq = lfsr_advance(usb_bulk_buffer[0]);
|
||||
for (int i = 1; i < 512; ++i) {
|
||||
if (usb_bulk_buffer[i] != seq) {
|
||||
seq_in_sync = false;
|
||||
break;
|
||||
}
|
||||
seq = lfsr_advance(seq);
|
||||
}
|
||||
|
||||
// Update selftest result.
|
||||
if (seq_in_sync) {
|
||||
selftest.sgpio_rx = PASSED;
|
||||
} else if (timeout) {
|
||||
selftest.sgpio_rx = TIMEOUT;
|
||||
} else {
|
||||
selftest.sgpio_rx = FAILED;
|
||||
}
|
||||
if (selftest.sgpio_rx != PASSED) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
return selftest.sgpio_rx == PASSED;
|
||||
}
|
||||
|
||||
static void measure_tone(int8_t* samples, size_t len, struct xcvr_measurements* results)
|
||||
{
|
||||
results->zcs_i = 0;
|
||||
results->zcs_q = 0;
|
||||
results->max_mag_i = 0;
|
||||
results->max_mag_q = 0;
|
||||
results->avg_mag_sq_i = 0;
|
||||
results->avg_mag_sq_q = 0;
|
||||
uint8_t last_sign_i = 0;
|
||||
uint8_t last_sign_q = 0;
|
||||
for (size_t i = 0; i < len; i += 2) {
|
||||
int8_t sample_i = samples[i];
|
||||
int8_t sample_q = samples[i + 1];
|
||||
uint8_t sign_i = sample_i < 0 ? 1 : 0;
|
||||
uint8_t sign_q = sample_q < 0 ? 1 : 0;
|
||||
results->zcs_i += sign_i ^ last_sign_i;
|
||||
results->zcs_q += sign_q ^ last_sign_q;
|
||||
last_sign_i = sign_i;
|
||||
last_sign_q = sign_q;
|
||||
uint8_t mag_i = sign_i ? -sample_i : sample_i;
|
||||
uint8_t mag_q = sign_q ? -sample_q : sample_q;
|
||||
if (mag_i > results->max_mag_i)
|
||||
results->max_mag_i = mag_i;
|
||||
if (mag_q > results->max_mag_q)
|
||||
results->max_mag_q = mag_q;
|
||||
results->avg_mag_sq_i += mag_i * mag_i;
|
||||
results->avg_mag_sq_q += mag_q * mag_q;
|
||||
}
|
||||
results->avg_mag_sq_i /= (len / 2);
|
||||
results->avg_mag_sq_q /= (len / 2);
|
||||
}
|
||||
|
||||
static bool in_range(int value, int expected, int error)
|
||||
{
|
||||
int max = expected * (100 + error) / 100;
|
||||
int min = expected * (100 - error) / 100;
|
||||
return (value > min) && (value < max);
|
||||
}
|
||||
|
||||
bool fpga_if_xcvr_selftest()
|
||||
{
|
||||
bool timeout = false;
|
||||
|
||||
// Skip if FPGA configuration failed or its SPI bus is not working.
|
||||
if ((selftest.fpga_image_load != PASSED) || (selftest.fpga_spi != PASSED)) {
|
||||
selftest.xcvr_loopback = SKIPPED;
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t num_samples = USB_BULK_BUFFER_SIZE / 2;
|
||||
|
||||
// Set common RX path and gateware settings for the measurements.
|
||||
fpga_set_tx_nco_pstep(&fpga, 64); // NCO phase increment
|
||||
fpga_set_tx_nco_enable(&fpga, true); // TX enable
|
||||
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX_CALIBRATION);
|
||||
max2831_set_lna_gain(&max283x, 16);
|
||||
max2831_set_vga_gain(&max283x, 36);
|
||||
max2831_set_frequency(&max283x, 2500000000);
|
||||
|
||||
// Capture 1: 4 Msps, tone at 0.5 MHz, narrowband filter OFF
|
||||
sample_rate_frac_set(4000000 * 2, 1);
|
||||
delay_us_at_mhz(1000, 204);
|
||||
if (rx_samples(num_samples, 2000000) == -1) {
|
||||
timeout = true;
|
||||
}
|
||||
measure_tone(
|
||||
(int8_t*) usb_bulk_buffer,
|
||||
num_samples,
|
||||
&selftest.xcvr_measurements[0]);
|
||||
|
||||
// Capture 2: 4 Msps, tone at 0.5 MHz, narrowband filter ON
|
||||
narrowband_filter_set(1);
|
||||
delay_us_at_mhz(1000, 204);
|
||||
if (rx_samples(num_samples, 2000000) == -1) {
|
||||
timeout = true;
|
||||
}
|
||||
measure_tone(
|
||||
(int8_t*) usb_bulk_buffer,
|
||||
num_samples,
|
||||
&selftest.xcvr_measurements[1]);
|
||||
|
||||
// Capture 3: 20 Msps, tone at 5 MHz, narrowband filter OFF
|
||||
fpga_set_tx_nco_pstep(&fpga, 255);
|
||||
sample_rate_frac_set(20000000 * 2, 1);
|
||||
narrowband_filter_set(0);
|
||||
delay_us_at_mhz(1000, 204);
|
||||
if (rx_samples(num_samples, 2000000) == -1) {
|
||||
timeout = true;
|
||||
}
|
||||
measure_tone(
|
||||
(int8_t*) usb_bulk_buffer,
|
||||
num_samples,
|
||||
&selftest.xcvr_measurements[2]);
|
||||
|
||||
// Capture 4: 20 Msps, tone at 5 MHz, narrowband filter ON
|
||||
narrowband_filter_set(1);
|
||||
delay_us_at_mhz(1000, 204);
|
||||
if (rx_samples(num_samples, 2000000) == -1) {
|
||||
timeout = true;
|
||||
}
|
||||
measure_tone(
|
||||
(int8_t*) usb_bulk_buffer,
|
||||
num_samples,
|
||||
&selftest.xcvr_measurements[3]);
|
||||
|
||||
// Restore default settings.
|
||||
sample_rate_set(10000000);
|
||||
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
|
||||
narrowband_filter_set(0);
|
||||
fpga_init(&fpga);
|
||||
|
||||
if (timeout) {
|
||||
selftest.xcvr_loopback = TIMEOUT;
|
||||
selftest.report.pass = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int expected_zcs;
|
||||
bool i_in_range;
|
||||
bool q_in_range;
|
||||
bool i_energy_in_range;
|
||||
bool q_energy_in_range;
|
||||
|
||||
// Capture 0:
|
||||
// Count zero crossings.
|
||||
// N/2 samples/channel * 2 zcs/cycle / 16 samples/cycle = N/16 zcs/channel
|
||||
expected_zcs = num_samples / 16;
|
||||
i_in_range = in_range(selftest.xcvr_measurements[0].zcs_i, expected_zcs, 5);
|
||||
q_in_range = in_range(selftest.xcvr_measurements[0].zcs_q, expected_zcs, 5);
|
||||
i_energy_in_range = (selftest.xcvr_measurements[0].avg_mag_sq_i > 500) &&
|
||||
(selftest.xcvr_measurements[0].avg_mag_sq_i < 2500);
|
||||
q_energy_in_range = (selftest.xcvr_measurements[0].avg_mag_sq_q > 500) &&
|
||||
(selftest.xcvr_measurements[0].avg_mag_sq_q < 2500);
|
||||
bool capture_0_test =
|
||||
i_in_range && q_in_range && i_energy_in_range && q_energy_in_range;
|
||||
|
||||
// Capture 1:
|
||||
// Count zero crossings.
|
||||
expected_zcs = num_samples / 16;
|
||||
i_in_range = in_range(selftest.xcvr_measurements[1].zcs_i, expected_zcs, 5);
|
||||
q_in_range = in_range(selftest.xcvr_measurements[1].zcs_q, expected_zcs, 5);
|
||||
i_energy_in_range = (selftest.xcvr_measurements[1].avg_mag_sq_i > 500) &&
|
||||
(selftest.xcvr_measurements[1].avg_mag_sq_i < 2500);
|
||||
q_energy_in_range = (selftest.xcvr_measurements[1].avg_mag_sq_q > 500) &&
|
||||
(selftest.xcvr_measurements[1].avg_mag_sq_q < 2500);
|
||||
bool capture_1_test =
|
||||
i_in_range && q_in_range && i_energy_in_range && q_energy_in_range;
|
||||
|
||||
// Capture 2:
|
||||
// Count zero crossings.
|
||||
expected_zcs = num_samples / 4;
|
||||
i_in_range = in_range(selftest.xcvr_measurements[2].zcs_i, expected_zcs, 5);
|
||||
q_in_range = in_range(selftest.xcvr_measurements[2].zcs_q, expected_zcs, 5);
|
||||
i_energy_in_range = (selftest.xcvr_measurements[2].avg_mag_sq_i > 400) &&
|
||||
(selftest.xcvr_measurements[2].avg_mag_sq_i < 2000);
|
||||
q_energy_in_range = (selftest.xcvr_measurements[2].avg_mag_sq_q > 400) &&
|
||||
(selftest.xcvr_measurements[2].avg_mag_sq_q < 2000);
|
||||
bool capture_2_test =
|
||||
i_in_range && q_in_range && i_energy_in_range && q_energy_in_range;
|
||||
|
||||
// Capture 3:
|
||||
i_energy_in_range = (selftest.xcvr_measurements[3].avg_mag_sq_i < 100);
|
||||
q_energy_in_range = (selftest.xcvr_measurements[3].avg_mag_sq_q < 100);
|
||||
bool capture_3_test = i_energy_in_range && q_energy_in_range;
|
||||
|
||||
// Update selftest result.
|
||||
selftest.xcvr_loopback =
|
||||
(capture_0_test && capture_1_test && capture_2_test && capture_3_test) ?
|
||||
PASSED :
|
||||
FAILED;
|
||||
if (selftest.xcvr_loopback != PASSED) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
return selftest.xcvr_loopback == PASSED;
|
||||
}
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "sgpio.h"
|
||||
#include "si5351c.h"
|
||||
#include "spi_ssp.h"
|
||||
#include "max2831.h"
|
||||
#include "max2831_target.h"
|
||||
#include "max283x.h"
|
||||
#include "max5864.h"
|
||||
#include "max5864_target.h"
|
||||
@@ -34,6 +36,7 @@
|
||||
#include "i2c_bus.h"
|
||||
#include "i2c_lpc.h"
|
||||
#include "cpld_jtag.h"
|
||||
#include "ice40_spi.h"
|
||||
#include "platform_detect.h"
|
||||
#include "clkin.h"
|
||||
#include <libopencm3/lpc43xx/cgu.h>
|
||||
@@ -41,7 +44,7 @@
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include <libopencm3/lpc43xx/ssp.h>
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
#include "portapack.h"
|
||||
#endif
|
||||
|
||||
@@ -55,16 +58,32 @@ static struct gpio_t gpio_led[] = {
|
||||
#ifdef RAD1O
|
||||
GPIO(5, 26),
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
GPIO(4, 6),
|
||||
#endif
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
#ifndef PRALINE
|
||||
static struct gpio_t gpio_1v8_enable = GPIO(3, 6);
|
||||
#else
|
||||
static struct gpio_t gpio_1v2_enable = GPIO(4, 7);
|
||||
static struct gpio_t gpio_3v3aux_enable_n = GPIO(5, 15);
|
||||
#endif
|
||||
|
||||
/* MAX283x GPIO (XCVR_CTL) PinMux */
|
||||
/* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_max283x_select = GPIO(6, 28);
|
||||
#else
|
||||
static struct gpio_t gpio_max283x_select = GPIO(0, 15);
|
||||
#endif
|
||||
|
||||
/* MAX5864 SPI chip select (AD_CS) GPIO PinMux */
|
||||
/* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_max5864_select = GPIO(6, 30);
|
||||
#else
|
||||
static struct gpio_t gpio_max5864_select = GPIO(2, 7);
|
||||
#endif
|
||||
|
||||
/* RFFC5071 GPIO serial interface PinMux */
|
||||
// #ifdef RAD1O
|
||||
@@ -78,6 +97,9 @@ static struct gpio_t gpio_max5864_select = GPIO(2, 7);
|
||||
#ifdef HACKRF_ONE
|
||||
static struct gpio_t gpio_vaa_disable = GPIO(2, 9);
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_vaa_disable = GPIO(4, 1);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
static struct gpio_t gpio_vaa_enable = GPIO(2, 9);
|
||||
#endif
|
||||
@@ -115,10 +137,23 @@ static struct gpio_t gpio_low_high_filt_n = GPIO(2, 12);
|
||||
static struct gpio_t gpio_tx_amp = GPIO(2, 15);
|
||||
static struct gpio_t gpio_rx_lna = GPIO(5, 15);
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_tx_en = GPIO(3, 4);
|
||||
static struct gpio_t gpio_mix_en_n = GPIO(3, 2);
|
||||
static struct gpio_t gpio_mix_en_n_r1_0 = GPIO(5, 6);
|
||||
static struct gpio_t gpio_lpf_en = GPIO(4, 8);
|
||||
static struct gpio_t gpio_rf_amp_en = GPIO(4, 9);
|
||||
static struct gpio_t gpio_ant_bias_en_n = GPIO(1, 12);
|
||||
#endif
|
||||
|
||||
/* CPLD JTAG interface GPIO pins */
|
||||
static struct gpio_t gpio_cpld_tdo = GPIO(5, 18);
|
||||
/* CPLD JTAG interface GPIO pins, FPGA config pins in Praline */
|
||||
static struct gpio_t gpio_cpld_tck = GPIO(3, 0);
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_fpga_cfg_creset = GPIO(2, 11);
|
||||
static struct gpio_t gpio_fpga_cfg_cdone = GPIO(5, 14);
|
||||
static struct gpio_t gpio_fpga_cfg_spi_cs = GPIO(2, 10);
|
||||
#else
|
||||
static struct gpio_t gpio_cpld_tdo = GPIO(5, 18);
|
||||
#if (defined HACKRF_ONE || defined RAD1O)
|
||||
static struct gpio_t gpio_cpld_tms = GPIO(3, 4);
|
||||
static struct gpio_t gpio_cpld_tdi = GPIO(3, 1);
|
||||
@@ -126,14 +161,17 @@ static struct gpio_t gpio_cpld_tdi = GPIO(3, 1);
|
||||
static struct gpio_t gpio_cpld_tms = GPIO(3, 1);
|
||||
static struct gpio_t gpio_cpld_tdi = GPIO(3, 4);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
static struct gpio_t gpio_cpld_pp_tms = GPIO(1, 1);
|
||||
static struct gpio_t gpio_cpld_pp_tdo = GPIO(1, 8);
|
||||
#endif
|
||||
|
||||
/* other CPLD interface GPIO pins */
|
||||
static struct gpio_t gpio_hw_sync_enable = GPIO(5, 12);
|
||||
#ifndef PRALINE
|
||||
static struct gpio_t gpio_trigger_enable = GPIO(5, 12);
|
||||
#endif
|
||||
static struct gpio_t gpio_q_invert = GPIO(0, 13);
|
||||
|
||||
/* HackRF One r9 */
|
||||
@@ -141,7 +179,20 @@ static struct gpio_t gpio_q_invert = GPIO(0, 13);
|
||||
static struct gpio_t gpio_h1r9_rx = GPIO(0, 7);
|
||||
static struct gpio_t gpio_h1r9_1v8_enable = GPIO(2, 9);
|
||||
static struct gpio_t gpio_h1r9_vaa_disable = GPIO(3, 6);
|
||||
static struct gpio_t gpio_h1r9_hw_sync_enable = GPIO(5, 5);
|
||||
static struct gpio_t gpio_h1r9_trigger_enable = GPIO(5, 5);
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_p2_ctrl0 = GPIO(7, 3);
|
||||
static struct gpio_t gpio_p2_ctrl1 = GPIO(7, 4);
|
||||
static struct gpio_t gpio_p1_ctrl0 = GPIO(0, 14);
|
||||
static struct gpio_t gpio_p1_ctrl1 = GPIO(5, 16);
|
||||
static struct gpio_t gpio_p1_ctrl2 = GPIO(3, 5);
|
||||
static struct gpio_t gpio_clkin_ctrl = GPIO(0, 15);
|
||||
static struct gpio_t gpio_aa_en = GPIO(1, 7);
|
||||
static struct gpio_t gpio_trigger_in = GPIO(6, 26);
|
||||
static struct gpio_t gpio_trigger_out = GPIO(5, 6);
|
||||
static struct gpio_t gpio_pps_out = GPIO(5, 5);
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
@@ -172,6 +223,45 @@ si5351c_driver_t clock_gen = {
|
||||
.i2c_address = 0x60,
|
||||
};
|
||||
|
||||
spi_bus_t spi_bus_ssp1 = {
|
||||
.obj = (void*) SSP1_BASE,
|
||||
.config = &ssp_config_max5864,
|
||||
.start = spi_ssp_start,
|
||||
.stop = spi_ssp_stop,
|
||||
.transfer = spi_ssp_transfer,
|
||||
.transfer_gather = spi_ssp_transfer_gather,
|
||||
};
|
||||
|
||||
#ifdef PRALINE
|
||||
const ssp_config_t ssp_config_max283x = {
|
||||
/* FIXME speed up once everything is working reliably */
|
||||
/*
|
||||
// Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz
|
||||
const uint8_t serial_clock_rate = 32;
|
||||
const uint8_t clock_prescale_rate = 128;
|
||||
*/
|
||||
// Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz
|
||||
.data_bits = SSP_DATA_9BITS, // send 2 words
|
||||
.serial_clock_rate = 21,
|
||||
.clock_prescale_rate = 2,
|
||||
.gpio_select = &gpio_max283x_select,
|
||||
};
|
||||
|
||||
static struct gpio_t gpio_max2831_enable = GPIO(7, 1);
|
||||
static struct gpio_t gpio_max2831_rx_enable = GPIO(7, 2);
|
||||
static struct gpio_t gpio_max2831_rxhp = GPIO(6, 29);
|
||||
static struct gpio_t gpio_max2831_ld = GPIO(4, 11);
|
||||
|
||||
max2831_driver_t max283x = {
|
||||
.bus = &spi_bus_ssp1,
|
||||
.gpio_enable = &gpio_max2831_enable,
|
||||
.gpio_rxtx = &gpio_max2831_rx_enable,
|
||||
.gpio_rxhp = &gpio_max2831_rxhp,
|
||||
.gpio_ld = &gpio_max2831_ld,
|
||||
.target_init = max2831_target_init,
|
||||
.set_mode = max2831_target_set_mode,
|
||||
};
|
||||
#else
|
||||
const ssp_config_t ssp_config_max283x = {
|
||||
/* FIXME speed up once everything is working reliably */
|
||||
/*
|
||||
@@ -186,6 +276,9 @@ const ssp_config_t ssp_config_max283x = {
|
||||
.gpio_select = &gpio_max283x_select,
|
||||
};
|
||||
|
||||
max283x_driver_t max283x = {};
|
||||
#endif
|
||||
|
||||
const ssp_config_t ssp_config_max5864 = {
|
||||
/* FIXME speed up once everything is working reliably */
|
||||
/*
|
||||
@@ -200,17 +293,6 @@ const ssp_config_t ssp_config_max5864 = {
|
||||
.gpio_select = &gpio_max5864_select,
|
||||
};
|
||||
|
||||
spi_bus_t spi_bus_ssp1 = {
|
||||
.obj = (void*) SSP1_BASE,
|
||||
.config = &ssp_config_max5864,
|
||||
.start = spi_ssp_start,
|
||||
.stop = spi_ssp_stop,
|
||||
.transfer = spi_ssp_transfer,
|
||||
.transfer_gather = spi_ssp_transfer_gather,
|
||||
};
|
||||
|
||||
max283x_driver_t max283x = {};
|
||||
|
||||
max5864_driver_t max5864 = {
|
||||
.bus = &spi_bus_ssp1,
|
||||
.target_init = max5864_target_init,
|
||||
@@ -241,10 +323,68 @@ w25q80bv_driver_t spi_flash = {
|
||||
|
||||
sgpio_config_t sgpio_config = {
|
||||
.gpio_q_invert = &gpio_q_invert,
|
||||
.gpio_hw_sync_enable = &gpio_hw_sync_enable,
|
||||
#ifndef PRALINE
|
||||
.gpio_trigger_enable = &gpio_trigger_enable,
|
||||
#endif
|
||||
.slice_mode_multislice = true,
|
||||
};
|
||||
|
||||
#ifdef PRALINE
|
||||
const ssp_config_t ssp_config_ice40_fpga = {
|
||||
.data_bits = SSP_DATA_8BITS,
|
||||
.spi_mode = SSP_CPOL_1_CPHA_1,
|
||||
.serial_clock_rate = 21,
|
||||
.clock_prescale_rate = 2,
|
||||
.gpio_select = &gpio_fpga_cfg_spi_cs,
|
||||
};
|
||||
|
||||
ice40_spi_driver_t ice40 = {
|
||||
.bus = &spi_bus_ssp1,
|
||||
.gpio_select = &gpio_fpga_cfg_spi_cs,
|
||||
.gpio_creset = &gpio_fpga_cfg_creset,
|
||||
.gpio_cdone = &gpio_fpga_cfg_cdone,
|
||||
};
|
||||
|
||||
fpga_driver_t fpga = {
|
||||
.bus = &ice40,
|
||||
};
|
||||
#endif
|
||||
|
||||
radio_t radio = {
|
||||
.channel[RADIO_CHANNEL0] =
|
||||
{
|
||||
.id = RADIO_CHANNEL0,
|
||||
.config =
|
||||
{
|
||||
.sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN] =
|
||||
{.hz = 0},
|
||||
.filter[RADIO_FILTER_BASEBAND] = {.hz = 0},
|
||||
.frequency[RADIO_FREQUENCY_RF] =
|
||||
{
|
||||
.hz = 0,
|
||||
.if_hz = 0,
|
||||
.lo_hz = 0,
|
||||
.path = 0,
|
||||
},
|
||||
.gain[RADIO_GAIN_RF_AMP] = {.enable = 0},
|
||||
.gain[RADIO_GAIN_RX_LNA] = {.db = 0},
|
||||
.gain[RADIO_GAIN_RX_VGA] = {.db = 0},
|
||||
.gain[RADIO_GAIN_TX_VGA] = {.db = 0},
|
||||
.antenna[RADIO_ANTENNA_BIAS_TEE] =
|
||||
{.enable = false},
|
||||
.mode = TRANSCEIVER_MODE_OFF,
|
||||
.clock[RADIO_CLOCK_CLKIN] = {.enable = false},
|
||||
.clock[RADIO_CLOCK_CLKOUT] = {.enable = false},
|
||||
.trigger_enable = false,
|
||||
#ifdef PRALINE
|
||||
.resampling_n = 0,
|
||||
.shift = FPGA_QUARTER_SHIFT_MODE_NONE,
|
||||
#endif
|
||||
},
|
||||
.clock_source = CLOCK_SOURCE_HACKRF,
|
||||
},
|
||||
};
|
||||
|
||||
rf_path_t rf_path = {
|
||||
.switchctrl = 0,
|
||||
#ifdef HACKRF_ONE
|
||||
@@ -275,14 +415,23 @@ rf_path_t rf_path = {
|
||||
.gpio_tx_amp = &gpio_tx_amp,
|
||||
.gpio_rx_lna = &gpio_rx_lna,
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
.gpio_tx_en = &gpio_tx_en,
|
||||
.gpio_mix_en_n = &gpio_mix_en_n,
|
||||
.gpio_lpf_en = &gpio_lpf_en,
|
||||
.gpio_rf_amp_en = &gpio_rf_amp_en,
|
||||
.gpio_ant_bias_en_n = &gpio_ant_bias_en_n,
|
||||
#endif
|
||||
};
|
||||
|
||||
jtag_gpio_t jtag_gpio_cpld = {
|
||||
.gpio_tms = &gpio_cpld_tms,
|
||||
.gpio_tck = &gpio_cpld_tck,
|
||||
#ifndef PRALINE
|
||||
.gpio_tms = &gpio_cpld_tms,
|
||||
.gpio_tdi = &gpio_cpld_tdi,
|
||||
.gpio_tdo = &gpio_cpld_tdo,
|
||||
#ifdef HACKRF_ONE
|
||||
#endif
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
.gpio_pp_tms = &gpio_cpld_pp_tms,
|
||||
.gpio_pp_tdo = &gpio_cpld_pp_tdo,
|
||||
#endif
|
||||
@@ -406,6 +555,7 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
|
||||
MSx_P2 = (128 * b) % c;
|
||||
MSx_P3 = c;
|
||||
|
||||
#ifndef PRALINE
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
/*
|
||||
* On HackRF One r9 all sample clocks are externally derived
|
||||
@@ -426,6 +576,10 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
|
||||
/* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */
|
||||
si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0); //p1 doesn't matter
|
||||
}
|
||||
#else
|
||||
/* MS0/CLK0 is the source for the MAX5864/FPGA (AFE_CLK). */
|
||||
si5351c_configure_multisynth(&clock_gen, 0, MSx_P1, MSx_P2, MSx_P3, 1);
|
||||
#endif
|
||||
|
||||
if (streaming) {
|
||||
sgpio_cpld_stream_enable(&sgpio_config);
|
||||
@@ -486,6 +640,7 @@ bool sample_rate_set(const uint32_t sample_rate_hz)
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef PRALINE
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
/*
|
||||
* On HackRF One r9 all sample clocks are externally derived
|
||||
@@ -518,22 +673,17 @@ bool sample_rate_set(const uint32_t sample_rate_hz)
|
||||
1,
|
||||
0); //p1 doesn't matter
|
||||
}
|
||||
#else
|
||||
/* MS0/CLK0 is the source for the MAX5864/FPGA (AFE_CLK). */
|
||||
si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1);
|
||||
|
||||
/* MS0/CLK1 is the source for SCT_CLK (CODEC_X2_CLK). */
|
||||
si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool baseband_filter_bandwidth_set(const uint32_t bandwidth_hz)
|
||||
{
|
||||
uint32_t bandwidth_hz_real;
|
||||
bandwidth_hz_real = max283x_set_lpf_bandwidth(&max283x, bandwidth_hz);
|
||||
|
||||
if (bandwidth_hz_real) {
|
||||
hackrf_ui()->set_filter_bw(bandwidth_hz_real);
|
||||
}
|
||||
|
||||
return bandwidth_hz_real != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Configure PLL1 (Main MCU Clock) to max speed (204MHz).
|
||||
Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1.
|
||||
@@ -613,73 +763,6 @@ void cpu_clock_init(void)
|
||||
/* use IRC as clock source for APB3 */
|
||||
CGU_BASE_APB3_CLK = CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_IRC);
|
||||
|
||||
i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock);
|
||||
|
||||
si5351c_init(&clock_gen);
|
||||
si5351c_disable_all_outputs(&clock_gen);
|
||||
si5351c_disable_oeb_pin_control(&clock_gen);
|
||||
si5351c_power_down_all_clocks(&clock_gen);
|
||||
si5351c_set_crystal_configuration(&clock_gen);
|
||||
si5351c_enable_xo_and_ms_fanout(&clock_gen);
|
||||
si5351c_configure_pll_sources(&clock_gen);
|
||||
si5351c_configure_pll_multisynth(&clock_gen);
|
||||
|
||||
/*
|
||||
* Clocks on HackRF One r9:
|
||||
* CLK0 -> MAX5864/CPLD/SGPIO (sample clocks)
|
||||
* CLK1 -> RFFC5072/MAX2839
|
||||
* CLK2 -> External Clock Output/LPC43xx (power down at boot)
|
||||
*
|
||||
* Clocks on other platforms:
|
||||
* CLK0 -> MAX5864/CPLD
|
||||
* CLK1 -> CPLD
|
||||
* CLK2 -> SGPIO
|
||||
* CLK3 -> External Clock Output (power down at boot)
|
||||
* CLK4 -> RFFC5072 (MAX2837 on rad1o)
|
||||
* CLK5 -> MAX2837 (MAX2871 on rad1o)
|
||||
* CLK6 -> none
|
||||
* CLK7 -> LPC43xx (uses a 12MHz crystal by default)
|
||||
*/
|
||||
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
/* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
0,
|
||||
20 * 128 - 512,
|
||||
0,
|
||||
1,
|
||||
0); /* 800/20 = 40MHz */
|
||||
} else {
|
||||
/* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
4,
|
||||
20 * 128 - 512,
|
||||
0,
|
||||
1,
|
||||
0); /* 800/20 = 40MHz */
|
||||
/* MS5/CLK5 is the source for the MAX2837 clock input (MAX2871 on rad1o). */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
5,
|
||||
20 * 128 - 512,
|
||||
0,
|
||||
1,
|
||||
0); /* 800/20 = 40MHz */
|
||||
}
|
||||
|
||||
/* MS6/CLK6 is unused. */
|
||||
/* MS7/CLK7 is unused. */
|
||||
|
||||
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
|
||||
sample_rate_set(10000000);
|
||||
|
||||
si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL);
|
||||
// soft reset
|
||||
si5351c_reset_pll(&clock_gen);
|
||||
si5351c_enable_clock_outputs(&clock_gen);
|
||||
|
||||
//FIXME disable I2C
|
||||
/* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */
|
||||
i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock);
|
||||
@@ -744,7 +827,7 @@ void cpu_clock_init(void)
|
||||
CGU_BASE_SSP1_CLK =
|
||||
CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1);
|
||||
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
/* Disable unused clocks */
|
||||
/* Start with PLLs */
|
||||
CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1);
|
||||
@@ -777,7 +860,7 @@ void cpu_clock_init(void)
|
||||
CCU1_CLK_APB1_CAN1_CFG = 0;
|
||||
CCU1_CLK_APB1_I2S_CFG = 0;
|
||||
CCU1_CLK_APB1_MOTOCONPWM_CFG = 0;
|
||||
CCU1_CLK_APB3_ADC0_CFG = 0;
|
||||
//CCU1_CLK_APB3_ADC0_CFG = 0;
|
||||
CCU1_CLK_APB3_ADC1_CFG = 0;
|
||||
CCU1_CLK_APB3_CAN0_CFG = 0;
|
||||
CCU1_CLK_APB3_DAC_CFG = 0;
|
||||
@@ -813,9 +896,97 @@ void cpu_clock_init(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
void clock_gen_init(void)
|
||||
{
|
||||
i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock);
|
||||
|
||||
si5351c_init(&clock_gen);
|
||||
si5351c_disable_all_outputs(&clock_gen);
|
||||
si5351c_disable_oeb_pin_control(&clock_gen);
|
||||
si5351c_power_down_all_clocks(&clock_gen);
|
||||
si5351c_set_crystal_configuration(&clock_gen);
|
||||
si5351c_enable_xo_and_ms_fanout(&clock_gen);
|
||||
si5351c_configure_pll_sources(&clock_gen);
|
||||
si5351c_configure_pll_multisynth(&clock_gen);
|
||||
|
||||
/*
|
||||
* Clocks on HackRF One r9:
|
||||
* CLK0 -> MAX5864/CPLD/SGPIO (sample clocks)
|
||||
* CLK1 -> RFFC5072/MAX2839
|
||||
* CLK2 -> External Clock Output/LPC43xx (power down at boot)
|
||||
*
|
||||
* Clocks on other platforms:
|
||||
* CLK0 -> MAX5864/CPLD
|
||||
* CLK1 -> CPLD
|
||||
* CLK2 -> SGPIO
|
||||
* CLK3 -> External Clock Output (power down at boot)
|
||||
* CLK4 -> RFFC5072 (MAX2837 on rad1o)
|
||||
* CLK5 -> MAX2837 (MAX2871 on rad1o)
|
||||
* CLK6 -> none
|
||||
* CLK7 -> LPC43xx (uses a 12MHz crystal by default)
|
||||
*
|
||||
* Clocks on Praline:
|
||||
* CLK0 -> AFE_CLK (MAX5864/FPGA)
|
||||
* CLK1 -> SCT_CLK
|
||||
* CLK2 -> MCU_CLK (uses a 12MHz crystal by default)
|
||||
* CLK3 -> External Clock Output (power down at boot)
|
||||
* CLK4 -> XCVR_CLK (MAX2837)
|
||||
* CLK5 -> MIX_CLK (RFFC5072)
|
||||
* CLK6 -> AUX_CLK1
|
||||
* CLK7 -> AUX_CLK2
|
||||
*/
|
||||
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
/* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
0,
|
||||
20 * 128 - 512,
|
||||
0,
|
||||
1,
|
||||
0); /* 800/20 = 40MHz */
|
||||
} else {
|
||||
/* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
4,
|
||||
20 * 128 - 512,
|
||||
0,
|
||||
1,
|
||||
0); /* 800/20 = 40MHz */
|
||||
/* MS5/CLK5 is the source for the MAX2837 clock input (MAX2871 on rad1o). */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
5,
|
||||
20 * 128 - 512,
|
||||
0,
|
||||
1,
|
||||
0); /* 800/20 = 40MHz */
|
||||
}
|
||||
|
||||
/* MS6/CLK6 is unused. */
|
||||
/* MS7/CLK7 is unused. */
|
||||
|
||||
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
|
||||
sample_rate_set(10000000);
|
||||
|
||||
si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL);
|
||||
// soft reset
|
||||
si5351c_reset_pll(&clock_gen);
|
||||
si5351c_enable_clock_outputs(&clock_gen);
|
||||
}
|
||||
|
||||
void clock_gen_shutdown(void)
|
||||
{
|
||||
i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock);
|
||||
si5351c_disable_all_outputs(&clock_gen);
|
||||
si5351c_disable_oeb_pin_control(&clock_gen);
|
||||
si5351c_power_down_all_clocks(&clock_gen);
|
||||
}
|
||||
|
||||
clock_source_t activate_best_clock_source(void)
|
||||
{
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
/* Ensure PortaPack reference oscillator is off while checking for external clock input. */
|
||||
if (portapack_reference_oscillator && portapack()) {
|
||||
portapack_reference_oscillator(false);
|
||||
@@ -828,7 +999,7 @@ clock_source_t activate_best_clock_source(void)
|
||||
if (si5351c_clkin_signal_valid(&clock_gen)) {
|
||||
source = CLOCK_SOURCE_EXTERNAL;
|
||||
} else {
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
/* Enable PortaPack reference oscillator (if present), and check for valid clock. */
|
||||
if (portapack_reference_oscillator && portapack()) {
|
||||
portapack_reference_oscillator(true);
|
||||
@@ -847,6 +1018,8 @@ clock_source_t activate_best_clock_source(void)
|
||||
&clock_gen,
|
||||
(source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN);
|
||||
hackrf_ui()->set_clock_source(source);
|
||||
radio.channel[RADIO_CHANNEL0].clock_source = source;
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -860,7 +1033,14 @@ void ssp1_set_mode_max5864(void)
|
||||
spi_bus_start(max5864.bus, &ssp_config_max5864);
|
||||
}
|
||||
|
||||
void pin_setup(void)
|
||||
#ifdef PRALINE
|
||||
void ssp1_set_mode_ice40(void)
|
||||
{
|
||||
spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga);
|
||||
}
|
||||
#endif
|
||||
|
||||
void pin_shutdown(void)
|
||||
{
|
||||
/* Configure all GPIO as Input (safe state) */
|
||||
gpio_init();
|
||||
@@ -879,14 +1059,16 @@ void pin_setup(void)
|
||||
*
|
||||
* LPC43xx pull-up and pull-down resistors are approximately 53K.
|
||||
*/
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
scu_pinmux(SCU_PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
#ifndef PRALINE
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
|
||||
/* Configure SCU Pin Mux as GPIO */
|
||||
scu_pinmux(SCU_PINMUX_LED1, SCU_GPIO_NOPULL);
|
||||
@@ -895,6 +1077,9 @@ void pin_setup(void)
|
||||
#ifdef RAD1O
|
||||
scu_pinmux(SCU_PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4);
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
scu_pinmux(SCU_PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
|
||||
/* Configure USB indicators */
|
||||
#ifdef JAWBREAKER
|
||||
@@ -902,31 +1087,35 @@ void pin_setup(void)
|
||||
scu_pinmux(SCU_PINMUX_USB_LED1, SCU_CONF_FUNCTION3);
|
||||
#endif
|
||||
|
||||
gpio_output(&gpio_led[0]);
|
||||
gpio_output(&gpio_led[1]);
|
||||
gpio_output(&gpio_led[2]);
|
||||
#ifdef RAD1O
|
||||
gpio_output(&gpio_led[3]);
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
disable_1v2_power();
|
||||
disable_3v3aux_power();
|
||||
gpio_output(&gpio_1v2_enable);
|
||||
gpio_output(&gpio_3v3aux_enable_n);
|
||||
scu_pinmux(SCU_PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
|
||||
#else
|
||||
disable_1v8_power();
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
#ifdef HACKRF_ONE
|
||||
#ifdef HACKRF_ONE
|
||||
gpio_output(&gpio_h1r9_1v8_enable);
|
||||
scu_pinmux(SCU_H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
#endif
|
||||
} else {
|
||||
gpio_output(&gpio_1v8_enable);
|
||||
scu_pinmux(SCU_PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
/* Safe state: start with VAA turned off: */
|
||||
disable_rf_power();
|
||||
|
||||
/* Configure RF power supply (VAA) switch control signal as output */
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
#ifdef HACKRF_ONE
|
||||
gpio_output(&gpio_h1r9_vaa_disable);
|
||||
#endif
|
||||
} else {
|
||||
gpio_output(&gpio_vaa_disable);
|
||||
}
|
||||
@@ -945,22 +1134,84 @@ void pin_setup(void)
|
||||
|
||||
scu_pinmux(SCU_PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
scu_pinmux(SCU_P2_CTRL0, SCU_P2_CTRL0_PINCFG);
|
||||
scu_pinmux(SCU_P2_CTRL1, SCU_P2_CTRL1_PINCFG);
|
||||
scu_pinmux(SCU_P1_CTRL0, SCU_P1_CTRL0_PINCFG);
|
||||
scu_pinmux(SCU_P1_CTRL1, SCU_P1_CTRL1_PINCFG);
|
||||
scu_pinmux(SCU_P1_CTRL2, SCU_P1_CTRL2_PINCFG);
|
||||
scu_pinmux(SCU_CLKIN_CTRL, SCU_CLKIN_CTRL_PINCFG);
|
||||
scu_pinmux(SCU_AA_EN, SCU_AA_EN_PINCFG);
|
||||
scu_pinmux(SCU_TRIGGER_IN, SCU_TRIGGER_IN_PINCFG);
|
||||
scu_pinmux(SCU_TRIGGER_OUT, SCU_TRIGGER_OUT_PINCFG);
|
||||
scu_pinmux(SCU_PPS_OUT, SCU_PPS_OUT_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(SCU_PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
|
||||
p2_ctrl_set(P2_SIGNAL_CLK3);
|
||||
p1_ctrl_set(P1_SIGNAL_CLKIN);
|
||||
narrowband_filter_set(0);
|
||||
clkin_ctrl_set(CLKIN_SIGNAL_P22);
|
||||
|
||||
gpio_output(&gpio_p2_ctrl0);
|
||||
gpio_output(&gpio_p2_ctrl1);
|
||||
gpio_output(&gpio_p1_ctrl0);
|
||||
gpio_output(&gpio_p1_ctrl1);
|
||||
gpio_output(&gpio_p1_ctrl2);
|
||||
gpio_output(&gpio_clkin_ctrl);
|
||||
gpio_output(&gpio_pps_out);
|
||||
gpio_output(&gpio_aa_en);
|
||||
gpio_input(&gpio_trigger_in);
|
||||
gpio_input(&gpio_trigger_out);
|
||||
gpio_clear(&gpio_fpga_cfg_spi_cs);
|
||||
gpio_output(&gpio_fpga_cfg_spi_cs);
|
||||
gpio_clear(&gpio_fpga_cfg_creset);
|
||||
gpio_output(&gpio_fpga_cfg_creset);
|
||||
gpio_input(&gpio_fpga_cfg_cdone);
|
||||
#endif
|
||||
|
||||
/* enable input on SCL and SDA pins */
|
||||
SCU_SFSI2C0 = SCU_I2C0_NOMINAL;
|
||||
}
|
||||
|
||||
spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x);
|
||||
/* Run after pin_shutdown() and prior to enabling power supplies. */
|
||||
void pin_setup(void)
|
||||
{
|
||||
led_off(0);
|
||||
led_off(1);
|
||||
led_off(2);
|
||||
#ifdef RAD1O
|
||||
led_off(3);
|
||||
#endif
|
||||
|
||||
gpio_output(&gpio_led[0]);
|
||||
gpio_output(&gpio_led[1]);
|
||||
gpio_output(&gpio_led[2]);
|
||||
#if (defined RAD1O || defined PRALINE)
|
||||
gpio_output(&gpio_led[3]);
|
||||
#endif
|
||||
|
||||
ssp1_set_mode_max283x();
|
||||
|
||||
mixer_bus_setup(&mixer);
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
rf_path.gpio_rx = &gpio_h1r9_rx;
|
||||
sgpio_config.gpio_hw_sync_enable = &gpio_h1r9_hw_sync_enable;
|
||||
sgpio_config.gpio_trigger_enable = &gpio_h1r9_trigger_enable;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
board_rev_t rev = detected_revision();
|
||||
if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) {
|
||||
rf_path.gpio_mix_en_n = &gpio_mix_en_n_r1_0;
|
||||
}
|
||||
#endif
|
||||
|
||||
rf_path_pin_setup(&rf_path);
|
||||
|
||||
/* Configure external clock in */
|
||||
@@ -969,12 +1220,33 @@ void pin_setup(void)
|
||||
sgpio_configure_pin_functions(&sgpio_config);
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
void enable_1v2_power(void)
|
||||
{
|
||||
gpio_set(&gpio_1v2_enable);
|
||||
}
|
||||
|
||||
void disable_1v2_power(void)
|
||||
{
|
||||
gpio_clear(&gpio_1v2_enable);
|
||||
}
|
||||
|
||||
void enable_3v3aux_power(void)
|
||||
{
|
||||
gpio_clear(&gpio_3v3aux_enable_n);
|
||||
}
|
||||
|
||||
void disable_3v3aux_power(void)
|
||||
{
|
||||
gpio_set(&gpio_3v3aux_enable_n);
|
||||
}
|
||||
#else
|
||||
void enable_1v8_power(void)
|
||||
{
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
#ifdef HACKRF_ONE
|
||||
#ifdef HACKRF_ONE
|
||||
gpio_set(&gpio_h1r9_1v8_enable);
|
||||
#endif
|
||||
#endif
|
||||
} else {
|
||||
gpio_set(&gpio_1v8_enable);
|
||||
}
|
||||
@@ -983,13 +1255,14 @@ void enable_1v8_power(void)
|
||||
void disable_1v8_power(void)
|
||||
{
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
#ifdef HACKRF_ONE
|
||||
#ifdef HACKRF_ONE
|
||||
gpio_clear(&gpio_h1r9_1v8_enable);
|
||||
#endif
|
||||
#endif
|
||||
} else {
|
||||
gpio_clear(&gpio_1v8_enable);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
void enable_rf_power(void)
|
||||
@@ -1018,6 +1291,21 @@ void disable_rf_power(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
void enable_rf_power(void)
|
||||
{
|
||||
gpio_clear(&gpio_vaa_disable);
|
||||
|
||||
/* Let the voltage stabilize */
|
||||
delay(1000000);
|
||||
}
|
||||
|
||||
void disable_rf_power(void)
|
||||
{
|
||||
gpio_set(&gpio_vaa_disable);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RAD1O
|
||||
void enable_rf_power(void)
|
||||
{
|
||||
@@ -1033,6 +1321,17 @@ void disable_rf_power(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
void led_on(const led_t led)
|
||||
{
|
||||
gpio_clear(&gpio_led[led]);
|
||||
}
|
||||
|
||||
void led_off(const led_t led)
|
||||
{
|
||||
gpio_set(&gpio_led[led]);
|
||||
}
|
||||
#else
|
||||
void led_on(const led_t led)
|
||||
{
|
||||
gpio_set(&gpio_led[led]);
|
||||
@@ -1042,6 +1341,7 @@ void led_off(const led_t led)
|
||||
{
|
||||
gpio_clear(&gpio_led[led]);
|
||||
}
|
||||
#endif
|
||||
|
||||
void led_toggle(const led_t led)
|
||||
{
|
||||
@@ -1051,17 +1351,25 @@ void led_toggle(const led_t led)
|
||||
void set_leds(const uint8_t state)
|
||||
{
|
||||
int num_leds = 3;
|
||||
#ifdef RAD1O
|
||||
#if (defined RAD1O || defined PRALINE)
|
||||
num_leds = 4;
|
||||
#endif
|
||||
for (int i = 0; i < num_leds; i++) {
|
||||
#ifdef PRALINE
|
||||
gpio_write(&gpio_led[i], ((state >> i) & 1) == 0);
|
||||
#else
|
||||
gpio_write(&gpio_led[i], ((state >> i) & 1) == 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void hw_sync_enable(const hw_sync_mode_t hw_sync_mode)
|
||||
void trigger_enable(const bool enable)
|
||||
{
|
||||
gpio_write(sgpio_config.gpio_hw_sync_enable, hw_sync_mode == 1);
|
||||
#ifndef PRALINE
|
||||
gpio_write(sgpio_config.gpio_trigger_enable, enable);
|
||||
#else
|
||||
fpga_set_trigger_enable(&fpga, enable);
|
||||
#endif
|
||||
}
|
||||
|
||||
void halt_and_flash(const uint32_t duration)
|
||||
@@ -1078,3 +1386,33 @@ void halt_and_flash(const uint32_t duration)
|
||||
delay(duration);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
void p1_ctrl_set(const p1_ctrl_signal_t signal)
|
||||
{
|
||||
gpio_write(&gpio_p1_ctrl0, signal & 1);
|
||||
gpio_write(&gpio_p1_ctrl1, (signal >> 1) & 1);
|
||||
gpio_write(&gpio_p1_ctrl2, (signal >> 2) & 1);
|
||||
}
|
||||
|
||||
void p2_ctrl_set(const p2_ctrl_signal_t signal)
|
||||
{
|
||||
gpio_write(&gpio_p2_ctrl0, signal & 1);
|
||||
gpio_write(&gpio_p2_ctrl1, (signal >> 1) & 1);
|
||||
}
|
||||
|
||||
void clkin_ctrl_set(const clkin_signal_t signal)
|
||||
{
|
||||
gpio_write(&gpio_clkin_ctrl, signal & 1);
|
||||
}
|
||||
|
||||
void pps_out_set(const uint8_t value)
|
||||
{
|
||||
gpio_write(&gpio_pps_out, value & 1);
|
||||
}
|
||||
|
||||
void narrowband_filter_set(const uint8_t value)
|
||||
{
|
||||
gpio_write(&gpio_aa_en, value & 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -34,13 +34,17 @@ extern "C" {
|
||||
#include "si5351c.h"
|
||||
#include "spi_ssp.h"
|
||||
|
||||
#include "max2831.h"
|
||||
#include "max283x.h"
|
||||
#include "max5864.h"
|
||||
#include "mixer.h"
|
||||
#include "w25q80bv.h"
|
||||
#include "sgpio.h"
|
||||
#include "radio.h"
|
||||
#include "rf_path.h"
|
||||
#include "cpld_jtag.h"
|
||||
#include "ice40_spi.h"
|
||||
#include "fpga.h"
|
||||
|
||||
/*
|
||||
* SCU PinMux
|
||||
@@ -53,8 +57,16 @@ extern "C" {
|
||||
#ifdef RAD1O
|
||||
#define SCU_PINMUX_LED4 (PB_6) /* GPIO5[26] on PB_6 */
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
#define SCU_PINMUX_LED4 (P8_6) /* GPIO4[6] on P8_6 */
|
||||
#endif
|
||||
|
||||
#define SCU_PINMUX_EN1V8 (P6_10) /* GPIO3[6] on P6_10 */
|
||||
#define SCU_PINMUX_EN1V2 (P8_7) /* GPIO4[7] on P8_7 */
|
||||
#ifdef PRALINE
|
||||
#define SCU_PINMUX_EN3V3_AUX_N (P6_7) /* GPIO5[15] on P6_7 */
|
||||
#define SCU_PINMUX_EN3V3_OC_N (P6_11) /* GPIO3[7] on P6_11 */
|
||||
#endif
|
||||
|
||||
/* GPIO Input PinMux */
|
||||
#define SCU_PINMUX_BOOT0 (P1_1) /* GPIO0[8] on P1_1 */
|
||||
@@ -82,9 +94,14 @@ extern "C" {
|
||||
#define SCU_SSP1_CS (P1_20) /* P1_20 */
|
||||
|
||||
/* CPLD JTAG interface */
|
||||
#ifdef PRALINE
|
||||
#define SCU_PINMUX_FPGA_CRESET (P5_2) /* GPIO2[11] on P5_2 */
|
||||
#define SCU_PINMUX_FPGA_CDONE (P4_10) /* GPIO5[14] */
|
||||
#define SCU_PINMUX_FPGA_SPI_CS (P5_1) /* GPIO2[10] */
|
||||
#endif
|
||||
#define SCU_PINMUX_CPLD_TDO (P9_5) /* GPIO5[18] */
|
||||
#define SCU_PINMUX_CPLD_TCK (P6_1) /* GPIO3[ 0] */
|
||||
#if (defined HACKRF_ONE || defined RAD1O)
|
||||
#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE)
|
||||
#define SCU_PINMUX_CPLD_TMS (P6_5) /* GPIO3[ 4] */
|
||||
#define SCU_PINMUX_CPLD_TDI (P6_2) /* GPIO3[ 1] */
|
||||
#else
|
||||
@@ -93,24 +110,76 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* CPLD SGPIO interface */
|
||||
#define SCU_PINMUX_SGPIO0 (P0_0)
|
||||
#define SCU_PINMUX_SGPIO1 (P0_1)
|
||||
#define SCU_PINMUX_SGPIO2 (P1_15)
|
||||
#define SCU_PINMUX_SGPIO3 (P1_16)
|
||||
#define SCU_PINMUX_SGPIO4 (P6_3)
|
||||
#define SCU_PINMUX_SGPIO5 (P6_6)
|
||||
#define SCU_PINMUX_SGPIO6 (P2_2)
|
||||
#define SCU_PINMUX_SGPIO7 (P1_0)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O)
|
||||
#define SCU_PINMUX_SGPIO8 (P9_6)
|
||||
#ifdef PRALINE
|
||||
#define SCU_PINMUX_SGPIO0 (P0_0)
|
||||
#define SCU_PINMUX_SGPIO1 (P0_1)
|
||||
#define SCU_PINMUX_SGPIO2 (P1_15)
|
||||
#define SCU_PINMUX_SGPIO3 (P1_16)
|
||||
#define SCU_PINMUX_SGPIO4 (P9_4)
|
||||
#define SCU_PINMUX_SGPIO5 (P6_6)
|
||||
#define SCU_PINMUX_SGPIO6 (P2_2)
|
||||
#define SCU_PINMUX_SGPIO7 (P1_0)
|
||||
#define SCU_PINMUX_SGPIO8 (P8_0)
|
||||
#define SCU_PINMUX_SGPIO9 (P9_3)
|
||||
#define SCU_PINMUX_SGPIO10 (P8_2)
|
||||
#define SCU_PINMUX_SGPIO11 (P1_17)
|
||||
#define SCU_PINMUX_SGPIO12 (P1_18)
|
||||
#define SCU_PINMUX_SGPIO14 (P1_18)
|
||||
#define SCU_PINMUX_SGPIO15 (P1_18)
|
||||
|
||||
#define SCU_PINMUX_SGPIO0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
|
||||
#define SCU_PINMUX_SGPIO1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
|
||||
#define SCU_PINMUX_SGPIO2_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO3_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO4_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO5_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO6_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_PINMUX_SGPIO7_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO8_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_PINMUX_SGPIO9_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO10_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_PINMUX_SGPIO11_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO12_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_PINMUX_SGPIO14_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_PINMUX_SGPIO15_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
|
||||
#else
|
||||
#define SCU_PINMUX_SGPIO0 (P0_0)
|
||||
#define SCU_PINMUX_SGPIO1 (P0_1)
|
||||
#define SCU_PINMUX_SGPIO2 (P1_15)
|
||||
#define SCU_PINMUX_SGPIO3 (P1_16)
|
||||
#define SCU_PINMUX_SGPIO4 (P6_3)
|
||||
#define SCU_PINMUX_SGPIO5 (P6_6)
|
||||
#define SCU_PINMUX_SGPIO6 (P2_2)
|
||||
#define SCU_PINMUX_SGPIO7 (P1_0)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O)
|
||||
#define SCU_PINMUX_SGPIO8 (P9_6)
|
||||
#endif
|
||||
#define SCU_PINMUX_SGPIO9 (P4_3)
|
||||
#define SCU_PINMUX_SGPIO10 (P1_14)
|
||||
#define SCU_PINMUX_SGPIO11 (P1_17)
|
||||
#define SCU_PINMUX_SGPIO12 (P1_18)
|
||||
#define SCU_PINMUX_SGPIO14 (P4_9)
|
||||
#define SCU_PINMUX_SGPIO15 (P4_10)
|
||||
|
||||
#define SCU_PINMUX_SGPIO0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
|
||||
#define SCU_PINMUX_SGPIO1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
|
||||
#define SCU_PINMUX_SGPIO2_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO3_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO4_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO5_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
|
||||
#define SCU_PINMUX_SGPIO6_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_PINMUX_SGPIO7_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO8_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO9_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION7)
|
||||
#define SCU_PINMUX_SGPIO10_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO11_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
|
||||
#define SCU_PINMUX_SGPIO12_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_PINMUX_SGPIO14_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_PINMUX_SGPIO15_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
|
||||
#endif
|
||||
#define SCU_PINMUX_SGPIO9 (P4_3)
|
||||
#define SCU_PINMUX_SGPIO10 (P1_14)
|
||||
#define SCU_PINMUX_SGPIO11 (P1_17)
|
||||
#define SCU_PINMUX_SGPIO12 (P1_18)
|
||||
#define SCU_PINMUX_SGPIO14 (P4_9)
|
||||
#define SCU_PINMUX_SGPIO15 (P4_10)
|
||||
#define SCU_HW_SYNC_EN (P4_8) /* GPIO5[12] on P4_8 */
|
||||
#define SCU_TRIGGER_EN (P4_8) /* GPIO5[12] on P4_8 */
|
||||
|
||||
/* MAX2837 GPIO (XCVR_CTL) PinMux */
|
||||
#ifdef RAD1O
|
||||
@@ -119,13 +188,40 @@ extern "C" {
|
||||
#define SCU_XCVR_B7 (P9_3) /* GPIO[] on P8_3 */
|
||||
#endif
|
||||
|
||||
#define SCU_XCVR_ENABLE (P4_6) /* GPIO2[6] on P4_6 */
|
||||
#define SCU_XCVR_RXENABLE (P4_5) /* GPIO2[5] on P4_5 */
|
||||
#define SCU_XCVR_TXENABLE (P4_4) /* GPIO2[4] on P4_4 */
|
||||
#define SCU_XCVR_CS (P1_20) /* GPIO0[15] on P1_20 */
|
||||
#ifdef PRALINE
|
||||
#define SCU_XCVR_ENABLE (PE_1) /* GPIO7[1] on PE_1 */
|
||||
#define SCU_XCVR_RXENABLE (PE_2) /* GPIO7[2] on PE_2 */
|
||||
#define SCU_XCVR_CS (PD_14) /* GPIO6[28] on PD_14 */
|
||||
#define SCU_XCVR_RXHP (PD_15) /* GPIO6[29] on PD_15 */
|
||||
#define SCU_XCVR_LD (P9_6) /* GPIO4[11] on P9_6 */
|
||||
|
||||
#define SCU_XCVR_ENABLE_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_XCVR_RXENABLE_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_XCVR_CS_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_XCVR_RXHP_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_XCVR_LD_PINCFG \
|
||||
(SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | SCU_CONF_EPD_EN_PULLDOWN | \
|
||||
SCU_CONF_EPUN_DIS_PULLUP)
|
||||
#else
|
||||
#define SCU_XCVR_ENABLE (P4_6) /* GPIO2[6] on P4_6 */
|
||||
#define SCU_XCVR_RXENABLE (P4_5) /* GPIO2[5] on P4_5 */
|
||||
#define SCU_XCVR_TXENABLE (P4_4) /* GPIO2[4] on P4_4 */
|
||||
#define SCU_XCVR_CS (P1_20) /* GPIO0[15] on P1_20 */
|
||||
|
||||
#define SCU_XCVR_ENABLE_PINCFG (SCU_GPIO_FAST)
|
||||
#define SCU_XCVR_RXENABLE_PINCFG (SCU_GPIO_FAST)
|
||||
#define SCU_XCVR_TXENABLE_PINCFG (SCU_GPIO_FAST)
|
||||
#define SCU_XCVR_CS_PINCFG (SCU_GPIO_FAST)
|
||||
#endif
|
||||
|
||||
/* MAX5864 SPI chip select (AD_CS) GPIO PinMux */
|
||||
#define SCU_AD_CS (P5_7) /* GPIO2[7] on P5_7 */
|
||||
#ifdef PRALINE
|
||||
#define SCU_AD_CS (PD_16) /* GPIO6[30] on PD_16 */
|
||||
#define SCU_AD_CS_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#else
|
||||
#define SCU_AD_CS (P5_7) /* GPIO2[7] on P5_7 */
|
||||
#define SCU_AD_CS_PINCFG (SCU_GPIO_FAST)
|
||||
#endif
|
||||
|
||||
/* RFFC5071 GPIO serial interface PinMux */
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
@@ -133,6 +229,20 @@ extern "C" {
|
||||
#define SCU_MIXER_SCLK (P2_6) /* GPIO5[6] on P2_6 */
|
||||
#define SCU_MIXER_SDATA (P6_4) /* GPIO3[3] on P6_4 */
|
||||
#define SCU_MIXER_RESETX (P5_5) /* GPIO2[14] on P5_5 */
|
||||
|
||||
#define SCU_MIXER_SCLK_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_MIXER_SDATA_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
#define SCU_MIXER_ENX (P5_4) /* GPIO2[13] on P5_4 */
|
||||
#define SCU_MIXER_SCLK (P9_5) /* GPIO5[18] on P9_5 */
|
||||
#define SCU_MIXER_SDATA (P9_2) /* GPIO4[14] on P9_2 */
|
||||
#define SCU_MIXER_RESETX (P5_5) /* GPIO2[14] on P5_5 */
|
||||
#define SCU_MIXER_LD (PD_11) /* GPIO6[25] on PD_11 */
|
||||
|
||||
#define SCU_MIXER_SCLK_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_MIXER_SDATA_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_MIXER_LD_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
#define SCU_VCO_CE (P5_4) /* GPIO2[13] on P5_4 */
|
||||
@@ -153,6 +263,9 @@ extern "C" {
|
||||
#ifdef HACKRF_ONE
|
||||
#define SCU_NO_VAA_ENABLE (P5_0) /* GPIO2[9] on P5_0 */
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
#define SCU_NO_VAA_ENABLE (P8_1) /* GPIO4[1] on P8_1 */
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
#define SCU_VAA_ENABLE (P5_0) /* GPIO2[9] on P5_0 */
|
||||
#endif
|
||||
@@ -193,6 +306,40 @@ extern "C" {
|
||||
#define SCU_TX_AMP (P5_6) /* GPIO2[15] on P5_6 */
|
||||
#define SCU_RX_LNA (P6_7) /* GPIO5[15] on P6_7 */
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
#define SCU_TX_EN (P6_5) /* GPIO3[4] on P6_5 */
|
||||
#define SCU_MIX_EN_N (P6_3) /* GPIO3[2] on P6_3 */
|
||||
#define SCU_MIX_EN_N_R1_0 (P2_6) /* GPIO5[6] on P2_6 */
|
||||
#define SCU_LPF_EN (PA_1) /* GPIO4[8] on PA_1 */
|
||||
#define SCU_RF_AMP_EN (PA_2) /* GPIO4[9] on PA_2 */
|
||||
#define SCU_ANT_BIAS_EN_N (P2_12) /* GPIO1[12] on P2_12 */
|
||||
#define SCU_ANT_BIAS_OC_N (P2_11) /* GPIO1[11] on P2_11 */
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
#define SCU_P2_CTRL0 (PE_3) /* GPIO7[3] on PE_3 */
|
||||
#define SCU_P2_CTRL1 (PE_4) /* GPIO7[4] on PE_4 */
|
||||
#define SCU_P1_CTRL0 (P2_10) /* GPIO0[14] on P2_10 */
|
||||
#define SCU_P1_CTRL1 (P6_8) /* GPIO5[16] on P6_8 */
|
||||
#define SCU_P1_CTRL2 (P6_9) /* GPIO3[5] on P6_9 */
|
||||
#define SCU_CLKIN_CTRL (P1_20) /* GPIO0[15] on P1_20 */
|
||||
#define SCU_AA_EN (P1_14) /* GPIO1[7] on P1_14 */
|
||||
#define SCU_TRIGGER_IN (PD_12) /* GPIO6[26] on PD_12 */
|
||||
#define SCU_TRIGGER_OUT (P2_6) /* GPIO5[6] on P2_6 */
|
||||
#define SCU_PPS_OUT (P2_5) /* GPIO5[5] on P2_5 */
|
||||
|
||||
#define SCU_P2_CTRL0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_P2_CTRL1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_P1_CTRL0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_P1_CTRL1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_P1_CTRL2_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_CLKIN_CTRL_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_AA_EN_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
|
||||
#define SCU_TRIGGER_IN_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_TRIGGER_OUT_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
#define SCU_PPS_OUT_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
|
||||
|
||||
#endif
|
||||
|
||||
#define SCU_PINMUX_PP_D0 (P7_0) /* GPIO3[8] */
|
||||
#define SCU_PINMUX_PP_D1 (P7_1) /* GPIO3[9] */
|
||||
@@ -240,27 +387,7 @@ extern "C" {
|
||||
#define SCU_H1R9_NO_ANT_PWR (P4_4) /* GPIO2[4] on P4_4 */
|
||||
#define SCU_H1R9_EN1V8 (P5_0) /* GPIO2[9] on P5_0 */
|
||||
#define SCU_H1R9_NO_VAA_EN (P6_10) /* GPIO3[6] on P6_10 */
|
||||
#define SCU_H1R9_HW_SYNC_EN (P2_5) /* GPIO5[5] on P2_5 */
|
||||
|
||||
typedef enum {
|
||||
TRANSCEIVER_MODE_OFF = 0,
|
||||
TRANSCEIVER_MODE_RX = 1,
|
||||
TRANSCEIVER_MODE_TX = 2,
|
||||
TRANSCEIVER_MODE_SS = 3,
|
||||
TRANSCEIVER_MODE_CPLD_UPDATE = 4,
|
||||
TRANSCEIVER_MODE_RX_SWEEP = 5,
|
||||
} transceiver_mode_t;
|
||||
|
||||
typedef enum {
|
||||
HW_SYNC_MODE_OFF = 0,
|
||||
HW_SYNC_MODE_ON = 1,
|
||||
} hw_sync_mode_t;
|
||||
|
||||
typedef enum {
|
||||
CLOCK_SOURCE_HACKRF = 0,
|
||||
CLOCK_SOURCE_EXTERNAL = 1,
|
||||
CLOCK_SOURCE_PORTAPACK = 2,
|
||||
} clock_source_t;
|
||||
#define SCU_H1R9_TRIGGER_EN (P2_5) /* GPIO5[5] on P2_5 */
|
||||
|
||||
void delay(uint32_t duration);
|
||||
void delay_us_at_mhz(uint32_t us, uint32_t mhz);
|
||||
@@ -271,31 +398,52 @@ extern const ssp_config_t ssp_config_w25q80bv;
|
||||
extern const ssp_config_t ssp_config_max283x;
|
||||
extern const ssp_config_t ssp_config_max5864;
|
||||
|
||||
#ifndef PRALINE
|
||||
extern max283x_driver_t max283x;
|
||||
#else
|
||||
extern max2831_driver_t max283x;
|
||||
extern ice40_spi_driver_t ice40;
|
||||
extern fpga_driver_t fpga;
|
||||
|
||||
#endif
|
||||
extern max5864_driver_t max5864;
|
||||
extern mixer_driver_t mixer;
|
||||
extern w25q80bv_driver_t spi_flash;
|
||||
extern sgpio_config_t sgpio_config;
|
||||
extern radio_t radio;
|
||||
extern rf_path_t rf_path;
|
||||
extern jtag_t jtag_cpld;
|
||||
extern i2c_bus_t i2c0;
|
||||
|
||||
void cpu_clock_init(void);
|
||||
void clock_gen_init(void);
|
||||
void clock_gen_shutdown(void);
|
||||
void ssp1_set_mode_max283x(void);
|
||||
void ssp1_set_mode_max5864(void);
|
||||
#ifdef PRALINE
|
||||
void ssp1_set_mode_max2831(void);
|
||||
void ssp1_set_mode_ice40(void);
|
||||
#endif
|
||||
|
||||
void pin_shutdown(void);
|
||||
void pin_setup(void);
|
||||
|
||||
#ifdef PRALINE
|
||||
void enable_1v2_power(void);
|
||||
void disable_1v2_power(void);
|
||||
void enable_3v3aux_power(void);
|
||||
void disable_3v3aux_power(void);
|
||||
#else
|
||||
void enable_1v8_power(void);
|
||||
void disable_1v8_power(void);
|
||||
#endif
|
||||
|
||||
bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom);
|
||||
bool sample_rate_set(const uint32_t sampling_rate_hz);
|
||||
bool baseband_filter_bandwidth_set(const uint32_t bandwidth_hz);
|
||||
|
||||
clock_source_t activate_best_clock_source(void);
|
||||
|
||||
#if (defined HACKRF_ONE || defined RAD1O)
|
||||
#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE)
|
||||
void enable_rf_power(void);
|
||||
void disable_rf_power(void);
|
||||
#endif
|
||||
@@ -312,10 +460,40 @@ void led_off(const led_t led);
|
||||
void led_toggle(const led_t led);
|
||||
void set_leds(const uint8_t state);
|
||||
|
||||
void hw_sync_enable(const hw_sync_mode_t hw_sync_mode);
|
||||
void trigger_enable(const bool enable);
|
||||
|
||||
void halt_and_flash(const uint32_t duration);
|
||||
|
||||
#ifdef PRALINE
|
||||
typedef enum {
|
||||
P1_SIGNAL_TRIGGER_IN = 0,
|
||||
P1_SIGNAL_AUX_CLK1 = 1,
|
||||
P1_SIGNAL_CLKIN = 2,
|
||||
P1_SIGNAL_TRIGGER_OUT = 3,
|
||||
P1_SIGNAL_P22_CLKIN = 4,
|
||||
P1_SIGNAL_P2_5 = 5,
|
||||
P1_SIGNAL_NC = 6,
|
||||
P1_SIGNAL_AUX_CLK2 = 7,
|
||||
} p1_ctrl_signal_t;
|
||||
|
||||
typedef enum {
|
||||
P2_SIGNAL_CLK3 = 0,
|
||||
P2_SIGNAL_TRIGGER_IN = 2,
|
||||
P2_SIGNAL_TRIGGER_OUT = 3,
|
||||
} p2_ctrl_signal_t;
|
||||
|
||||
typedef enum {
|
||||
CLKIN_SIGNAL_P1 = 0,
|
||||
CLKIN_SIGNAL_P22 = 1,
|
||||
} clkin_signal_t;
|
||||
|
||||
void p1_ctrl_set(const p1_ctrl_signal_t signal);
|
||||
void p2_ctrl_set(const p2_ctrl_signal_t signal);
|
||||
void narrowband_filter_set(const uint8_t value);
|
||||
void clkin_ctrl_set(const clkin_signal_t value);
|
||||
void pps_out_set(const uint8_t value);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -78,7 +78,7 @@ const hackrf_ui_t* hackrf_ui(void)
|
||||
{
|
||||
/* Detect on first use. If no UI hardware is detected, use a stub function table. */
|
||||
if (ui == NULL && ui_enabled) {
|
||||
#ifdef HACKRF_ONE
|
||||
#if (defined HACKRF_ONE || defined PRALINE)
|
||||
if (portapack_hackrf_ui_init) {
|
||||
ui = portapack_hackrf_ui_init();
|
||||
}
|
||||
|
||||
124
firmware/common/ice40_spi.c
Normal file
124
firmware/common/ice40_spi.c
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ice40_spi.h"
|
||||
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include "hackrf_core.h"
|
||||
|
||||
void ice40_spi_target_init(ice40_spi_driver_t* const drv)
|
||||
{
|
||||
/* Configure SSP1 Peripheral and relevant FPGA pins. */
|
||||
scu_pinmux(SCU_SSP1_CIPO, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
|
||||
scu_pinmux(SCU_SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
|
||||
scu_pinmux(SCU_SSP1_SCK, (SCU_SSP_IO | SCU_CONF_FUNCTION1));
|
||||
scu_pinmux(SCU_PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(SCU_PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
|
||||
/* Configure GPIOs as inputs or outputs as needed. */
|
||||
gpio_clear(drv->gpio_creset);
|
||||
gpio_output(drv->gpio_creset);
|
||||
gpio_input(drv->gpio_cdone);
|
||||
// select is configured in SSP code
|
||||
}
|
||||
|
||||
uint8_t ice40_spi_read(ice40_spi_driver_t* const drv, uint8_t r)
|
||||
{
|
||||
uint8_t value[3] = {r & 0x7F, 0, 0};
|
||||
spi_bus_transfer(drv->bus, value, 3);
|
||||
return value[2];
|
||||
}
|
||||
|
||||
void ice40_spi_write(ice40_spi_driver_t* const drv, uint8_t r, uint16_t v)
|
||||
{
|
||||
uint8_t value[3] = {(r & 0x7F) | 0x80, v, 0};
|
||||
spi_bus_transfer(drv->bus, value, 3);
|
||||
}
|
||||
|
||||
static void spi_ssp1_wait_for_tx_fifo_not_full()
|
||||
{
|
||||
while ((SSP_SR(SSP1_BASE) & SSP_SR_TNF) == 0) {}
|
||||
}
|
||||
|
||||
static void spi_ssp1_wait_for_rx_fifo_not_empty()
|
||||
{
|
||||
while ((SSP_SR(SSP1_BASE) & SSP_SR_RNE) == 0) {}
|
||||
}
|
||||
|
||||
static void spi_ssp1_wait_for_not_busy()
|
||||
{
|
||||
while (SSP_SR(SSP1_BASE) & SSP_SR_BSY) {}
|
||||
}
|
||||
|
||||
static uint32_t spi_ssp1_transfer_word(const uint32_t data)
|
||||
{
|
||||
spi_ssp1_wait_for_tx_fifo_not_full();
|
||||
SSP_DR(SSP1_BASE) = data;
|
||||
spi_ssp1_wait_for_not_busy();
|
||||
spi_ssp1_wait_for_rx_fifo_not_empty();
|
||||
return SSP_DR(SSP1_BASE);
|
||||
}
|
||||
|
||||
bool ice40_spi_syscfg_program(
|
||||
ice40_spi_driver_t* const drv,
|
||||
size_t (*read_block_cb)(void* ctx, uint8_t* buffer),
|
||||
void* read_ctx)
|
||||
{
|
||||
// Drive CRESET_B = 0, SPI_SS = 0, SPI_SCK = 1.
|
||||
gpio_clear(drv->gpio_creset);
|
||||
gpio_clear(drv->gpio_select);
|
||||
|
||||
// Wait a minimum of 200 ns.
|
||||
delay_us_at_mhz(1, 204 / 4); // 250 ns.
|
||||
|
||||
// Release CRESET_B or drive CRESET_B = 1.
|
||||
gpio_set(drv->gpio_creset);
|
||||
|
||||
// Wait a minimum of 1200 μs to clear internal configuration memory.
|
||||
// Testing showed us that we need to wait longer. Let's wait 1800 μs.
|
||||
delay_us_at_mhz(1800, 204);
|
||||
|
||||
// Set SPI_SS = 1, Send 8 dummy clocks.
|
||||
gpio_set(drv->gpio_select);
|
||||
spi_ssp1_transfer_word(0);
|
||||
|
||||
// Send configuration image serially on SPI_SI to iCE40, most-significant bit
|
||||
// first, on falling edge of SPI_SCK.
|
||||
uint8_t out_buffer[4096] = {0};
|
||||
gpio_clear(drv->gpio_select);
|
||||
for (;;) {
|
||||
size_t read_sz = read_block_cb(read_ctx, out_buffer);
|
||||
if (read_sz == 0)
|
||||
break;
|
||||
for (size_t j = 0; j < read_sz; j++) {
|
||||
spi_ssp1_transfer_word(out_buffer[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for 100 clocks cycles for CDONE to go high.
|
||||
gpio_set(drv->gpio_select);
|
||||
for (size_t j = 0; j < 13; j++) {
|
||||
spi_ssp1_transfer_word(0);
|
||||
}
|
||||
|
||||
return gpio_read(drv->gpio_cdone);
|
||||
}
|
||||
48
firmware/common/ice40_spi.h
Normal file
48
firmware/common/ice40_spi.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __ICE40_SPI_H
|
||||
#define __ICE40_SPI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "gpio.h"
|
||||
#include "spi_bus.h"
|
||||
|
||||
struct ice40_spi_driver_t;
|
||||
typedef struct ice40_spi_driver_t ice40_spi_driver_t;
|
||||
|
||||
struct ice40_spi_driver_t {
|
||||
spi_bus_t* const bus;
|
||||
gpio_t gpio_select;
|
||||
gpio_t gpio_creset;
|
||||
gpio_t gpio_cdone;
|
||||
};
|
||||
|
||||
void ice40_spi_target_init(ice40_spi_driver_t* const drv);
|
||||
uint8_t ice40_spi_read(ice40_spi_driver_t* const drv, uint8_t r);
|
||||
void ice40_spi_write(ice40_spi_driver_t* const drv, uint8_t r, uint16_t v);
|
||||
bool ice40_spi_syscfg_program(
|
||||
ice40_spi_driver_t* const drv,
|
||||
size_t (*read_block_cb)(void* ctx, uint8_t* buffer),
|
||||
void* read_ctx);
|
||||
|
||||
#endif // __ICE40_SPI_H
|
||||
86
firmware/common/lz4_blk.c
Normal file
86
firmware/common/lz4_blk.c
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lz4_blk.h"
|
||||
|
||||
// Decompress raw LZ4 block.
|
||||
int lz4_blk_decompress(const uint8_t* src, uint8_t* dst, size_t length)
|
||||
{
|
||||
const uint8_t* src_end = src + length; // Point to the end of the current block.
|
||||
const uint8_t* dst_0 = dst; // Store original dst pointer to compute output size.
|
||||
|
||||
while (src < src_end) {
|
||||
uint8_t token = *src++;
|
||||
|
||||
// Get the literal length from the high nibble of the token.
|
||||
uint32_t literal_length = token >> 4;
|
||||
|
||||
// If literal length is 15 or more, we need to read additional length bytes.
|
||||
if (literal_length == 0x0F) {
|
||||
uint8_t len;
|
||||
while ((len = *src++) == 0xFF) {
|
||||
literal_length += 0xFF;
|
||||
}
|
||||
literal_length += len;
|
||||
}
|
||||
|
||||
// Copy the literals, if any.
|
||||
if (literal_length > 0) {
|
||||
memcpy(dst, src, literal_length);
|
||||
src += literal_length;
|
||||
dst += literal_length;
|
||||
}
|
||||
|
||||
// If we're at the end, break (no match data to process).
|
||||
if (src >= src_end) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the match offset (2 bytes).
|
||||
uint16_t offset = src[0] | (src[1] << 8);
|
||||
src += 2;
|
||||
|
||||
// Match length (low nibble of token + 4).
|
||||
uint32_t match_length = (token & 0x0F) + 4;
|
||||
|
||||
// If match length is 19 or more, we need to read additional length bytes.
|
||||
if ((token & 0x0F) == 0x0F) {
|
||||
uint8_t len;
|
||||
while ((len = *src++) == 0xFF) {
|
||||
match_length += 0xFF;
|
||||
}
|
||||
match_length += len;
|
||||
}
|
||||
|
||||
// Copy the match data.
|
||||
const uint8_t* match_ptr = dst - offset;
|
||||
for (uint32_t i = 0; i < match_length; i++) {
|
||||
*dst++ = *match_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the size of the output.
|
||||
return dst - dst_0;
|
||||
}
|
||||
30
firmware/common/lz4_blk.h
Normal file
30
firmware/common/lz4_blk.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __LZ4_BLK_H
|
||||
#define __LZ4_BLK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int lz4_blk_decompress(const uint8_t* src, uint8_t* dst, size_t length);
|
||||
|
||||
#endif
|
||||
39
firmware/common/m0_state.c
Normal file
39
firmware/common/m0_state.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "m0_state.h"
|
||||
#include <libopencm3/lpc43xx/sgpio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void m0_set_mode(enum m0_mode mode)
|
||||
{
|
||||
// Set requested mode and flag bit.
|
||||
m0_state.requested_mode = M0_REQUEST_FLAG | mode;
|
||||
|
||||
// The M0 may be blocked waiting for the next SGPIO interrupt.
|
||||
// In order to ensure that it sees our request, we need to set
|
||||
// the interrupt flag here. The M0 will clear the flag again
|
||||
// before acknowledging our request.
|
||||
SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A);
|
||||
|
||||
// Wait for M0 to acknowledge by clearing the flag.
|
||||
while (m0_state.requested_mode & M0_REQUEST_FLAG) {}
|
||||
}
|
||||
64
firmware/common/m0_state.h
Normal file
64
firmware/common/m0_state.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __M0_STATE_H__
|
||||
#define __M0_STATE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define M0_REQUEST_FLAG (1 << 16)
|
||||
|
||||
struct m0_state {
|
||||
uint32_t requested_mode;
|
||||
uint32_t active_mode;
|
||||
uint32_t m0_count;
|
||||
uint32_t m4_count;
|
||||
uint32_t num_shortfalls;
|
||||
uint32_t longest_shortfall;
|
||||
uint32_t shortfall_limit;
|
||||
uint32_t threshold;
|
||||
uint32_t next_mode;
|
||||
uint32_t error;
|
||||
};
|
||||
|
||||
enum m0_mode {
|
||||
M0_MODE_IDLE = 0,
|
||||
M0_MODE_WAIT = 1,
|
||||
M0_MODE_RX = 2,
|
||||
M0_MODE_TX_START = 3,
|
||||
M0_MODE_TX_RUN = 4,
|
||||
};
|
||||
|
||||
enum m0_error {
|
||||
M0_ERROR_NONE = 0,
|
||||
M0_ERROR_RX_TIMEOUT = 1,
|
||||
M0_ERROR_TX_TIMEOUT = 2,
|
||||
};
|
||||
|
||||
/* Address of m0_state is set in ldscripts. If you change the name of this
|
||||
* variable, it won't be where it needs to be in the processor's address space,
|
||||
* unless you also adjust the ldscripts.
|
||||
*/
|
||||
extern volatile struct m0_state m0_state;
|
||||
|
||||
void m0_set_mode(enum m0_mode mode);
|
||||
|
||||
#endif /*__M0_STATE_H__*/
|
||||
419
firmware/common/max2831.c
Normal file
419
firmware/common/max2831.c
Normal file
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 'gcc -DTEST -DDEBUG -O2 -o test max2831.c' prints out what test
|
||||
* program would do if it had a real spi library
|
||||
*
|
||||
* 'gcc -DTEST -DBUS_PIRATE -O2 -o test max2831.c' prints out bus
|
||||
* pirate commands to do the same thing.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "max2831.h"
|
||||
#include "max2831_regs.def" // private register def macros
|
||||
#include "selftest.h"
|
||||
#include "adc.h"
|
||||
|
||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
/* Default register values. */
|
||||
static const uint16_t max2831_regs_default[MAX2831_NUM_REGS] = {
|
||||
0x1740, /* 0: enable fractional mode (Table 16 recommends 0x0740, clearing unknown bit) */
|
||||
0x119a, /* 1 */
|
||||
0x1003, /* 2 */
|
||||
0x0079, /* 3: PLL divider settings for 2437 MHz */
|
||||
0x3666, /* 4: PLL divider settings for 2437 MHz */
|
||||
0x00a4, /* 5: divide reference frequency by 2 */
|
||||
0x0060, /* 6: enable TX power detector */
|
||||
0x1022, /* 7: 110% TX LPF bandwidth */
|
||||
0x2021, /* 8: pin control of RX gain, 11 MHz LPF bandwidth */
|
||||
0x03b5, /* 9: pin control of TX gain */
|
||||
0x1d80, /* 10: 3.5 us PA enable delay, zero PA bias */
|
||||
0x0074, /* 11: LNA high gain, RX VGA moderate gain (Table 27 recommends 0x007f, maximum gain) */
|
||||
0x0140, /* 12: TX VGA minimum */
|
||||
0x0e92, /* 13 */
|
||||
0x0100, /* 14: reference clock output disabled */
|
||||
0x0145, /* 15: RX IQ common mode 1.1 V */
|
||||
};
|
||||
|
||||
/* Set up all registers according to defaults specified in docs. */
|
||||
static void max2831_init(max2831_driver_t* const drv)
|
||||
{
|
||||
drv->target_init(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_SHUTDOWN);
|
||||
|
||||
memcpy(drv->regs, max2831_regs_default, sizeof(drv->regs));
|
||||
drv->regs_dirty = 0xffff;
|
||||
|
||||
/* Write default register values to chip. */
|
||||
max2831_regs_commit(drv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up pins for GPIO and SPI control, configure SSP peripheral for SPI, and
|
||||
* set our own default register configuration.
|
||||
*/
|
||||
void max2831_setup(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_init(drv);
|
||||
|
||||
/* Use SPI control instead of B1-B7 pins for gain settings. */
|
||||
set_MAX2831_RXVGA_GAIN_SPI_EN(drv, 1);
|
||||
set_MAX2831_TXVGA_GAIN_SPI_EN(drv, 1);
|
||||
|
||||
//set_MAX2831_TXVGA_GAIN(0x3f); /* maximum gain */
|
||||
set_MAX2831_TXVGA_GAIN(drv, 0x00); /* minimum gain */
|
||||
set_MAX2831_RX_HPF_SEL(drv, MAX2831_RX_HPF_30_KHZ);
|
||||
set_MAX2831_LNA_GAIN(drv, MAX2831_LNA_GAIN_MAX); /* maximum gain */
|
||||
set_MAX2831_RXVGA_GAIN(drv, 0x18);
|
||||
|
||||
/* maximum rx output common-mode voltage */
|
||||
//set_MAX2831_RXIQ_VCM(drv, MAX2831_RXIQ_VCM_1_2);
|
||||
|
||||
/* configure baseband filter for 8 MHz TX */
|
||||
set_MAX2831_LPF_COARSE(drv, MAX2831_RX_LPF_7_5M);
|
||||
set_MAX2831_RX_LPF_FINE_ADJ(drv, MAX2831_RX_LPF_FINE_100);
|
||||
set_MAX2831_TX_LPF_FINE_ADJ(drv, MAX2831_TX_LPF_FINE_100);
|
||||
|
||||
/* clock output disable */
|
||||
set_MAX2831_CLKOUT_PIN_EN(drv, 0);
|
||||
|
||||
max2831_regs_commit(drv);
|
||||
}
|
||||
|
||||
static void max2831_write(max2831_driver_t* const drv, uint8_t r, uint16_t v)
|
||||
{
|
||||
uint32_t word = (((uint32_t) v & 0x3fff) << 4) | (r & 0xf);
|
||||
uint16_t values[2] = {word >> 9, word & 0x1ff};
|
||||
spi_bus_transfer(drv->bus, values, 2);
|
||||
}
|
||||
|
||||
uint16_t max2831_reg_read(max2831_driver_t* const drv, uint8_t r)
|
||||
{
|
||||
return drv->regs[r];
|
||||
}
|
||||
|
||||
void max2831_reg_write(max2831_driver_t* const drv, uint8_t r, uint16_t v)
|
||||
{
|
||||
drv->regs[r] = v;
|
||||
max2831_write(drv, r, v);
|
||||
MAX2831_REG_SET_CLEAN(drv, r);
|
||||
}
|
||||
|
||||
static inline void max2831_reg_commit(max2831_driver_t* const drv, uint8_t r)
|
||||
{
|
||||
max2831_reg_write(drv, r, drv->regs[r]);
|
||||
}
|
||||
|
||||
void max2831_regs_commit(max2831_driver_t* const drv)
|
||||
{
|
||||
int r;
|
||||
for (r = 0; r < MAX2831_NUM_REGS; r++) {
|
||||
if ((drv->regs_dirty >> r) & 0x1) {
|
||||
max2831_reg_commit(drv, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void max2831_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode)
|
||||
{
|
||||
// Only change calibration bits if necessary to reduce SPI activity.
|
||||
bool tx_cal = (new_mode == MAX2831_MODE_TX_CALIBRATION);
|
||||
bool rx_cal = (new_mode == MAX2831_MODE_RX_CALIBRATION);
|
||||
if (get_MAX2831_TX_CAL_MODE_EN(drv) != tx_cal) {
|
||||
set_MAX2831_TX_CAL_MODE_EN(drv, tx_cal);
|
||||
max2831_regs_commit(drv);
|
||||
}
|
||||
if (get_MAX2831_RX_CAL_MODE_EN(drv) != rx_cal) {
|
||||
set_MAX2831_RX_CAL_MODE_EN(drv, rx_cal);
|
||||
max2831_regs_commit(drv);
|
||||
}
|
||||
|
||||
drv->set_mode(drv, new_mode);
|
||||
max2831_set_lpf_bandwidth(drv, drv->desired_lpf_bw);
|
||||
}
|
||||
|
||||
max2831_mode_t max2831_mode(max2831_driver_t* const drv)
|
||||
{
|
||||
return drv->mode;
|
||||
}
|
||||
|
||||
void max2831_start(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_regs_commit(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_STANDBY);
|
||||
|
||||
/* Read RSSI with ADC. */
|
||||
uint16_t rssi_1 = selftest.max2831_mux_rssi_1 = adc_read(1);
|
||||
|
||||
/* Switch to temperature sensor. */
|
||||
set_MAX2831_RSSI_MUX(drv, MAX2831_RSSI_MUX_TEMP);
|
||||
max2831_regs_commit(drv);
|
||||
|
||||
/* Read temperature. */
|
||||
uint16_t temp = selftest.max2831_mux_temp = adc_read(1);
|
||||
|
||||
/* Switch back to RSSI. */
|
||||
set_MAX2831_RSSI_MUX(drv, MAX2831_RSSI_MUX_RSSI);
|
||||
max2831_regs_commit(drv);
|
||||
|
||||
/* Read RSSI again. */
|
||||
uint16_t rssi_2 = selftest.max2831_mux_rssi_2 = adc_read(1);
|
||||
|
||||
/* If the ADC results are as expected, we know our writes are working. */
|
||||
bool rssi_1_good = (rssi_1 < 10);
|
||||
bool rssi_2_good = (rssi_2 < 10);
|
||||
bool temp_good = (temp > 100) && (temp < 500); // -40 to +85C
|
||||
|
||||
selftest.max2831_mux_test_ok = rssi_1_good & rssi_2_good & temp_good;
|
||||
|
||||
if (!selftest.max2831_mux_test_ok) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
}
|
||||
|
||||
void max2831_tx(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_regs_commit(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_TX);
|
||||
}
|
||||
|
||||
void max2831_rx(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_regs_commit(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_RX);
|
||||
}
|
||||
|
||||
void max2831_tx_calibration(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_regs_commit(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_TX_CALIBRATION);
|
||||
}
|
||||
|
||||
void max2831_rx_calibration(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_regs_commit(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_RX_CALIBRATION);
|
||||
}
|
||||
|
||||
void max2831_stop(max2831_driver_t* const drv)
|
||||
{
|
||||
max2831_regs_commit(drv);
|
||||
max2831_set_mode(drv, MAX2831_MODE_SHUTDOWN);
|
||||
}
|
||||
|
||||
void max2831_set_frequency(max2831_driver_t* const drv, uint32_t freq)
|
||||
{
|
||||
uint32_t div_frac;
|
||||
uint32_t div_int;
|
||||
uint32_t div_rem;
|
||||
uint32_t div_cmp;
|
||||
int i;
|
||||
|
||||
/* ASSUME 40MHz PLL. Ratio = F*R/40,000,000. */
|
||||
/* TODO: fixed to R=2. Check if it's worth exploring R=1. */
|
||||
freq += (20000000 >> 21); /* round to nearest frequency */
|
||||
div_int = freq / 20000000;
|
||||
div_rem = freq % 20000000;
|
||||
div_frac = 0;
|
||||
div_cmp = 20000000;
|
||||
for (i = 0; i < 20; i++) {
|
||||
div_frac <<= 1;
|
||||
div_rem <<= 1;
|
||||
if (div_rem >= div_cmp) {
|
||||
div_frac |= 0x1;
|
||||
div_rem -= div_cmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write order matters? */
|
||||
//set_MAX2831_SYN_REF_DIV(drv, MAX2831_SYN_REF_DIV_2);
|
||||
set_MAX2831_SYN_INT(drv, div_int);
|
||||
set_MAX2831_SYN_FRAC_HI(drv, (div_frac >> 6) & 0x3fff);
|
||||
set_MAX2831_SYN_FRAC_LO(drv, div_frac & 0x3f);
|
||||
max2831_regs_commit(drv);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t bandwidth_hz;
|
||||
uint8_t ft;
|
||||
} max2831_ft_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t percent;
|
||||
uint8_t ft_fine;
|
||||
} max2831_ft_fine_t;
|
||||
|
||||
// clang-format off
|
||||
/* measured -0.5 dB complex baseband bandwidth for each register setting */
|
||||
static const max2831_ft_t max2831_rx_ft[] = {
|
||||
{ 11600000, MAX2831_RX_LPF_7_5M },
|
||||
{ 15100000, MAX2831_RX_LPF_8_5M },
|
||||
{ 22600000, MAX2831_RX_LPF_15M },
|
||||
{ 28300000, MAX2831_RX_LPF_18M },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
static const max2831_ft_fine_t max2831_rx_ft_fine[] = {
|
||||
{ 90, MAX2831_RX_LPF_FINE_90 },
|
||||
{ 95, MAX2831_RX_LPF_FINE_95 },
|
||||
{ 100, MAX2831_RX_LPF_FINE_100 },
|
||||
{ 105, MAX2831_RX_LPF_FINE_105 },
|
||||
{ 110, MAX2831_RX_LPF_FINE_110 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
/* measured -0.5 dB complex baseband bandwidth for each register setting */
|
||||
static const max2831_ft_t max2831_tx_ft[] = {
|
||||
{ 11900000, MAX2831_TX_LPF_8M },
|
||||
{ 15800000, MAX2831_TX_LPF_11M },
|
||||
{ 23600000, MAX2831_TX_LPF_16_5M },
|
||||
{ 31300000, MAX2831_TX_LPF_22_5M },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
static const max2831_ft_fine_t max2831_tx_ft_fine[] = {
|
||||
{ 90, MAX2831_TX_LPF_FINE_90 },
|
||||
{ 95, MAX2831_TX_LPF_FINE_95 },
|
||||
{ 100, MAX2831_TX_LPF_FINE_100 },
|
||||
{ 105, MAX2831_TX_LPF_FINE_105 },
|
||||
{ 110, MAX2831_TX_LPF_FINE_110 },
|
||||
{ 115, MAX2831_TX_LPF_FINE_115 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
//clang-format on
|
||||
|
||||
|
||||
uint32_t max2831_set_lpf_bandwidth(max2831_driver_t* const drv, const uint32_t bandwidth_hz) {
|
||||
const max2831_ft_t* coarse;
|
||||
const max2831_ft_fine_t* fine;
|
||||
|
||||
drv->desired_lpf_bw = bandwidth_hz;
|
||||
|
||||
if (drv->mode == MAX2831_MODE_RX) {
|
||||
coarse = max2831_rx_ft;
|
||||
fine = max2831_rx_ft_fine;
|
||||
} else {
|
||||
coarse = max2831_tx_ft;
|
||||
fine = max2831_tx_ft_fine;
|
||||
}
|
||||
|
||||
/* Find coarse and fine settings for LPF. */
|
||||
bool found = false;
|
||||
const max2831_ft_fine_t* f = fine;
|
||||
for (; coarse->bandwidth_hz != 0; coarse++) {
|
||||
uint32_t coarse_aux = coarse->bandwidth_hz / 100;
|
||||
for (f = fine; f->percent != 0; f++) {
|
||||
if ((coarse_aux * f->percent) >= drv->desired_lpf_bw) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the widest setting if a wider bandwidth than our maximum is
|
||||
* requested.
|
||||
*/
|
||||
if (!found) {
|
||||
coarse--;
|
||||
f--;
|
||||
}
|
||||
|
||||
/* Program found settings. */
|
||||
set_MAX2831_LPF_COARSE(drv, coarse->ft);
|
||||
if (drv->mode == MAX2831_MODE_RX) {
|
||||
set_MAX2831_RX_LPF_FINE_ADJ(drv, f->ft_fine);
|
||||
} else {
|
||||
set_MAX2831_TX_LPF_FINE_ADJ(drv, f->ft_fine);
|
||||
}
|
||||
max2831_regs_commit(drv);
|
||||
|
||||
return coarse->bandwidth_hz * f->percent / 100;
|
||||
}
|
||||
|
||||
bool max2831_set_lna_gain(max2831_driver_t* const drv, const uint32_t gain_db) {
|
||||
uint16_t val;
|
||||
switch(gain_db){
|
||||
case 40: // MAX2837 compatibility
|
||||
case 33:
|
||||
case 32: // MAX2837 compatibility
|
||||
val = MAX2831_LNA_GAIN_MAX;
|
||||
break;
|
||||
case 24: // MAX2837 compatibility
|
||||
case 16:
|
||||
val = MAX2831_LNA_GAIN_M16;
|
||||
break;
|
||||
case 8: // MAX2837 compatibility
|
||||
case 0:
|
||||
val = MAX2831_LNA_GAIN_M33;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
set_MAX2831_LNA_GAIN(drv, val);
|
||||
max2831_reg_commit(drv, 11);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool max2831_set_vga_gain(max2831_driver_t* const drv, const uint32_t gain_db) {
|
||||
if( (gain_db & 0x1) || gain_db > 62) {/* 0b11111*2 */
|
||||
return false;
|
||||
}
|
||||
|
||||
set_MAX2831_RXVGA_GAIN(drv, (gain_db >> 1) );
|
||||
max2831_reg_commit(drv, 11);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool max2831_set_txvga_gain(max2831_driver_t* const drv, const uint32_t gain_db) {
|
||||
uint16_t value = MIN((gain_db << 1) | 1, 0x3f);
|
||||
set_MAX2831_TXVGA_GAIN(drv, value);
|
||||
max2831_reg_commit(drv, 12);
|
||||
return true;
|
||||
}
|
||||
|
||||
void max2831_set_rx_hpf_frequency(max2831_driver_t* const drv, const max2831_rx_hpf_freq_t freq)
|
||||
{
|
||||
/**
|
||||
* Frequency RXHP RX_HPF_SEL (D13:D12)
|
||||
*
|
||||
* 100 Hz low 00
|
||||
* 4 kHz low x1
|
||||
* 30 kHz low 10
|
||||
* 600 kHz high xx
|
||||
*/
|
||||
switch (freq) {
|
||||
case MAX2831_RX_HPF_100_HZ:
|
||||
case MAX2831_RX_HPF_4_KHZ:
|
||||
case MAX2831_RX_HPF_30_KHZ:
|
||||
set_MAX2831_RX_HPF_SEL(drv, freq);
|
||||
max2831_reg_commit(drv, 7);
|
||||
gpio_clear(drv->gpio_rxhp);
|
||||
break;
|
||||
case MAX2831_RX_HPF_600_KHZ:
|
||||
gpio_set(drv->gpio_rxhp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
111
firmware/common/max2831.h
Normal file
111
firmware/common/max2831.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __MAX2831_H
|
||||
#define __MAX2831_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "gpio.h"
|
||||
#include "spi_bus.h"
|
||||
|
||||
/* 16 registers, each containing 14 bits of data. */
|
||||
#define MAX2831_NUM_REGS 16
|
||||
#define MAX2831_DATA_REGS_MAX_VALUE 16384
|
||||
|
||||
typedef enum {
|
||||
MAX2831_MODE_SHUTDOWN,
|
||||
MAX2831_MODE_STANDBY,
|
||||
MAX2831_MODE_TX,
|
||||
MAX2831_MODE_RX,
|
||||
MAX2831_MODE_TX_CALIBRATION,
|
||||
MAX2831_MODE_RX_CALIBRATION,
|
||||
} max2831_mode_t;
|
||||
|
||||
typedef enum {
|
||||
MAX2831_RX_HPF_100_HZ = 0,
|
||||
MAX2831_RX_HPF_4_KHZ = 1,
|
||||
MAX2831_RX_HPF_30_KHZ = 2,
|
||||
MAX2831_RX_HPF_600_KHZ = 3,
|
||||
} max2831_rx_hpf_freq_t;
|
||||
|
||||
struct max2831_driver_t;
|
||||
typedef struct max2831_driver_t max2831_driver_t;
|
||||
|
||||
struct max2831_driver_t {
|
||||
spi_bus_t* bus;
|
||||
gpio_t gpio_enable;
|
||||
gpio_t gpio_rxtx;
|
||||
gpio_t gpio_rxhp;
|
||||
gpio_t gpio_ld;
|
||||
void (*target_init)(max2831_driver_t* const drv);
|
||||
void (*set_mode)(max2831_driver_t* const drv, const max2831_mode_t new_mode);
|
||||
max2831_mode_t mode;
|
||||
uint16_t regs[MAX2831_NUM_REGS];
|
||||
uint16_t regs_dirty;
|
||||
uint32_t desired_lpf_bw;
|
||||
};
|
||||
|
||||
/* Initialize chip. */
|
||||
extern void max2831_setup(max2831_driver_t* const drv);
|
||||
|
||||
/* Read a register via SPI. Save a copy to memory and return
|
||||
* value. Mark clean. */
|
||||
extern uint16_t max2831_reg_read(max2831_driver_t* const drv, uint8_t r);
|
||||
|
||||
/* Write value to register via SPI and save a copy to memory. Mark
|
||||
* clean. */
|
||||
extern void max2831_reg_write(max2831_driver_t* const drv, uint8_t r, uint16_t v);
|
||||
|
||||
/* Write all dirty registers via SPI from memory. Mark all clean. Some
|
||||
* operations require registers to be written in a certain order. Use
|
||||
* provided routines for those operations. */
|
||||
extern void max2831_regs_commit(max2831_driver_t* const drv);
|
||||
|
||||
max2831_mode_t max2831_mode(max2831_driver_t* const drv);
|
||||
void max2831_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode);
|
||||
|
||||
/* Turn on/off all chip functions. Does not control oscillator and CLKOUT */
|
||||
extern void max2831_start(max2831_driver_t* const drv);
|
||||
extern void max2831_stop(max2831_driver_t* const drv);
|
||||
|
||||
/* Set frequency in Hz. Frequency setting is a multi-step function
|
||||
* where order of register writes matters. */
|
||||
extern void max2831_set_frequency(max2831_driver_t* const drv, uint32_t freq);
|
||||
uint32_t max2831_set_lpf_bandwidth(
|
||||
max2831_driver_t* const drv,
|
||||
const uint32_t bandwidth_hz);
|
||||
bool max2831_set_lna_gain(max2831_driver_t* const drv, const uint32_t gain_db);
|
||||
bool max2831_set_vga_gain(max2831_driver_t* const drv, const uint32_t gain_db);
|
||||
bool max2831_set_txvga_gain(max2831_driver_t* const drv, const uint32_t gain_db);
|
||||
|
||||
/* Set receiver high-pass filter corner frequency in Hz */
|
||||
extern void max2831_set_rx_hpf_frequency(
|
||||
max2831_driver_t* const drv,
|
||||
const max2831_rx_hpf_freq_t freq);
|
||||
|
||||
extern void max2831_tx(max2831_driver_t* const drv);
|
||||
extern void max2831_rx(max2831_driver_t* const drv);
|
||||
extern void max2831_tx_calibration(max2831_driver_t* const drv);
|
||||
extern void max2831_rx_calibration(max2831_driver_t* const drv);
|
||||
|
||||
#endif // __MAX2831_H
|
||||
132
firmware/common/max2831_regs.def
Normal file
132
firmware/common/max2831_regs.def
Normal file
@@ -0,0 +1,132 @@
|
||||
/* -*- mode: c -*- */
|
||||
|
||||
#ifndef __MAX2831_REGS_DEF
|
||||
#define __MAX2831_REGS_DEF
|
||||
|
||||
/* Generate static inline accessors that operate on the global
|
||||
* regs. Done this way to (1) allow defs to be scraped out and used
|
||||
* elsewhere, e.g. in scripts, (2) to avoid dealing with endian
|
||||
* (structs). This may be used in firmware, or on host predefined
|
||||
* register loads. */
|
||||
|
||||
#define MAX2831_REG_SET_CLEAN(_d, _r) (_d->regs_dirty &= ~(1UL<<_r))
|
||||
#define MAX2831_REG_SET_DIRTY(_d, _r) (_d->regs_dirty |= (1UL<<_r))
|
||||
|
||||
/* On set_, register is always set dirty, even if nothing
|
||||
* changed. This makes sure that write that have side effects,
|
||||
* e.g. frequency setting, are not skipped. */
|
||||
|
||||
/* n=name, r=regnum, o=offset (bits from LSB), l=length (bits) */
|
||||
#define __MREG__(n,r,o,l) \
|
||||
static inline uint16_t get_##n(max2831_driver_t* const _d) { \
|
||||
return (_d->regs[r] >> o) & ((1<<l)-1); \
|
||||
} \
|
||||
static inline void set_##n(max2831_driver_t* const _d, uint16_t v) { \
|
||||
_d->regs[r] &= ~(((1<<l)-1)<<o); \
|
||||
_d->regs[r] |= ((v&((1<<l)-1))<<o); \
|
||||
MAX2831_REG_SET_DIRTY(_d, r); \
|
||||
}
|
||||
|
||||
/* REG 0 */
|
||||
__MREG__(MAX2831_PLL_MODE, 0, 10, 1)
|
||||
#define MAX2831_PLL_MODE_INTEGER 0
|
||||
#define MAX2831_PLL_MODE_FRACTIONAL 1
|
||||
|
||||
/* REG 1 */
|
||||
__MREG__(MAX2831_LOCK_DETECT_OUTPUT_SEL, 1, 12, 1)
|
||||
#define MAX2831_LOCK_DETECT_OUTPUT_OPEN_DRAIN 0
|
||||
#define MAX2831_LOCK_DETECT_OUTPUT_CMOS 1
|
||||
|
||||
/* REG 3 */
|
||||
__MREG__(MAX2831_SYN_INT,3,0,8)
|
||||
__MREG__(MAX2831_SYN_FRAC_LO,3,8,6)
|
||||
|
||||
/* REG 4 */
|
||||
__MREG__(MAX2831_SYN_FRAC_HI,4,0,14)
|
||||
|
||||
/* REG 5 */
|
||||
__MREG__(MAX2831_SYN_REF_DIV,5,2,1)
|
||||
#define MAX2831_SYN_REF_DIV_1 0
|
||||
#define MAX2831_SYN_REF_DIV_2 1
|
||||
__MREG__(MAX2831_LOCK_DETECT_OUTPUT_EN,5,5,1)
|
||||
__MREG__(MAX2831_LOCK_DETECT_OUTPUT_PULLUP_EN,5,9,1)
|
||||
|
||||
/* REG 6 */
|
||||
__MREG__(MAX2831_RX_CAL_MODE_EN,6,0,1)
|
||||
__MREG__(MAX2831_TX_CAL_MODE_EN,6,1,1)
|
||||
__MREG__(MAX2831_TX_POWER_DETECT_EN,6,6,1)
|
||||
__MREG__(MAX2831_TX_IQ_CALIB_GAIN,6,11,2)
|
||||
#define MAX2831_TX_IQ_CALIB_GAIN_9 0
|
||||
#define MAX2831_TX_IQ_CALIB_GAIN_19 1
|
||||
#define MAX2831_TX_IQ_CALIB_GAIN_29 2
|
||||
#define MAX2831_TX_IQ_CALIB_GAIN_39 3
|
||||
|
||||
/* REG 7 */
|
||||
__MREG__(MAX2831_RX_LPF_FINE_ADJ,7,0,3)
|
||||
#define MAX2831_RX_LPF_FINE_90 0
|
||||
#define MAX2831_RX_LPF_FINE_95 1
|
||||
#define MAX2831_RX_LPF_FINE_100 2
|
||||
#define MAX2831_RX_LPF_FINE_105 3
|
||||
#define MAX2831_RX_LPF_FINE_110 4
|
||||
__MREG__(MAX2831_TX_LPF_FINE_ADJ,7,3,3)
|
||||
#define MAX2831_TX_LPF_FINE_90 0
|
||||
#define MAX2831_TX_LPF_FINE_95 1
|
||||
#define MAX2831_TX_LPF_FINE_100 2
|
||||
#define MAX2831_TX_LPF_FINE_105 3
|
||||
#define MAX2831_TX_LPF_FINE_110 4
|
||||
#define MAX2831_TX_LPF_FINE_115 5
|
||||
__MREG__(MAX2831_RX_HPF_SEL,7,12,2)
|
||||
#define MAX2831_RX_HPF_100_HZ 0
|
||||
#define MAX2831_RX_HPF_4_KHZ 1
|
||||
#define MAX2831_RX_HPF_30_KHZ 2
|
||||
|
||||
/* REG 8 */
|
||||
__MREG__(MAX2831_LPF_COARSE,8,0,2)
|
||||
#define MAX2831_RX_LPF_7_5M 0
|
||||
#define MAX2831_RX_LPF_8_5M 1
|
||||
#define MAX2831_RX_LPF_15M 2
|
||||
#define MAX2831_RX_LPF_18M 3
|
||||
#define MAX2831_TX_LPF_8M 0
|
||||
#define MAX2831_TX_LPF_11M 1
|
||||
#define MAX2831_TX_LPF_16_5M 2
|
||||
#define MAX2831_TX_LPF_22_5M 3
|
||||
__MREG__(MAX2831_RSSI_MUX,8,8,2)
|
||||
#define MAX2831_RSSI_MUX_RSSI 0
|
||||
#define MAX2831_RSSI_MUX_TEMP 1
|
||||
#define MAX2831_RSSI_MUX_TX_POWER 2
|
||||
__MREG__(MAX2831_RSSI_MODE,8,10,1) // set to override RXHP pin
|
||||
__MREG__(MAX2831_RXVGA_GAIN_SPI_EN,8,12,1)
|
||||
|
||||
/* REG 9 */
|
||||
__MREG__(MAX2831_TXVGA_GAIN_SPI_EN,9,10,1)
|
||||
|
||||
/* REG 10 */
|
||||
__MREG__(MAX2831_PA_BIAS1_ADJ,10,0,3)
|
||||
__MREG__(MAX2831_PA_BIAS2_ADJ,10,3,4)
|
||||
__MREG__(MAX2831_PA_DELAY,10,10,4)
|
||||
|
||||
/* REG 11 */
|
||||
__MREG__(MAX2831_RXVGA_GAIN,11,0,5)
|
||||
__MREG__(MAX2831_LNA_GAIN,11,5,2)
|
||||
#define MAX2831_LNA_GAIN_M33 0
|
||||
#define MAX2831_LNA_GAIN_M16 2
|
||||
#define MAX2831_LNA_GAIN_MAX 3
|
||||
|
||||
/* REG 12 */
|
||||
__MREG__(MAX2831_TXVGA_GAIN,12,0,6)
|
||||
|
||||
/* REG 14 */
|
||||
__MREG__(MAX2831_XTAL_TUNE,14,0,7)
|
||||
__MREG__(MAX2831_CLKOUT_PIN_EN,14,9,1)
|
||||
__MREG__(MAX2831_CLKOUT_DIV,14,10,1)
|
||||
#define MAX2831_CLKOUT_DIV_1 0
|
||||
#define MAX2831_CLKOUT_DIV_2 1
|
||||
|
||||
/* REG 15 */
|
||||
__MREG__(MAX2831_RXIQ_VCM,15,10,2) // RX I/Q output common mode
|
||||
#define MAX2831_RXIQ_VCM_1_1 0 // 1.1V
|
||||
#define MAX2831_RXIQ_VCM_1_2 1 // 1.2V
|
||||
#define MAX2831_RXIQ_VCM_1_3 2 // 1.3V
|
||||
#define MAX2831_RXIQ_VCM_1_45 3 // 1.45V
|
||||
|
||||
#endif // __MAX2831_REGS_DEF
|
||||
101
firmware/common/max2831_target.c
Normal file
101
firmware/common/max2831_target.c
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "max2831_target.h"
|
||||
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include "hackrf_core.h"
|
||||
|
||||
void max2831_target_init(max2831_driver_t* const drv)
|
||||
{
|
||||
/* Configure SSP1 Peripheral (to be moved later in SSP driver) */
|
||||
scu_pinmux(SCU_SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
|
||||
scu_pinmux(SCU_SSP1_SCK, (SCU_SSP_IO | SCU_CONF_FUNCTION1));
|
||||
|
||||
scu_pinmux(SCU_XCVR_CS, SCU_XCVR_CS_PINCFG);
|
||||
|
||||
/*
|
||||
* Configure XCVR_CTL GPIO pins.
|
||||
*
|
||||
* The RXTX pin is also known as RXENABLE because of its use on the
|
||||
* MAX2837 which had a separate TXENABLE. On MAX2831 a single RXTX pin
|
||||
* switches between RX (high) and TX (low) modes.
|
||||
*/
|
||||
scu_pinmux(SCU_XCVR_ENABLE, SCU_XCVR_ENABLE_PINCFG);
|
||||
scu_pinmux(SCU_XCVR_RXENABLE, SCU_XCVR_RXENABLE_PINCFG);
|
||||
scu_pinmux(SCU_XCVR_RXHP, SCU_XCVR_RXHP_PINCFG);
|
||||
scu_pinmux(SCU_XCVR_LD, SCU_XCVR_LD_PINCFG);
|
||||
|
||||
/* Set GPIO pins as outputs. */
|
||||
gpio_output(drv->gpio_enable);
|
||||
gpio_output(drv->gpio_rxtx);
|
||||
gpio_output(drv->gpio_rxhp);
|
||||
gpio_input(drv->gpio_ld);
|
||||
}
|
||||
|
||||
void max2831_target_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode)
|
||||
{
|
||||
/* MAX2831_MODE_SHUTDOWN:
|
||||
* All circuit blocks are powered down, except the 4-wire serial bus
|
||||
* and its internal programmable registers.
|
||||
*
|
||||
* MAX2831_MODE_STANDBY:
|
||||
* Used to enable the frequency synthesizer block while the rest of the
|
||||
* device is powered down. In this mode, PLL, VCO, and LO generator
|
||||
* are on, so that Tx or Rx modes can be quickly enabled from this mode.
|
||||
* These and other blocks can be selectively enabled in this mode.
|
||||
*
|
||||
* MAX2831_MODE_TX:
|
||||
* All Tx circuit blocks are powered on. The external PA is powered on
|
||||
* after a programmable delay using the on-chip PA bias DAC. The slow-
|
||||
* charging Rx circuits are in a precharged “idle-off” state for fast
|
||||
* Tx-to-Rx turnaround time.
|
||||
*
|
||||
* MAX2831_MODE_RX:
|
||||
* All Rx circuit blocks are powered on and active. Antenna signal is
|
||||
* applied; RF is downconverted, filtered, and buffered at Rx BB I and Q
|
||||
* outputs. The slow- charging Tx circuits are in a precharged “idle-off”
|
||||
* state for fast Rx-to-Tx turnaround time.
|
||||
*/
|
||||
|
||||
switch (new_mode) {
|
||||
default:
|
||||
case MAX2831_MODE_SHUTDOWN:
|
||||
gpio_clear(drv->gpio_rxtx);
|
||||
gpio_clear(drv->gpio_enable);
|
||||
break;
|
||||
case MAX2831_MODE_STANDBY:
|
||||
gpio_set(drv->gpio_rxtx);
|
||||
gpio_clear(drv->gpio_enable);
|
||||
break;
|
||||
case MAX2831_MODE_TX:
|
||||
case MAX2831_MODE_TX_CALIBRATION:
|
||||
gpio_set(drv->gpio_rxtx);
|
||||
gpio_set(drv->gpio_enable);
|
||||
break;
|
||||
case MAX2831_MODE_RX:
|
||||
case MAX2831_MODE_RX_CALIBRATION:
|
||||
gpio_clear(drv->gpio_rxtx);
|
||||
gpio_set(drv->gpio_enable);
|
||||
break;
|
||||
}
|
||||
drv->mode = new_mode;
|
||||
}
|
||||
30
firmware/common/max2831_target.h
Normal file
30
firmware/common/max2831_target.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __MAX2831_TARGET_H
|
||||
#define __MAX2831_TARGET_H
|
||||
|
||||
#include "max2831.h"
|
||||
|
||||
void max2831_target_init(max2831_driver_t* const drv);
|
||||
void max2831_target_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode);
|
||||
|
||||
#endif // __MAX2831_TARGET_H
|
||||
@@ -33,11 +33,12 @@
|
||||
#include <string.h>
|
||||
#include "max2837.h"
|
||||
#include "max2837_regs.def" // private register def macros
|
||||
#include "selftest.h"
|
||||
|
||||
/* Default register values. */
|
||||
static const uint16_t max2837_regs_default[MAX2837_NUM_REGS] = {
|
||||
0x150, /* 0 */
|
||||
0x002, /* 1 */
|
||||
0x002, /* 1: data sheet says 0x002 but read 0x1c2 */
|
||||
0x1f4, /* 2 */
|
||||
0x1b9, /* 3 */
|
||||
0x00a, /* 4 */
|
||||
@@ -66,13 +67,17 @@ static const uint16_t max2837_regs_default[MAX2837_NUM_REGS] = {
|
||||
0x1a9, /* 22 */
|
||||
0x24f, /* 23 */
|
||||
0x180, /* 24 */
|
||||
0x100, /* 25 */
|
||||
0x100, /* 25: data sheet says 0x100 but read 0x10a */
|
||||
0x3ca, /* 26 */
|
||||
0x3e3, /* 27 */
|
||||
0x3e3, /* 27: data sheet says 0x100 but read 0x3f3 */
|
||||
0x0c0, /* 28 */
|
||||
0x3f0, /* 29 */
|
||||
0x080, /* 30 */
|
||||
0x000}; /* 31 */
|
||||
0x080, /* 30: data sheet says 0x080 but read 0x092 */
|
||||
0x000}; /* 31: data sheet says 0x000 but read 0x1ae */
|
||||
|
||||
static const uint8_t max2837_regs_skip_verify[] = {1, 25, 27, 30, 31};
|
||||
|
||||
static uint16_t max2837_read(max2837_driver_t* const drv, uint8_t r);
|
||||
|
||||
/* Set up all registers according to defaults specified in docs. */
|
||||
static void max2837_init(max2837_driver_t* const drv)
|
||||
@@ -85,6 +90,28 @@ static void max2837_init(max2837_driver_t* const drv)
|
||||
|
||||
/* Write default register values to chip. */
|
||||
max2837_regs_commit(drv);
|
||||
|
||||
/* Read back registers to verify. */
|
||||
selftest.max283x_readback_total_registers = MAX2837_NUM_REGS;
|
||||
for (int r = 0; r < MAX2837_NUM_REGS; r++) {
|
||||
for (unsigned int i = 0; i < sizeof(max2837_regs_skip_verify); i++) {
|
||||
if (max2837_regs_skip_verify[i] == r) {
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
uint16_t value = max2837_read(drv, r);
|
||||
if (value != drv->regs[r]) {
|
||||
selftest.max283x_readback_bad_value = value;
|
||||
selftest.max283x_readback_expected_value = drv->regs[r];
|
||||
break;
|
||||
}
|
||||
next:
|
||||
selftest.max283x_readback_register_count = r + 1;
|
||||
}
|
||||
|
||||
if (selftest.max283x_readback_register_count < MAX2837_NUM_REGS) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -33,12 +33,12 @@ void max2837_target_init(max2837_driver_t* const drv)
|
||||
scu_pinmux(SCU_SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
|
||||
scu_pinmux(SCU_SSP1_SCK, (SCU_SSP_IO | SCU_CONF_FUNCTION1));
|
||||
|
||||
scu_pinmux(SCU_XCVR_CS, SCU_GPIO_FAST);
|
||||
scu_pinmux(SCU_XCVR_CS, SCU_XCVR_CS_PINCFG);
|
||||
|
||||
/* Configure XCVR_CTL GPIO pins. */
|
||||
scu_pinmux(SCU_XCVR_ENABLE, SCU_GPIO_FAST);
|
||||
scu_pinmux(SCU_XCVR_RXENABLE, SCU_GPIO_FAST);
|
||||
scu_pinmux(SCU_XCVR_TXENABLE, SCU_GPIO_FAST);
|
||||
scu_pinmux(SCU_XCVR_ENABLE, SCU_XCVR_ENABLE_PINCFG);
|
||||
scu_pinmux(SCU_XCVR_RXENABLE, SCU_XCVR_RXENABLE_PINCFG);
|
||||
scu_pinmux(SCU_XCVR_TXENABLE, SCU_XCVR_TXENABLE_PINCFG);
|
||||
|
||||
/* Set GPIO pins as outputs. */
|
||||
gpio_output(drv->gpio_enable);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <string.h>
|
||||
#include "max2839.h"
|
||||
#include "max2839_regs.def" // private register def macros
|
||||
#include "selftest.h"
|
||||
|
||||
static uint8_t requested_lna_gain = 0;
|
||||
static uint8_t requested_vga_gain = 0;
|
||||
@@ -40,9 +41,9 @@ static uint8_t requested_vga_gain = 0;
|
||||
/* Default register values. */
|
||||
static const uint16_t max2839_regs_default[MAX2839_NUM_REGS] = {
|
||||
0x000, /* 0 */
|
||||
0x00c, /* 1: data sheet says 0x00c but read 0x22c */
|
||||
0x00c, /* 1: data sheet says 0x00c but read 0x20c or 0x22c*/
|
||||
0x080, /* 2 */
|
||||
0x1b9, /* 3: data sheet says 0x1b9 but read 0x1b0 */
|
||||
0x1b0, /* 3: data sheet says 0x1b9 but read 0x1b0 or 0x1b9 */
|
||||
0x3e6, /* 4 */
|
||||
0x100, /* 5 */
|
||||
0x000, /* 6 */
|
||||
@@ -64,12 +65,12 @@ static const uint16_t max2839_regs_default[MAX2839_NUM_REGS] = {
|
||||
0x1a9, /* 22 */
|
||||
0x24f, /* 23 */
|
||||
0x180, /* 24 */
|
||||
0x000, /* 25: data sheet says 0x000 but read 0x00a */
|
||||
0x00a, /* 25: data sheet says 0x000 but read 0x00a */
|
||||
0x3c0, /* 26 */
|
||||
0x200, /* 27: data sheet says 0x200 but read 0x22a */
|
||||
0x200, /* 27: data sheet says 0x200 but read 0x22a or 0x22f */
|
||||
0x0c0, /* 28 */
|
||||
0x03f, /* 29: data sheet says 0x03f but read 0x07f */
|
||||
0x300, /* 30: data sheet says 0x300 but read 0x398 */
|
||||
0x03f, /* 29: data sheet says 0x03f but read 0x07f or 0x17f */
|
||||
0x300, /* 30: data sheet says 0x300 but read 0x398 or 0x31a */
|
||||
0x340}; /* 31: data sheet says 0x340 but read 0x359 */
|
||||
|
||||
/*
|
||||
@@ -79,6 +80,10 @@ static const uint16_t max2839_regs_default[MAX2839_NUM_REGS] = {
|
||||
* different settings.
|
||||
*/
|
||||
|
||||
static const uint8_t max2839_regs_skip_verify[] = {1, 3, 8, 11, 21, 25, 27, 29, 30, 31};
|
||||
|
||||
static uint16_t max2839_read(max2839_driver_t* const drv, uint8_t r);
|
||||
|
||||
/* Set up all registers according to defaults specified in docs. */
|
||||
static void max2839_init(max2839_driver_t* const drv)
|
||||
{
|
||||
@@ -90,6 +95,28 @@ static void max2839_init(max2839_driver_t* const drv)
|
||||
|
||||
/* Write default register values to chip. */
|
||||
max2839_regs_commit(drv);
|
||||
|
||||
/* Read back registers to verify. */
|
||||
selftest.max283x_readback_total_registers = MAX2839_NUM_REGS;
|
||||
for (int r = 0; r < MAX2839_NUM_REGS; r++) {
|
||||
for (unsigned int i = 0; i < sizeof(max2839_regs_skip_verify); i++) {
|
||||
if (max2839_regs_skip_verify[i] == r) {
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
uint16_t value = max2839_read(drv, r);
|
||||
if (value != drv->regs[r]) {
|
||||
selftest.max283x_readback_bad_value = value;
|
||||
selftest.max283x_readback_expected_value = drv->regs[r];
|
||||
break;
|
||||
}
|
||||
next:
|
||||
selftest.max283x_readback_register_count = r + 1;
|
||||
}
|
||||
|
||||
if (selftest.max283x_readback_register_count < MAX2839_NUM_REGS) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -32,9 +32,15 @@
|
||||
#include "spi_bus.h"
|
||||
|
||||
extern spi_bus_t spi_bus_ssp1;
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_max2837_enable = GPIO(6, 29);
|
||||
static struct gpio_t gpio_max2837_rx_enable = GPIO(3, 3);
|
||||
static struct gpio_t gpio_max2837_tx_enable = GPIO(3, 2);
|
||||
#else
|
||||
static struct gpio_t gpio_max2837_enable = GPIO(2, 6);
|
||||
static struct gpio_t gpio_max2837_rx_enable = GPIO(2, 5);
|
||||
static struct gpio_t gpio_max2837_tx_enable = GPIO(2, 4);
|
||||
#endif
|
||||
|
||||
max2837_driver_t max2837 = {
|
||||
.bus = &spi_bus_ssp1,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "max2871.h"
|
||||
#include "max2871_regs.h"
|
||||
#include "selftest.h"
|
||||
|
||||
#if (defined DEBUG)
|
||||
#include <stdio.h>
|
||||
@@ -35,6 +36,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint32_t max2871_spi_read(max2871_driver_t* const drv);
|
||||
static void max2871_spi_write(max2871_driver_t* const drv, uint8_t r, uint32_t v);
|
||||
static void max2871_write_registers(max2871_driver_t* const drv);
|
||||
static void delay_ms(int ms);
|
||||
@@ -67,6 +69,11 @@ void max2871_setup(max2871_driver_t* const drv)
|
||||
gpio_set(drv->gpio_vco_le); /* active low */
|
||||
gpio_set(drv->gpio_synt_rfout_en); /* active high */
|
||||
|
||||
selftest.mixer_id = max2871_spi_read(drv) >> MAX2871_DIE_SHIFT;
|
||||
if (selftest.mixer_id != 7) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
max2871_regs_init();
|
||||
int i;
|
||||
for (i = 5; i >= 0; i--) {
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
#define MAX2871_REGS_H
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX2871_VASA (1 << 9)
|
||||
#define MAX2871_VASA (1 << 9)
|
||||
#define MAX2871_DIE_SHIFT 28
|
||||
|
||||
void max2871_regs_init(void);
|
||||
uint32_t max2871_get_register(int reg);
|
||||
|
||||
@@ -38,5 +38,5 @@ void max5864_target_init(max5864_driver_t* const drv)
|
||||
* Configure CS_AD pin to keep the MAX5864 SPI disabled while we use the
|
||||
* SPI bus for the MAX2837. FIXME: this should probably be somewhere else.
|
||||
*/
|
||||
scu_pinmux(SCU_AD_CS, SCU_GPIO_FAST);
|
||||
scu_pinmux(SCU_AD_CS, SCU_AD_CS_PINCFG);
|
||||
}
|
||||
|
||||
@@ -41,9 +41,16 @@ static struct gpio_t gpio_vco_le = GPIO(2, 14);
|
||||
static struct gpio_t gpio_vco_mux = GPIO(5, 25);
|
||||
static struct gpio_t gpio_synt_rfout_en = GPIO(3, 5);
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
static struct gpio_t gpio_rffc5072_select = GPIO(2, 13);
|
||||
static struct gpio_t gpio_rffc5072_clock = GPIO(5, 18);
|
||||
static struct gpio_t gpio_rffc5072_data = GPIO(4, 14);
|
||||
static struct gpio_t gpio_rffc5072_reset = GPIO(2, 14);
|
||||
static struct gpio_t gpio_rffc5072_ld = GPIO(6, 25);
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
const rffc5071_spi_config_t rffc5071_spi_config = {
|
||||
.gpio_select = &gpio_rffc5072_select,
|
||||
.gpio_clock = &gpio_rffc5072_clock,
|
||||
@@ -61,6 +68,9 @@ spi_bus_t spi_bus_rffc5071 = {
|
||||
mixer_driver_t mixer = {
|
||||
.bus = &spi_bus_rffc5071,
|
||||
.gpio_reset = &gpio_rffc5072_reset,
|
||||
#ifdef PRALINE
|
||||
.gpio_ld = &gpio_rffc5072_ld,
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -76,7 +86,7 @@ mixer_driver_t mixer = {
|
||||
|
||||
void mixer_bus_setup(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
(void) mixer;
|
||||
spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config);
|
||||
#endif
|
||||
@@ -87,7 +97,7 @@ void mixer_bus_setup(mixer_driver_t* const mixer)
|
||||
|
||||
void mixer_setup(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_setup(mixer);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -97,7 +107,7 @@ void mixer_setup(mixer_driver_t* const mixer)
|
||||
|
||||
uint64_t mixer_set_frequency(mixer_driver_t* const mixer, uint64_t hz)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
return rffc5071_set_frequency(mixer, hz);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -107,7 +117,7 @@ uint64_t mixer_set_frequency(mixer_driver_t* const mixer, uint64_t hz)
|
||||
|
||||
void mixer_tx(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_tx(mixer);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -117,7 +127,7 @@ void mixer_tx(mixer_driver_t* const mixer)
|
||||
|
||||
void mixer_rx(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_rx(mixer);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -127,7 +137,7 @@ void mixer_rx(mixer_driver_t* const mixer)
|
||||
|
||||
void mixer_rxtx(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_rxtx(mixer);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -137,7 +147,7 @@ void mixer_rxtx(mixer_driver_t* const mixer)
|
||||
|
||||
void mixer_enable(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_enable(mixer);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -147,7 +157,7 @@ void mixer_enable(mixer_driver_t* const mixer)
|
||||
|
||||
void mixer_disable(mixer_driver_t* const mixer)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_disable(mixer);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
@@ -157,7 +167,7 @@ void mixer_disable(mixer_driver_t* const mixer)
|
||||
|
||||
void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo)
|
||||
{
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
rffc5071_set_gpo(mixer, gpo);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#ifndef __MIXER_H
|
||||
#define __MIXER_H
|
||||
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
#include "rffc5071.h"
|
||||
typedef rffc5071_driver_t mixer_driver_t;
|
||||
#endif
|
||||
|
||||
@@ -51,6 +51,9 @@ static uint32_t default_output = 0;
|
||||
*
|
||||
* To trigger the antenna switching synchronously with the sample clock, the
|
||||
* SGPIO is configured to output its clock (f=2 * sample clock) to the SCTimer.
|
||||
*
|
||||
* On Praline, instead, MS0/CLK1 (SCT_CLK) is configured to output its
|
||||
* clock (f=2 * sample clock) to the SCTimer.
|
||||
*/
|
||||
void operacake_sctimer_init()
|
||||
{
|
||||
@@ -89,6 +92,7 @@ void operacake_sctimer_init()
|
||||
P7_0,
|
||||
SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1);
|
||||
|
||||
#ifndef PRALINE
|
||||
// Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12
|
||||
SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode
|
||||
SGPIO_OUT_MUX_CFG_P_OE_CFG(0); // gpio_oe
|
||||
@@ -97,9 +101,20 @@ void operacake_sctimer_init()
|
||||
// Use the GIMA to connect the SGPIO clock to the SCTimer
|
||||
GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1
|
||||
|
||||
uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1;
|
||||
#else
|
||||
// Configure pin P6_4 as SCT_IN_6
|
||||
scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1);
|
||||
|
||||
// Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer
|
||||
GIMA_CTIN_6_IN = 0x0 << 4;
|
||||
|
||||
uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6;
|
||||
#endif
|
||||
|
||||
// We configure this register first, because the user manual says to
|
||||
SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK |
|
||||
SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1;
|
||||
sct_clock_input;
|
||||
|
||||
// Halt the SCTimer to enable it to be configured
|
||||
SCT_CTRL = SCT_CTRL_HALT_L(1);
|
||||
|
||||
@@ -23,60 +23,47 @@
|
||||
#include "firmware_info.h"
|
||||
#include "gpio_lpc.h"
|
||||
#include "hackrf_core.h"
|
||||
#include "adc.h"
|
||||
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include <libopencm3/lpc43xx/adc.h>
|
||||
|
||||
static board_id_t platform = BOARD_ID_UNDETECTED;
|
||||
static board_rev_t revision = BOARD_REV_UNDETECTED;
|
||||
|
||||
static struct gpio_t gpio2_9_on_P5_0 = GPIO(2, 9);
|
||||
static struct gpio_t gpio3_6_on_P6_10 = GPIO(3, 6);
|
||||
static struct gpio_t gpio3_4_on_P6_5 = GPIO(3, 4);
|
||||
static struct gpio_t gpio2_6_on_P4_6 = GPIO(2, 6);
|
||||
|
||||
#define P5_0_PUP (1 << 0)
|
||||
#define P5_0_PDN (1 << 1)
|
||||
#define P6_10_PUP (1 << 2)
|
||||
#define P6_10_PDN (1 << 3)
|
||||
#define P6_5_PDN (1 << 4)
|
||||
|
||||
/*
|
||||
* Jawbreaker has a pull-down on P6_10 and nothing on P5_0.
|
||||
* rad1o has a pull-down on P6_10 and a pull-down on P5_0.
|
||||
* HackRF One OG has a pull-down on P6_10 and a pull-up on P5_0.
|
||||
* HackRF One r9 has a pull-up on P6_10 and a pull-down on P5_0.
|
||||
*/
|
||||
* Praline has a pull-down on P6_5. */
|
||||
|
||||
#define JAWBREAKER_RESISTORS (P6_10_PDN)
|
||||
#define RAD1O_RESISTORS (P6_10_PDN | P5_0_PDN)
|
||||
#define HACKRF1_OG_RESISTORS (P6_10_PDN | P5_0_PUP)
|
||||
#define HACKRF1_R9_RESISTORS (P6_10_PUP | P5_0_PDN)
|
||||
#define PRALINE_RESISTORS (P6_5_PDN)
|
||||
|
||||
/*
|
||||
* LEDs are configured so that they flash if the detected hardware platform is
|
||||
* not supported by the firmware binary. Only two LEDs are flashed for a
|
||||
* hardware detection failure, but three LEDs are flashed if CPLD configuration
|
||||
* hardware detection failure, but three LEDs are flashed if CPLD/FPGA configuration
|
||||
* fails.
|
||||
*/
|
||||
static struct gpio_t gpio_led1 = GPIO(2, 1);
|
||||
static struct gpio_t gpio_led2 = GPIO(2, 2);
|
||||
static struct gpio_t gpio_led3 = GPIO(2, 8);
|
||||
|
||||
/*
|
||||
* Return 10-bit ADC result.
|
||||
*/
|
||||
uint16_t adc_read(uint8_t pin)
|
||||
{
|
||||
pin &= 0x7;
|
||||
uint8_t pin_mask = (1 << pin);
|
||||
ADC0_CR = ADC_CR_SEL(pin_mask) | ADC_CR_CLKDIV(45) | ADC_CR_PDN | ADC_CR_START(1);
|
||||
while (!(ADC0_GDR & ADC_DR_DONE) || (((ADC0_GDR >> 24) & 0x7) != pin)) {}
|
||||
return (ADC0_GDR >> 6) & 0x03FF;
|
||||
}
|
||||
|
||||
void adc_off(void)
|
||||
{
|
||||
ADC0_CR = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Starting with r6, HackRF One has pin straps on ADC pins that indicate
|
||||
* hardware revision. Those pins were unconnected prior to r6, so we test for
|
||||
@@ -109,7 +96,7 @@ uint32_t check_pin_strap(uint8_t pin)
|
||||
* scheme with ADC0_3 tied to VCC.
|
||||
*/
|
||||
// clang-format off
|
||||
static const uint8_t revision_from_adc[32] = {
|
||||
static const uint8_t hackrf_revision_from_adc[32] = {
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
@@ -144,6 +131,47 @@ static const uint8_t revision_from_adc[32] = {
|
||||
BOARD_REV_HACKRF1_R8
|
||||
};
|
||||
|
||||
/*
|
||||
* Starting with r0.1, Praline also uses a voltage on ADC0_3 to set an
|
||||
* analog voltage that indicates the hardware revision. The high five
|
||||
* bits of the ADC result are mapped to 32 revisions. Note that,
|
||||
* unlike HackRF One, Praline revisions are mapped in ascending order.
|
||||
*/
|
||||
static const uint8_t praline_revision_from_adc[32] = {
|
||||
BOARD_REV_PRALINE_R0_1,
|
||||
BOARD_REV_PRALINE_R0_2,
|
||||
BOARD_REV_PRALINE_R0_3,
|
||||
BOARD_REV_PRALINE_R1_0,
|
||||
BOARD_REV_PRALINE_R1_1,
|
||||
BOARD_REV_PRALINE_R1_2,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
BOARD_REV_UNRECOGNIZED,
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
|
||||
void detect_hardware_platform(void)
|
||||
@@ -158,6 +186,7 @@ void detect_hardware_platform(void)
|
||||
|
||||
gpio_input(&gpio2_9_on_P5_0);
|
||||
gpio_input(&gpio3_6_on_P6_10);
|
||||
gpio_input(&gpio3_4_on_P6_5);
|
||||
|
||||
/* activate internal pull-down */
|
||||
scu_pinmux(P5_0, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
@@ -174,14 +203,17 @@ void detect_hardware_platform(void)
|
||||
/* activate internal pull-up */
|
||||
scu_pinmux(P5_0, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(P6_10, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(P6_5, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
|
||||
delay_us_at_mhz(4, 96);
|
||||
/* tri-state for a moment before testing input */
|
||||
scu_pinmux(P5_0, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(P6_10, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(P6_5, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
delay_us_at_mhz(4, 96);
|
||||
/* if input fell quickly, there must be an external pull-down */
|
||||
detected_resistors |= (gpio_read(&gpio2_9_on_P5_0)) ? 0 : P5_0_PDN;
|
||||
detected_resistors |= (gpio_read(&gpio3_6_on_P6_10)) ? 0 : P6_10_PDN;
|
||||
detected_resistors |= (gpio_read(&gpio3_4_on_P6_5)) ? 0 : P6_5_PDN;
|
||||
|
||||
switch (detected_resistors) {
|
||||
case JAWBREAKER_RESISTORS:
|
||||
@@ -208,6 +240,12 @@ void detect_hardware_platform(void)
|
||||
}
|
||||
platform = BOARD_ID_HACKRF1_R9;
|
||||
break;
|
||||
case PRALINE_RESISTORS:
|
||||
if (!(supported_platform() & PLATFORM_PRALINE)) {
|
||||
halt_and_flash(3000000);
|
||||
}
|
||||
platform = BOARD_ID_PRALINE;
|
||||
break;
|
||||
default:
|
||||
platform = BOARD_ID_UNRECOGNIZED;
|
||||
halt_and_flash(1000000);
|
||||
@@ -225,7 +263,7 @@ void detect_hardware_platform(void)
|
||||
} else if (LOW(adc0_3) && HIGH(adc0_4)) {
|
||||
revision = BOARD_REV_HACKRF1_R7;
|
||||
} else if (LOW(adc0_4)) {
|
||||
revision = revision_from_adc[adc0_3 >> 5];
|
||||
revision = hackrf_revision_from_adc[adc0_3 >> 5];
|
||||
} else {
|
||||
revision = BOARD_REV_UNRECOGNIZED;
|
||||
}
|
||||
@@ -235,10 +273,27 @@ void detect_hardware_platform(void)
|
||||
} else {
|
||||
revision = BOARD_REV_UNRECOGNIZED;
|
||||
}
|
||||
} else if (platform == BOARD_ID_PRALINE) {
|
||||
revision = praline_revision_from_adc[adc0_3 >> 5];
|
||||
}
|
||||
|
||||
if ((revision > BOARD_REV_HACKRF1_OLD) && LOW(adc0_7)) {
|
||||
revision |= BOARD_REV_GSG;
|
||||
switch (platform) {
|
||||
case BOARD_ID_HACKRF1_OG:
|
||||
case BOARD_ID_HACKRF1_R9:
|
||||
if ((revision > BOARD_REV_HACKRF1_OLD) && LOW(adc0_7)) {
|
||||
revision |= BOARD_REV_GSG;
|
||||
}
|
||||
break;
|
||||
case BOARD_ID_PRALINE:
|
||||
scu_pinmux(P4_6, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
gpio_input(&gpio2_6_on_P4_6);
|
||||
if (gpio_read(&gpio2_6_on_P4_6)) {
|
||||
revision |= BOARD_REV_GSG;
|
||||
}
|
||||
scu_pinmux(P4_6, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#define PLATFORM_HACKRF1_OG (1 << 1)
|
||||
#define PLATFORM_RAD1O (1 << 2)
|
||||
#define PLATFORM_HACKRF1_R9 (1 << 3)
|
||||
#define PLATFORM_PRALINE (1 << 4)
|
||||
|
||||
typedef enum {
|
||||
BOARD_ID_JELLYBEAN = 0,
|
||||
@@ -37,6 +38,7 @@ typedef enum {
|
||||
BOARD_ID_HACKRF1_OG = 2, /* HackRF One prior to r9 */
|
||||
BOARD_ID_RAD1O = 3,
|
||||
BOARD_ID_HACKRF1_R9 = 4,
|
||||
BOARD_ID_PRALINE = 5,
|
||||
BOARD_ID_UNRECOGNIZED = 0xFE, /* tried detection but did not recognize board */
|
||||
BOARD_ID_UNDETECTED = 0xFF, /* detection not yet attempted */
|
||||
} board_id_t;
|
||||
@@ -48,11 +50,23 @@ typedef enum {
|
||||
BOARD_REV_HACKRF1_R8 = 3,
|
||||
BOARD_REV_HACKRF1_R9 = 4,
|
||||
BOARD_REV_HACKRF1_R10 = 5,
|
||||
BOARD_REV_PRALINE_R0_1 = 6,
|
||||
BOARD_REV_PRALINE_R0_2 = 7,
|
||||
BOARD_REV_PRALINE_R0_3 = 8,
|
||||
BOARD_REV_PRALINE_R1_0 = 9,
|
||||
BOARD_REV_PRALINE_R1_1 = 10,
|
||||
BOARD_REV_PRALINE_R1_2 = 11,
|
||||
BOARD_REV_GSG_HACKRF1_R6 = 0x81,
|
||||
BOARD_REV_GSG_HACKRF1_R7 = 0x82,
|
||||
BOARD_REV_GSG_HACKRF1_R8 = 0x83,
|
||||
BOARD_REV_GSG_HACKRF1_R9 = 0x84,
|
||||
BOARD_REV_GSG_HACKRF1_R10 = 0x85,
|
||||
BOARD_REV_GSG_PRALINE_R0_1 = 0x86,
|
||||
BOARD_REV_GSG_PRALINE_R0_2 = 0x87,
|
||||
BOARD_REV_GSG_PRALINE_R0_3 = 0x88,
|
||||
BOARD_REV_GSG_PRALINE_R1_0 = 0x89,
|
||||
BOARD_REV_GSG_PRALINE_R1_1 = 0x8a,
|
||||
BOARD_REV_GSG_PRALINE_R1_2 = 0x8b,
|
||||
BOARD_REV_UNRECOGNIZED =
|
||||
0xFE, /* tried detection but did not recognize revision */
|
||||
BOARD_REV_UNDETECTED = 0xFF, /* detection not yet attempted */
|
||||
|
||||
547
firmware/common/radio.c
Normal file
547
firmware/common/radio.c
Normal file
@@ -0,0 +1,547 @@
|
||||
/*
|
||||
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
* Copyright 2012 Jared Boone
|
||||
* Copyright 2013 Benjamin Vernoux
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "hackrf_core.h"
|
||||
#include "tuning.h"
|
||||
|
||||
#include "radio.h"
|
||||
|
||||
radio_error_t radio_set_sample_rate(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_sample_rate_id element,
|
||||
radio_sample_rate_t sample_rate)
|
||||
{
|
||||
// we only support the clock generator at the moment
|
||||
if (element != RADIO_SAMPLE_RATE_CLOCKGEN) {
|
||||
return RADIO_ERR_INVALID_ELEMENT;
|
||||
}
|
||||
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
/*
|
||||
* Store the sample rate rounded to the nearest Hz for convenience.
|
||||
*/
|
||||
if (sample_rate.div == 0) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
sample_rate.hz = (sample_rate.num + (sample_rate.div >> 1)) / sample_rate.div;
|
||||
|
||||
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
||||
config->sample_rate[element] = sample_rate;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
#define MAX_AFE_RATE 40000000
|
||||
#define MAX_N 5
|
||||
uint8_t n = 0; // resampling ratio is 2**n
|
||||
if ((config->mode == TRANSCEIVER_MODE_RX) ||
|
||||
(config->mode == TRANSCEIVER_MODE_RX_SWEEP)) {
|
||||
n = 1;
|
||||
uint32_t afe_rate_x2 = 2 * sample_rate.hz;
|
||||
while ((afe_rate_x2 <= MAX_AFE_RATE) && (n < MAX_N)) {
|
||||
afe_rate_x2 <<= 1;
|
||||
n++;
|
||||
}
|
||||
fpga_set_rx_decimation_ratio(&fpga, n);
|
||||
}
|
||||
config->resampling_n = n;
|
||||
bool ok = sample_rate_frac_set(sample_rate.num << n, sample_rate.div);
|
||||
if (ok) {
|
||||
config->sample_rate[element] = sample_rate;
|
||||
radio_channel_t* channel = &radio->channel[chan_id];
|
||||
radio_frequency_t frequency =
|
||||
radio_get_frequency(radio, channel->id, RADIO_FREQUENCY_RF);
|
||||
ok = radio_set_frequency(
|
||||
radio,
|
||||
channel->id,
|
||||
RADIO_FREQUENCY_RF,
|
||||
frequency);
|
||||
}
|
||||
#else
|
||||
bool ok = sample_rate_frac_set(sample_rate.num, sample_rate.div);
|
||||
#endif
|
||||
if (!ok) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
config->sample_rate[element] = sample_rate;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
radio_sample_rate_t radio_get_sample_rate(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_sample_rate_id element)
|
||||
{
|
||||
return radio->channel[chan_id].config.sample_rate[element];
|
||||
}
|
||||
|
||||
radio_error_t radio_set_filter(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_filter_id element,
|
||||
radio_filter_t filter)
|
||||
{
|
||||
// we only support the baseband filter at the moment
|
||||
if (element != RADIO_FILTER_BASEBAND) {
|
||||
return RADIO_ERR_INVALID_ELEMENT;
|
||||
}
|
||||
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
||||
config->filter[element] = filter;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
#ifndef PRALINE
|
||||
max283x_set_lpf_bandwidth(&max283x, filter.hz);
|
||||
#else
|
||||
uint32_t lpf_bandwidth =
|
||||
(config->sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN].hz * 3) / 8;
|
||||
uint32_t offset = 0;
|
||||
if (config->shift != FPGA_QUARTER_SHIFT_MODE_NONE) {
|
||||
offset = (config->sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN].hz
|
||||
<< config->resampling_n) /
|
||||
8;
|
||||
}
|
||||
lpf_bandwidth += offset * 2;
|
||||
max2831_set_lpf_bandwidth(&max283x, lpf_bandwidth);
|
||||
#endif
|
||||
|
||||
config->filter[element] = filter;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
radio_filter_t radio_get_filter(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_filter_id element)
|
||||
{
|
||||
return radio->channel[chan_id].config.filter[element];
|
||||
}
|
||||
|
||||
radio_error_t radio_set_gain(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_gain_id element,
|
||||
radio_gain_t gain)
|
||||
{
|
||||
if (element > RADIO_GAIN_COUNT) {
|
||||
return RADIO_ERR_INVALID_ELEMENT;
|
||||
}
|
||||
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
||||
config->gain[element] = gain;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
uint8_t real_db;
|
||||
switch (element) {
|
||||
case RADIO_GAIN_RF_AMP:
|
||||
rf_path_set_lna(&rf_path, gain.enable);
|
||||
break;
|
||||
case RADIO_GAIN_RX_LNA:
|
||||
#ifndef PRALINE
|
||||
real_db = max283x_set_lna_gain(&max283x, gain.db);
|
||||
#else
|
||||
real_db = max2831_set_lna_gain(&max283x, gain.db);
|
||||
#endif
|
||||
if (real_db == 0) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
break;
|
||||
case RADIO_GAIN_RX_VGA:
|
||||
#ifndef PRALINE
|
||||
real_db = max283x_set_vga_gain(&max283x, gain.db);
|
||||
#else
|
||||
real_db = max2831_set_vga_gain(&max283x, gain.db);
|
||||
#endif
|
||||
if (real_db == 0) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
break;
|
||||
case RADIO_GAIN_TX_VGA:
|
||||
#ifndef PRALINE
|
||||
real_db = max283x_set_txvga_gain(&max283x, gain.db);
|
||||
#else
|
||||
real_db = max2831_set_txvga_gain(&max283x, gain.db);
|
||||
#endif
|
||||
if (real_db == 0) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
config->gain[element] = gain;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
radio_gain_t radio_get_gain(radio_t* radio, radio_chan_id chan_id, radio_gain_id element)
|
||||
{
|
||||
return radio->channel[chan_id].config.gain[element];
|
||||
}
|
||||
|
||||
radio_error_t radio_set_frequency(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_frequency_id element,
|
||||
radio_frequency_t frequency)
|
||||
{
|
||||
// we only support setting the final rf frequency at the moment
|
||||
if (element != RADIO_FREQUENCY_RF) {
|
||||
return RADIO_ERR_INVALID_ELEMENT;
|
||||
}
|
||||
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
||||
config->frequency[element] = frequency;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
// explicit
|
||||
if (frequency.if_hz || frequency.lo_hz) {
|
||||
bool ok = set_freq_explicit(
|
||||
frequency.if_hz,
|
||||
frequency.lo_hz,
|
||||
frequency.path);
|
||||
#ifdef PRALINE
|
||||
if (ok) {
|
||||
fpga_set_rx_quarter_shift_mode(
|
||||
&fpga,
|
||||
FPGA_QUARTER_SHIFT_MODE_NONE);
|
||||
config->shift = FPGA_QUARTER_SHIFT_MODE_NONE;
|
||||
radio_channel_t* channel = &radio->channel[chan_id];
|
||||
radio_filter_t filter = radio_get_filter(
|
||||
radio,
|
||||
channel->id,
|
||||
RADIO_FILTER_BASEBAND);
|
||||
ok = radio_set_filter(
|
||||
radio,
|
||||
channel->id,
|
||||
RADIO_FILTER_BASEBAND,
|
||||
filter);
|
||||
}
|
||||
#endif
|
||||
if (!ok) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
config->frequency[element] = frequency;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
// auto-tune
|
||||
bool ok;
|
||||
#ifndef PRALINE
|
||||
switch (config->mode) {
|
||||
case TRANSCEIVER_MODE_RX:
|
||||
case TRANSCEIVER_MODE_RX_SWEEP:
|
||||
case TRANSCEIVER_MODE_TX:
|
||||
ok = set_freq(frequency.hz);
|
||||
break;
|
||||
default:
|
||||
return RADIO_ERR_INVALID_CONFIG;
|
||||
}
|
||||
#else
|
||||
const tune_config_t* tune_config;
|
||||
switch (config->mode) {
|
||||
case TRANSCEIVER_MODE_RX:
|
||||
tune_config = praline_tune_config_rx;
|
||||
break;
|
||||
case TRANSCEIVER_MODE_RX_SWEEP:
|
||||
tune_config = praline_tune_config_rx_sweep;
|
||||
break;
|
||||
case TRANSCEIVER_MODE_TX:
|
||||
tune_config = praline_tune_config_tx;
|
||||
break;
|
||||
default:
|
||||
return RADIO_ERR_INVALID_CONFIG;
|
||||
}
|
||||
bool found = false;
|
||||
for (; (tune_config->rf_range_end_mhz != 0) || (tune_config->if_mhz != 0);
|
||||
tune_config++) {
|
||||
if ((frequency.hz == 0) ||
|
||||
(tune_config->rf_range_end_mhz > (frequency.hz / FREQ_ONE_MHZ))) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
fpga_set_rx_quarter_shift_mode(&fpga, tune_config->shift);
|
||||
config->shift = tune_config->shift;
|
||||
uint32_t offset = (config->sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN].hz
|
||||
<< config->resampling_n) /
|
||||
8;
|
||||
ok = tuning_set_frequency(tune_config, frequency.hz, offset);
|
||||
if (ok) {
|
||||
radio_channel_t* channel = &radio->channel[chan_id];
|
||||
radio_filter_t filter =
|
||||
radio_get_filter(radio, channel->id, RADIO_FILTER_BASEBAND);
|
||||
ok = radio_set_filter(radio, channel->id, RADIO_FILTER_BASEBAND, filter);
|
||||
}
|
||||
#endif
|
||||
if (!ok) {
|
||||
return RADIO_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
config->frequency[element] = frequency;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
radio_frequency_t radio_get_frequency(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_frequency_id element)
|
||||
{
|
||||
return radio->channel[chan_id].config.frequency[element];
|
||||
}
|
||||
|
||||
radio_error_t radio_set_antenna(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_antenna_id element,
|
||||
radio_antenna_t value)
|
||||
{
|
||||
if (element > RADIO_ANTENNA_COUNT) {
|
||||
return RADIO_ERR_INVALID_ELEMENT;
|
||||
}
|
||||
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
if (config->mode == TRANSCEIVER_MODE_OFF) {
|
||||
config->antenna[element] = value;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
switch (element) {
|
||||
case RADIO_ANTENNA_BIAS_TEE:
|
||||
rf_path_set_antenna(
|
||||
&rf_path,
|
||||
config->antenna[RADIO_ANTENNA_BIAS_TEE].enable);
|
||||
break;
|
||||
}
|
||||
|
||||
config->antenna[element] = value;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
radio_antenna_t radio_get_antenna(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_antenna_id element)
|
||||
{
|
||||
return radio->channel[chan_id].config.antenna[element];
|
||||
}
|
||||
|
||||
radio_error_t radio_set_clock(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_clock_id element,
|
||||
radio_clock_t value)
|
||||
{
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
if (element > RADIO_CLOCK_COUNT) {
|
||||
return RADIO_ERR_INVALID_ELEMENT;
|
||||
}
|
||||
|
||||
// CLKIN is not supported as it is automatically detected from hardware state
|
||||
if (element == RADIO_CLOCK_CLKIN) {
|
||||
return RADIO_ERR_UNSUPPORTED_OPERATION;
|
||||
}
|
||||
|
||||
si5351c_clkout_enable(&clock_gen, value.enable);
|
||||
|
||||
config->clock[element] = value;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
radio_clock_t radio_get_clock(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_clock_id element)
|
||||
{
|
||||
if (element == RADIO_CLOCK_CLKIN) {
|
||||
return (radio_clock_t){
|
||||
.enable = si5351c_clkin_signal_valid(&clock_gen),
|
||||
};
|
||||
}
|
||||
|
||||
return radio->channel[chan_id].config.clock[element];
|
||||
}
|
||||
|
||||
radio_error_t radio_set_trigger_enable(radio_t* radio, radio_chan_id chan_id, bool enable)
|
||||
{
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
config->trigger_enable = enable;
|
||||
return RADIO_OK;
|
||||
}
|
||||
|
||||
bool radio_get_trigger_enable(radio_t* radio, radio_chan_id chan_id)
|
||||
{
|
||||
return radio->channel[chan_id].config.trigger_enable;
|
||||
}
|
||||
|
||||
transceiver_mode_t radio_get_mode(radio_t* radio, radio_chan_id chan_id)
|
||||
{
|
||||
return radio->channel[chan_id].config.mode;
|
||||
}
|
||||
|
||||
rf_path_direction_t radio_get_direction(radio_t* radio, radio_chan_id chan_id)
|
||||
{
|
||||
radio_config_t* config = &radio->channel[chan_id].config;
|
||||
|
||||
switch (config->mode) {
|
||||
case TRANSCEIVER_MODE_RX:
|
||||
case TRANSCEIVER_MODE_RX_SWEEP:
|
||||
return RF_PATH_DIRECTION_RX;
|
||||
case TRANSCEIVER_MODE_TX:
|
||||
return RF_PATH_DIRECTION_TX;
|
||||
default:
|
||||
return RF_PATH_DIRECTION_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
clock_source_t radio_get_clock_source(radio_t* radio, radio_chan_id chan_id)
|
||||
{
|
||||
return radio->channel[chan_id].clock_source;
|
||||
}
|
||||
|
||||
radio_error_t radio_switch_mode(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
transceiver_mode_t mode)
|
||||
{
|
||||
radio_error_t result;
|
||||
radio_channel_t* channel = &radio->channel[chan_id];
|
||||
radio_config_t* config = &channel->config;
|
||||
|
||||
// configure firmware direction from mode (but don't configure the hardware yet!)
|
||||
rf_path_direction_t direction;
|
||||
switch (mode) {
|
||||
case TRANSCEIVER_MODE_RX:
|
||||
case TRANSCEIVER_MODE_RX_SWEEP:
|
||||
direction = RF_PATH_DIRECTION_RX;
|
||||
break;
|
||||
case TRANSCEIVER_MODE_TX:
|
||||
direction = RF_PATH_DIRECTION_TX;
|
||||
break;
|
||||
default:
|
||||
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
|
||||
config->mode = mode;
|
||||
return RADIO_OK;
|
||||
}
|
||||
config->mode = mode;
|
||||
|
||||
// sample rate
|
||||
radio_sample_rate_t sample_rate =
|
||||
radio_get_sample_rate(radio, channel->id, RADIO_SAMPLE_RATE_CLOCKGEN);
|
||||
result = radio_set_sample_rate(
|
||||
radio,
|
||||
channel->id,
|
||||
RADIO_SAMPLE_RATE_CLOCKGEN,
|
||||
sample_rate);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Because of offset tuning on Praline, the sample rate can affect the
|
||||
* tuning configuration, so radio_set_sample_rate() calls
|
||||
* radio_set_frequency(). Also because of offset tuning, the tuning
|
||||
* configuration can affect the baseband filter bandwidth (in addition
|
||||
* to the filter bandwidth being automatically based on the sample
|
||||
* rate), so radio_set_frequency() calls radio_set_filter().
|
||||
*/
|
||||
#ifndef PRALINE
|
||||
// tuning frequency
|
||||
radio_frequency_t frequency =
|
||||
radio_get_frequency(radio, channel->id, RADIO_FREQUENCY_RF);
|
||||
result = radio_set_frequency(radio, channel->id, RADIO_FREQUENCY_RF, frequency);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// baseband filter bandwidth
|
||||
radio_filter_t filter =
|
||||
radio_get_filter(radio, channel->id, RADIO_FILTER_BASEBAND);
|
||||
result = radio_set_filter(radio, channel->id, RADIO_FILTER_BASEBAND, filter);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
// rf_amp enable
|
||||
radio_gain_t enable = radio_get_gain(radio, channel->id, RADIO_GAIN_RF_AMP);
|
||||
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RF_AMP, enable);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// gain
|
||||
radio_gain_t gain;
|
||||
if (config->mode == TRANSCEIVER_MODE_RX ||
|
||||
config->mode == TRANSCEIVER_MODE_RX_SWEEP) {
|
||||
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_RX_LNA);
|
||||
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RX_LNA, gain);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_RX_VGA);
|
||||
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RX_VGA, gain);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
} else if (config->mode == TRANSCEIVER_MODE_TX) {
|
||||
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_TX_VGA);
|
||||
result = radio_set_gain(radio, channel->id, RADIO_GAIN_TX_VGA, gain);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// antenna
|
||||
radio_antenna_t bias_tee =
|
||||
radio_get_antenna(radio, channel->id, RADIO_ANTENNA_BIAS_TEE);
|
||||
result = radio_set_antenna(radio, channel->id, RADIO_ANTENNA_BIAS_TEE, bias_tee);
|
||||
if (result != RADIO_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// finally, set the rf path direction
|
||||
rf_path_set_direction(&rf_path, direction);
|
||||
|
||||
return RADIO_OK;
|
||||
}
|
||||
265
firmware/common/radio.h
Normal file
265
firmware/common/radio.h
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
* Copyright 2012 Jared Boone
|
||||
* Copyright 2013 Benjamin Vernoux
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __RF_CONFIG_H__
|
||||
#define __RF_CONFIG_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "rf_path.h"
|
||||
#include "fpga.h"
|
||||
|
||||
typedef enum {
|
||||
RADIO_OK = 1,
|
||||
RADIO_ERR_INVALID_PARAM = -2,
|
||||
RADIO_ERR_INVALID_CONFIG = -3,
|
||||
RADIO_ERR_INVALID_CHANNEL = -4,
|
||||
RADIO_ERR_INVALID_ELEMENT = -5,
|
||||
RADIO_ERR_UNSUPPORTED_OPERATION = -10,
|
||||
RADIO_ERR_UNIMPLEMENTED = -19,
|
||||
RADIO_ERR_OTHER = -9999,
|
||||
} radio_error_t;
|
||||
|
||||
typedef enum {
|
||||
RADIO_CHANNEL0 = 0x00,
|
||||
} radio_chan_id;
|
||||
|
||||
typedef enum {
|
||||
RADIO_FILTER_BASEBAND = 0x00,
|
||||
} radio_filter_id;
|
||||
|
||||
typedef enum {
|
||||
RADIO_SAMPLE_RATE_CLOCKGEN = 0x00,
|
||||
} radio_sample_rate_id;
|
||||
|
||||
typedef enum {
|
||||
RADIO_FREQUENCY_RF = 0x00,
|
||||
RADIO_FREQUENCY_IF = 0x01,
|
||||
RADIO_FREQUENCY_OF = 0x02,
|
||||
} radio_frequency_id;
|
||||
|
||||
typedef enum {
|
||||
RADIO_GAIN_RF_AMP = 0x00,
|
||||
RADIO_GAIN_RX_LNA = 0x01,
|
||||
RADIO_GAIN_RX_VGA = 0x02,
|
||||
RADIO_GAIN_TX_VGA = 0x03,
|
||||
} radio_gain_id;
|
||||
|
||||
typedef enum {
|
||||
RADIO_ANTENNA_BIAS_TEE = 0x00,
|
||||
} radio_antenna_id;
|
||||
|
||||
typedef enum {
|
||||
RADIO_CLOCK_CLKIN = 0x00,
|
||||
RADIO_CLOCK_CLKOUT = 0x01,
|
||||
} radio_clock_id;
|
||||
|
||||
typedef struct {
|
||||
uint32_t num;
|
||||
uint32_t div;
|
||||
uint32_t hz;
|
||||
} radio_sample_rate_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t hz;
|
||||
} radio_filter_t;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
bool enable;
|
||||
uint8_t db;
|
||||
};
|
||||
} radio_gain_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t hz; // desired frequency
|
||||
uint64_t if_hz; // intermediate frequency
|
||||
uint64_t lo_hz; // front-end local oscillator frequency
|
||||
uint8_t path; // image rejection filter path
|
||||
} radio_frequency_t;
|
||||
|
||||
typedef struct {
|
||||
bool enable;
|
||||
} radio_antenna_t;
|
||||
|
||||
typedef struct {
|
||||
bool enable;
|
||||
} radio_clock_t;
|
||||
|
||||
// legacy type, moved from hackrf_core
|
||||
typedef enum {
|
||||
CLOCK_SOURCE_HACKRF = 0,
|
||||
CLOCK_SOURCE_EXTERNAL = 1,
|
||||
CLOCK_SOURCE_PORTAPACK = 2,
|
||||
} clock_source_t;
|
||||
|
||||
// legacy type, moved from usb_api_transceiver
|
||||
typedef enum {
|
||||
TRANSCEIVER_MODE_OFF = 0,
|
||||
TRANSCEIVER_MODE_RX = 1,
|
||||
TRANSCEIVER_MODE_TX = 2,
|
||||
TRANSCEIVER_MODE_SS = 3,
|
||||
TRANSCEIVER_MODE_CPLD_UPDATE = 4,
|
||||
TRANSCEIVER_MODE_RX_SWEEP = 5,
|
||||
} transceiver_mode_t;
|
||||
|
||||
#define RADIO_CHANNEL_COUNT 1
|
||||
#define RADIO_SAMPLE_RATE_COUNT 1
|
||||
#define RADIO_FILTER_COUNT 1
|
||||
#define RADIO_FREQUENCY_COUNT 4
|
||||
#define RADIO_GAIN_COUNT 4
|
||||
#define RADIO_ANTENNA_COUNT 1
|
||||
#define RADIO_CLOCK_COUNT 2
|
||||
#define RADIO_MODE_COUNT 6
|
||||
|
||||
typedef struct {
|
||||
// sample rate elements
|
||||
radio_sample_rate_t sample_rate[RADIO_SAMPLE_RATE_COUNT];
|
||||
|
||||
// filter elements
|
||||
radio_filter_t filter[RADIO_FILTER_COUNT];
|
||||
|
||||
// gain elements
|
||||
radio_gain_t gain[RADIO_GAIN_COUNT];
|
||||
|
||||
// frequency elements
|
||||
radio_frequency_t frequency[RADIO_FREQUENCY_COUNT];
|
||||
|
||||
// antenna elements
|
||||
radio_antenna_t antenna[RADIO_ANTENNA_COUNT];
|
||||
|
||||
// clock elements
|
||||
radio_clock_t clock[RADIO_CLOCK_COUNT];
|
||||
|
||||
// trigger elements
|
||||
bool trigger_enable;
|
||||
|
||||
// currently active transceiver mode
|
||||
transceiver_mode_t mode;
|
||||
|
||||
#ifdef PRALINE
|
||||
// resampling ratio is 2**n
|
||||
uint8_t resampling_n;
|
||||
|
||||
// quarter-rate shift configuration for offset tuning
|
||||
fpga_quarter_shift_mode_t shift;
|
||||
#endif
|
||||
|
||||
} radio_config_t;
|
||||
|
||||
typedef struct radio_channel_t {
|
||||
radio_chan_id id;
|
||||
radio_config_t config;
|
||||
radio_config_t mode[RADIO_MODE_COUNT];
|
||||
clock_source_t clock_source;
|
||||
} radio_channel_t;
|
||||
|
||||
typedef struct radio_t {
|
||||
radio_channel_t channel[RADIO_CHANNEL_COUNT];
|
||||
} radio_t;
|
||||
|
||||
/**
|
||||
* API Notes
|
||||
*
|
||||
* - All radio_set_*() functions return a radio_error_t
|
||||
* - radio_set_*() functions work as follows:
|
||||
* - if the channel mode is TRANSCEIVER_MODE_OFF only the configuration will
|
||||
* be updated, the hardware state will remain unaffected.
|
||||
* - if the channel is something other than TRANSCEIVER_MODE_OFF both the
|
||||
* configuration and hardware state will be updated.
|
||||
* - this makes it possible to maintain multiple channel configurations and
|
||||
* switch between them with a single call to radio_switch_mode()
|
||||
*/
|
||||
|
||||
radio_error_t radio_set_sample_rate(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_sample_rate_id element,
|
||||
radio_sample_rate_t sample_rate);
|
||||
radio_sample_rate_t radio_get_sample_rate(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_sample_rate_id element);
|
||||
|
||||
radio_error_t radio_set_filter(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_filter_id element,
|
||||
radio_filter_t filter);
|
||||
radio_filter_t radio_get_filter(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_filter_id element);
|
||||
|
||||
radio_error_t radio_set_frequency(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_frequency_id element,
|
||||
radio_frequency_t frequency);
|
||||
radio_frequency_t radio_get_frequency(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_frequency_id element);
|
||||
|
||||
radio_error_t radio_set_gain(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_gain_id element,
|
||||
radio_gain_t gain);
|
||||
radio_gain_t radio_get_gain(radio_t* radio, radio_chan_id chan_id, radio_gain_id element);
|
||||
|
||||
radio_error_t radio_set_antenna(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_antenna_id element,
|
||||
radio_antenna_t value);
|
||||
radio_antenna_t radio_get_antenna(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_antenna_id element);
|
||||
|
||||
radio_error_t radio_set_clock(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_clock_id element,
|
||||
radio_clock_t value);
|
||||
radio_clock_t radio_get_clock(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
radio_clock_id element);
|
||||
|
||||
radio_error_t radio_set_trigger_enable(radio_t* radio, radio_chan_id chan_id, bool enable);
|
||||
bool radio_get_trigger_enable(radio_t* radio, radio_chan_id chan_id);
|
||||
|
||||
transceiver_mode_t radio_get_mode(radio_t* radio, radio_chan_id chan_id);
|
||||
rf_path_direction_t radio_get_direction(radio_t* radio, radio_chan_id chan_id);
|
||||
clock_source_t radio_get_clock_source(radio_t* radio, radio_chan_id chan_id);
|
||||
|
||||
// apply the current channel configuration and switch to the given transceiver mode
|
||||
radio_error_t radio_switch_mode(
|
||||
radio_t* radio,
|
||||
radio_chan_id chan_id,
|
||||
transceiver_mode_t mode);
|
||||
|
||||
#endif /*__RF_CONFIG_H__*/
|
||||
@@ -31,12 +31,13 @@
|
||||
#include "gpio_lpc.h"
|
||||
#include "platform_detect.h"
|
||||
#include "mixer.h"
|
||||
#include "max2831.h"
|
||||
#include "max283x.h"
|
||||
#include "max5864.h"
|
||||
#include "sgpio.h"
|
||||
#include "user_config.h"
|
||||
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O || defined PRALINE)
|
||||
/*
|
||||
* RF switches on Jawbreaker are controlled by General Purpose Outputs (GPO) on
|
||||
* the RFFC5072.
|
||||
@@ -48,6 +49,9 @@
|
||||
*
|
||||
* The rad1o also uses GPIO pins to control the different switches. The amplifiers
|
||||
* are also connected to the LPC.
|
||||
*
|
||||
* On Praline, a subset of control signals is managed by GPIO pins on the LPC, while
|
||||
* the remaining signals are generated through combinatorial logic in hardware.
|
||||
*/
|
||||
#define SWITCHCTRL_NO_TX_AMP_PWR (1 << 0) /* GPO1 turn off TX amp power */
|
||||
#define SWITCHCTRL_AMP_BYPASS (1 << 1) /* GPO2 bypass amp section */
|
||||
@@ -91,16 +95,13 @@
|
||||
|
||||
#define SWITCHCTRL_ANT_PWR (1 << 6) /* turn on antenna port power */
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
/*
|
||||
* Starting with HackRF One r9 this control signal has been moved to the
|
||||
* microcontroller.
|
||||
* In HackRF One r9 this control signal has been moved to the microcontroller.
|
||||
*/
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
static struct gpio_t gpio_h1r9_no_ant_pwr = GPIO(2, 4);
|
||||
#endif
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl)
|
||||
{
|
||||
if (ctrl & SWITCHCTRL_TX) {
|
||||
@@ -192,6 +193,47 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl)
|
||||
{
|
||||
if (ctrl & SWITCHCTRL_TX) {
|
||||
gpio_set(rf_path->gpio_tx_en);
|
||||
if (ctrl & SWITCHCTRL_NO_TX_AMP_PWR) {
|
||||
ctrl |= SWITCHCTRL_AMP_BYPASS;
|
||||
}
|
||||
} else {
|
||||
gpio_clear(rf_path->gpio_tx_en);
|
||||
if (ctrl & SWITCHCTRL_NO_RX_AMP_PWR) {
|
||||
ctrl |= SWITCHCTRL_AMP_BYPASS;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctrl & SWITCHCTRL_MIX_BYPASS) {
|
||||
gpio_set(rf_path->gpio_mix_en_n);
|
||||
} else {
|
||||
gpio_clear(rf_path->gpio_mix_en_n);
|
||||
}
|
||||
|
||||
if (ctrl & SWITCHCTRL_HP) {
|
||||
gpio_clear(rf_path->gpio_lpf_en);
|
||||
} else {
|
||||
gpio_set(rf_path->gpio_lpf_en);
|
||||
}
|
||||
|
||||
if (ctrl & SWITCHCTRL_AMP_BYPASS) {
|
||||
gpio_clear(rf_path->gpio_rf_amp_en);
|
||||
} else {
|
||||
gpio_set(rf_path->gpio_rf_amp_en);
|
||||
}
|
||||
|
||||
if (ctrl & SWITCHCTRL_ANT_PWR) {
|
||||
gpio_clear(rf_path->gpio_ant_bias_en_n);
|
||||
} else {
|
||||
gpio_set(rf_path->gpio_ant_bias_en_n);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RAD1O
|
||||
static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl)
|
||||
{
|
||||
@@ -264,6 +306,8 @@ static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo)
|
||||
mixer_set_gpo(&mixer, gpo);
|
||||
#elif HACKRF_ONE
|
||||
switchctrl_set_hackrf_one(rf_path, gpo);
|
||||
#elif PRALINE
|
||||
switchctrl_set_praline(rf_path, gpo);
|
||||
#elif RAD1O
|
||||
switchctrl_set_rad1o(rf_path, gpo);
|
||||
#else
|
||||
@@ -357,6 +401,36 @@ void rf_path_pin_setup(rf_path_t* const rf_path)
|
||||
gpio_output(rf_path->gpio_low_high_filt_n);
|
||||
gpio_output(rf_path->gpio_tx_amp);
|
||||
gpio_output(rf_path->gpio_rx_lna);
|
||||
#elif PRALINE
|
||||
/* Configure RF switch control signals */
|
||||
scu_pinmux(SCU_TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
board_rev_t rev = detected_revision();
|
||||
if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) {
|
||||
scu_pinmux(SCU_MIX_EN_N_R1_0, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
|
||||
} else {
|
||||
scu_pinmux(SCU_MIX_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
}
|
||||
scu_pinmux(SCU_LPF_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_RF_AMP_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
|
||||
/* Configure antenna port power control signal */
|
||||
scu_pinmux(SCU_ANT_BIAS_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
|
||||
/* Configure RF power supply (VAA) switch */
|
||||
scu_pinmux(SCU_NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
|
||||
/*
|
||||
* Safe (initial) switch settings turn off both amplifiers and antenna port
|
||||
* power and enable both amp bypass and mixer bypass.
|
||||
*/
|
||||
switchctrl_set(rf_path, SWITCHCTRL_SAFE);
|
||||
|
||||
/* Configure RF switch control signals as outputs */
|
||||
gpio_output(rf_path->gpio_ant_bias_en_n);
|
||||
gpio_output(rf_path->gpio_tx_en);
|
||||
gpio_output(rf_path->gpio_mix_en_n);
|
||||
gpio_output(rf_path->gpio_lpf_en);
|
||||
gpio_output(rf_path->gpio_rf_amp_en);
|
||||
#else
|
||||
(void) rf_path; /* silence unused param warning */
|
||||
#endif
|
||||
@@ -369,12 +443,17 @@ void rf_path_init(rf_path_t* const rf_path)
|
||||
max5864_shutdown(&max5864);
|
||||
|
||||
ssp1_set_mode_max283x();
|
||||
#ifdef PRALINE
|
||||
max2831_setup(&max283x);
|
||||
max2831_start(&max283x);
|
||||
#else
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
max283x_setup(&max283x, MAX2839_VARIANT);
|
||||
} else {
|
||||
max283x_setup(&max283x, MAX2837_VARIANT);
|
||||
}
|
||||
max283x_start(&max283x);
|
||||
#endif
|
||||
|
||||
// On HackRF One, the mixer is now set up earlier in boot.
|
||||
#ifndef HACKRF_ONE
|
||||
@@ -407,7 +486,11 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d
|
||||
ssp1_set_mode_max5864();
|
||||
max5864_tx(&max5864);
|
||||
ssp1_set_mode_max283x();
|
||||
#ifdef PRALINE
|
||||
max2831_tx(&max283x);
|
||||
#else
|
||||
max283x_tx(&max283x);
|
||||
#endif
|
||||
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_TX);
|
||||
break;
|
||||
|
||||
@@ -426,10 +509,31 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d
|
||||
ssp1_set_mode_max5864();
|
||||
max5864_rx(&max5864);
|
||||
ssp1_set_mode_max283x();
|
||||
#ifdef PRALINE
|
||||
max2831_rx(&max283x);
|
||||
#else
|
||||
max283x_rx(&max283x);
|
||||
#endif
|
||||
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
|
||||
break;
|
||||
|
||||
#ifdef PRALINE
|
||||
case RF_PATH_DIRECTION_TX_CALIBRATION:
|
||||
case RF_PATH_DIRECTION_RX_CALIBRATION:
|
||||
rf_path->switchctrl &= ~SWITCHCTRL_TX;
|
||||
mixer_disable(&mixer);
|
||||
ssp1_set_mode_max5864();
|
||||
max5864_xcvr(&max5864);
|
||||
ssp1_set_mode_max283x();
|
||||
if (direction == RF_PATH_DIRECTION_TX_CALIBRATION) {
|
||||
max2831_tx_calibration(&max283x);
|
||||
} else {
|
||||
max2831_rx_calibration(&max283x);
|
||||
}
|
||||
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case RF_PATH_DIRECTION_OFF:
|
||||
default:
|
||||
rf_path_set_lna(rf_path, 0);
|
||||
@@ -439,7 +543,11 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d
|
||||
ssp1_set_mode_max5864();
|
||||
max5864_standby(&max5864);
|
||||
ssp1_set_mode_max283x();
|
||||
#ifdef PRALINE
|
||||
max2831_set_mode(&max283x, MAX2831_MODE_STANDBY);
|
||||
#else
|
||||
max283x_set_mode(&max283x, MAX283x_MODE_STANDBY);
|
||||
#endif
|
||||
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ typedef enum {
|
||||
RF_PATH_DIRECTION_OFF,
|
||||
RF_PATH_DIRECTION_RX,
|
||||
RF_PATH_DIRECTION_TX,
|
||||
#ifdef PRALINE
|
||||
RF_PATH_DIRECTION_TX_CALIBRATION,
|
||||
RF_PATH_DIRECTION_RX_CALIBRATION,
|
||||
#endif
|
||||
} rf_path_direction_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -70,6 +74,13 @@ typedef struct rf_path_t {
|
||||
gpio_t gpio_tx_amp;
|
||||
gpio_t gpio_rx_lna;
|
||||
#endif
|
||||
#ifdef PRALINE
|
||||
gpio_t gpio_tx_en;
|
||||
gpio_t gpio_mix_en_n;
|
||||
gpio_t gpio_lpf_en;
|
||||
gpio_t gpio_rf_amp_en;
|
||||
gpio_t gpio_ant_bias_en_n;
|
||||
#endif
|
||||
} rf_path_t;
|
||||
|
||||
void rf_path_pin_setup(rf_path_t* const rf_path);
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
#include <string.h>
|
||||
#include "rffc5071.h"
|
||||
#include "rffc5071_regs.def" // private register def macros
|
||||
#include "selftest.h"
|
||||
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include "hackrf_core.h"
|
||||
|
||||
/* Default register values. */
|
||||
@@ -51,7 +53,7 @@ static const uint16_t rffc5071_regs_default[RFFC5071_NUM_REGS] = {
|
||||
0xff00, /* 08 */
|
||||
0x8220, /* 09 */
|
||||
0x0202, /* 0A */
|
||||
0x4800, /* 0B */
|
||||
0x0400, /* 0B */
|
||||
0x1a94, /* 0C */
|
||||
0xd89d, /* 0D */
|
||||
0x8900, /* 0E */
|
||||
@@ -69,7 +71,7 @@ static const uint16_t rffc5071_regs_default[RFFC5071_NUM_REGS] = {
|
||||
0x0000, /* 1A */
|
||||
0x0000, /* 1B */
|
||||
0xc840, /* 1C */
|
||||
0x1000, /* 1D */
|
||||
0x0000, /* 1D, readsel = 0b0000 */
|
||||
0x0005,
|
||||
/* 1E */};
|
||||
|
||||
@@ -81,6 +83,11 @@ void rffc5071_init(rffc5071_driver_t* const drv)
|
||||
|
||||
/* Write default register values to chip. */
|
||||
rffc5071_regs_commit(drv);
|
||||
|
||||
selftest.mixer_id = rffc5071_reg_read(drv, RFFC5071_READBACK_REG);
|
||||
if ((selftest.mixer_id >> 3) != 4416) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -92,6 +99,12 @@ void rffc5071_setup(rffc5071_driver_t* const drv)
|
||||
gpio_set(drv->gpio_reset);
|
||||
gpio_output(drv->gpio_reset);
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Configure mixer PLL lock detect pin */
|
||||
scu_pinmux(SCU_MIXER_LD, SCU_MIXER_LD_PINCFG);
|
||||
gpio_input(drv->gpio_ld);
|
||||
#endif
|
||||
|
||||
rffc5071_init(drv);
|
||||
|
||||
/* initial setup */
|
||||
@@ -109,6 +122,11 @@ void rffc5071_setup(rffc5071_driver_t* const drv)
|
||||
/* GPOs are active at all times */
|
||||
set_RFFC5071_GATE(drv, 1);
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Enable GPO Lock output signal */
|
||||
set_RFFC5071_LOCK(drv, 1);
|
||||
#endif
|
||||
|
||||
rffc5071_regs_commit(drv);
|
||||
}
|
||||
|
||||
@@ -231,19 +249,22 @@ uint64_t rffc5071_config_synth(rffc5071_driver_t* const drv, uint64_t lo)
|
||||
|
||||
fvco = lo << n_lo;
|
||||
|
||||
/* higher divider and charge pump current required above
|
||||
* 3.2GHz. Programming guide says these values (fbkdiv, n,
|
||||
* maybe pump?) can be changed back after enable in order to
|
||||
* improve phase noise, since the VCO will already be stable
|
||||
* and will be unaffected. */
|
||||
/*
|
||||
* Higher charge pump leakage setting is required above 3.2 GHz.
|
||||
*/
|
||||
if (fvco > (3200 * FREQ_ONE_MHZ)) {
|
||||
fbkdivlog = 2;
|
||||
set_RFFC5071_PLLCPL(drv, 3);
|
||||
} else {
|
||||
fbkdivlog = 1;
|
||||
set_RFFC5071_PLLCPL(drv, 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Supposedly fbkdivlog can be set to 1 when VCO is below 3.2 GHz, but
|
||||
* this has resulted in tuning instability on some boards, most evident
|
||||
* in RX sweep mode.
|
||||
*/
|
||||
fbkdivlog = 2;
|
||||
|
||||
uint64_t tmp_n = (fvco << (24ULL - fbkdivlog)) / REF_FREQ;
|
||||
|
||||
/* Round to nearest step = ref_MHz / 2**s. For s=6, step=625000 Hz */
|
||||
@@ -292,3 +313,113 @@ void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t gpo)
|
||||
|
||||
rffc5071_regs_commit(drv);
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state)
|
||||
{
|
||||
// The RFFC5072 can be configured to output PLL lock status on
|
||||
// GPO4. The lock detect signal is produced by a window detector
|
||||
// on the VCO tuning voltage. It goes high to show PLL lock when
|
||||
// the VCO tuning voltage is within the specified range, typically
|
||||
// 0.30V to 1.25V.
|
||||
//
|
||||
// During the tuning process the lock signal will often go high,
|
||||
// only to drop lock briefly before returning to the locked state.
|
||||
//
|
||||
// Therefore, to reliably detect lock it is necessary to also
|
||||
// track the state of the FSM that controls the tuning process.
|
||||
//
|
||||
// Before re-tuning begins, and after final lock has been
|
||||
// established, the FSM can be considered to be in STATE_LOCKED.
|
||||
//
|
||||
// The very first state change only occurs around 150us _after_ a
|
||||
// new frequency has been set and registers updated:
|
||||
//
|
||||
// L 123456L (STATE)
|
||||
// ----___------_--- (LD)
|
||||
//
|
||||
// This means we need to track the state(s) that occur before
|
||||
// STATE_LOCKED to be able to reliably identify lock.
|
||||
//
|
||||
// At the time of writing 15 different states have been spotted in
|
||||
// the wild.
|
||||
//
|
||||
// The first six states occur at some point during most tuning
|
||||
// operations with the others occuring less frequently.
|
||||
//
|
||||
// Of the first six, two states have been identified as
|
||||
// STATE_PRELOCKn which, once entered, indicate that no further
|
||||
// changes will occur to the locked state.
|
||||
enum state {
|
||||
STATE_LOCKED = 0x17, // 0b10111
|
||||
STATE_00010 = 0x02,
|
||||
STATE_00100 = 0x04,
|
||||
STATE_01011 = 0x0b,
|
||||
STATE_PRELOCK1 = 0x10, // 0b10000
|
||||
STATE_PRELOCK2 = 0x1e, // 0b11110
|
||||
STATE_00000 = 0x00,
|
||||
STATE_00001 = 0x01, // mixer bypassed
|
||||
STATE_00011 = 0x03,
|
||||
STATE_00101 = 0x05,
|
||||
STATE_00110 = 0x06,
|
||||
STATE_00111 = 0x07,
|
||||
STATE_01010 = 0x0a,
|
||||
STATE_10110 = 0x16,
|
||||
STATE_11110 = 0x1e, // ?
|
||||
STATE_11111 = 0x1f,
|
||||
STATE_NONE = 0xff,
|
||||
};
|
||||
|
||||
// Select which fields will be made available in the readback
|
||||
// register - we only need to do this the first time.
|
||||
if (*prelock_state == STATE_NONE) {
|
||||
set_RFFC5071_READSEL(drv, 0b0011);
|
||||
rffc5071_regs_commit(drv);
|
||||
}
|
||||
|
||||
// read fsm state
|
||||
uint16_t rb = rffc5071_reg_read(drv, RFFC5071_READBACK_REG);
|
||||
uint8_t rsm_state = (rb >> 11) & 0b11111;
|
||||
|
||||
// get gpo4 lock detect signal
|
||||
bool gpo4_ld = gpio_read(drv->gpio_ld);
|
||||
|
||||
// parse state
|
||||
switch (rsm_state) {
|
||||
case STATE_LOCKED: // 'normal operation'
|
||||
if (gpo4_ld &&
|
||||
((*prelock_state == STATE_PRELOCK1) ||
|
||||
(*prelock_state == STATE_PRELOCK2))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case STATE_00010:
|
||||
case STATE_00100:
|
||||
case STATE_01011:
|
||||
break;
|
||||
case STATE_PRELOCK1:
|
||||
*prelock_state = rsm_state;
|
||||
break;
|
||||
case STATE_PRELOCK2:
|
||||
*prelock_state = rsm_state;
|
||||
break;
|
||||
// other states
|
||||
case STATE_00000:
|
||||
case STATE_00001:
|
||||
case STATE_00011:
|
||||
case STATE_00101:
|
||||
case STATE_00110:
|
||||
case STATE_00111:
|
||||
case STATE_01010:
|
||||
case STATE_10110:
|
||||
case STATE_11111:
|
||||
case STATE_NONE:
|
||||
break;
|
||||
default:
|
||||
// unknown state
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
typedef struct {
|
||||
spi_bus_t* const bus;
|
||||
gpio_t gpio_reset;
|
||||
#ifdef PRALINE
|
||||
gpio_t gpio_ld;
|
||||
#endif
|
||||
uint16_t regs[RFFC5071_NUM_REGS];
|
||||
uint32_t regs_dirty;
|
||||
} rffc5071_driver_t;
|
||||
@@ -67,5 +70,8 @@ extern void rffc5071_enable(rffc5071_driver_t* const drv);
|
||||
extern void rffc5071_disable(rffc5071_driver_t* const drv);
|
||||
|
||||
extern void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t);
|
||||
#ifdef PRALINE
|
||||
extern bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state);
|
||||
#endif
|
||||
|
||||
#endif // __RFFC5071_H
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#define RFFC5071_REG_SET_DIRTY(_d, _r) (_d->regs_dirty |= (1UL<<_r))
|
||||
|
||||
#define RFFC5071_READBACK_REG 31
|
||||
#define RFFC5071_DEV_ID_MASK 0xFFFC
|
||||
|
||||
/* Generate static inline accessors that operate on the global
|
||||
* regs. Done this way to (1) allow defs to be scraped out and used
|
||||
|
||||
@@ -65,8 +65,8 @@ static void rffc5071_spi_bus_init(spi_bus_t* const bus)
|
||||
{
|
||||
const rffc5071_spi_config_t* const config = bus->config;
|
||||
|
||||
scu_pinmux(SCU_MIXER_SCLK, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(SCU_MIXER_SDATA, SCU_GPIO_FAST);
|
||||
scu_pinmux(SCU_MIXER_SCLK, SCU_MIXER_SCLK_PINCFG);
|
||||
scu_pinmux(SCU_MIXER_SDATA, SCU_MIXER_SDATA_PINCFG);
|
||||
|
||||
gpio_output(config->gpio_clock);
|
||||
rffc5071_spi_direction_out(bus);
|
||||
|
||||
23
firmware/common/selftest.c
Normal file
23
firmware/common/selftest.c
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "selftest.h"
|
||||
selftest_t selftest;
|
||||
75
firmware/common/selftest.h
Normal file
75
firmware/common/selftest.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __SELFTEST_H
|
||||
#define __SELFTEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum {
|
||||
FAILED = 0,
|
||||
PASSED = 1,
|
||||
SKIPPED = 2,
|
||||
TIMEOUT = 3,
|
||||
};
|
||||
|
||||
typedef uint8_t test_result_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t mixer_id;
|
||||
#ifdef PRALINE
|
||||
uint16_t max2831_mux_rssi_1;
|
||||
uint16_t max2831_mux_temp;
|
||||
uint16_t max2831_mux_rssi_2;
|
||||
bool max2831_mux_test_ok;
|
||||
#else
|
||||
uint16_t max283x_readback_bad_value;
|
||||
uint16_t max283x_readback_expected_value;
|
||||
uint8_t max283x_readback_register_count;
|
||||
uint8_t max283x_readback_total_registers;
|
||||
#endif
|
||||
uint8_t si5351_rev_id;
|
||||
bool si5351_readback_ok;
|
||||
#ifdef PRALINE
|
||||
test_result_t fpga_image_load;
|
||||
test_result_t fpga_spi;
|
||||
test_result_t sgpio_rx;
|
||||
test_result_t xcvr_loopback;
|
||||
|
||||
struct xcvr_measurements {
|
||||
uint32_t zcs_i;
|
||||
uint32_t zcs_q;
|
||||
uint8_t max_mag_i;
|
||||
uint8_t max_mag_q;
|
||||
uint32_t avg_mag_sq_i;
|
||||
uint32_t avg_mag_sq_q;
|
||||
} xcvr_measurements[4];
|
||||
#endif
|
||||
struct {
|
||||
bool pass;
|
||||
char msg[511];
|
||||
} report;
|
||||
} selftest_t;
|
||||
|
||||
extern selftest_t selftest;
|
||||
|
||||
#endif // __SELFTEST_H
|
||||
@@ -34,37 +34,39 @@ static void update_q_invert(sgpio_config_t* const config);
|
||||
|
||||
void sgpio_configure_pin_functions(sgpio_config_t* const config)
|
||||
{
|
||||
scu_pinmux(SCU_PINMUX_SGPIO0, SCU_GPIO_FAST | SCU_CONF_FUNCTION3);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO1, SCU_GPIO_FAST | SCU_CONF_FUNCTION3);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO2, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO3, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO4, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO5, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO6, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO7, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO8, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO9, SCU_GPIO_FAST | SCU_CONF_FUNCTION7);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO10, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO11, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO12, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); /* GPIO0[13] */
|
||||
scu_pinmux(SCU_PINMUX_SGPIO14, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[13] */
|
||||
scu_pinmux(SCU_PINMUX_SGPIO15, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[14] */
|
||||
scu_pinmux(SCU_PINMUX_SGPIO0, SCU_PINMUX_SGPIO0_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO1, SCU_PINMUX_SGPIO1_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO2, SCU_PINMUX_SGPIO2_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO3, SCU_PINMUX_SGPIO3_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO4, SCU_PINMUX_SGPIO4_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO5, SCU_PINMUX_SGPIO5_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO6, SCU_PINMUX_SGPIO6_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO7, SCU_PINMUX_SGPIO7_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO8, SCU_PINMUX_SGPIO8_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO9, SCU_PINMUX_SGPIO9_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO10, SCU_PINMUX_SGPIO10_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO11, SCU_PINMUX_SGPIO11_PINCFG);
|
||||
scu_pinmux(SCU_PINMUX_SGPIO12, SCU_PINMUX_SGPIO12_PINCFG); /* GPIO0[13] */
|
||||
scu_pinmux(SCU_PINMUX_SGPIO14, SCU_PINMUX_SGPIO14_PINCFG); /* GPIO5[13] */
|
||||
scu_pinmux(SCU_PINMUX_SGPIO15, SCU_PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */
|
||||
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
scu_pinmux(
|
||||
SCU_H1R9_HW_SYNC_EN,
|
||||
SCU_H1R9_TRIGGER_EN,
|
||||
SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */
|
||||
} else {
|
||||
scu_pinmux(
|
||||
SCU_HW_SYNC_EN,
|
||||
SCU_TRIGGER_EN,
|
||||
SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[12] */
|
||||
}
|
||||
|
||||
sgpio_cpld_set_mixer_invert(config, 0);
|
||||
hw_sync_enable(0);
|
||||
|
||||
gpio_output(config->gpio_q_invert);
|
||||
gpio_output(config->gpio_hw_sync_enable);
|
||||
|
||||
#ifndef PRALINE
|
||||
trigger_enable(false);
|
||||
gpio_output(config->gpio_trigger_enable);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice)
|
||||
|
||||
@@ -37,7 +37,9 @@ typedef enum {
|
||||
|
||||
typedef struct sgpio_config_t {
|
||||
gpio_t gpio_q_invert;
|
||||
gpio_t gpio_hw_sync_enable;
|
||||
#ifndef PRALINE
|
||||
gpio_t gpio_trigger_enable;
|
||||
#endif
|
||||
bool slice_mode_multislice;
|
||||
} sgpio_config_t;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "platform_detect.h"
|
||||
#include "gpio_lpc.h"
|
||||
#include "hackrf_core.h"
|
||||
#include "selftest.h"
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
|
||||
/* HackRF One r9 clock control */
|
||||
@@ -199,7 +200,7 @@ void si5351c_configure_clock_control(
|
||||
pll = SI5351C_CLK_PLL_SRC_A;
|
||||
#endif
|
||||
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE)
|
||||
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
|
||||
if (source == PLL_SOURCE_CLKIN) {
|
||||
/* PLLB on CLKIN */
|
||||
pll = SI5351C_CLK_PLL_SRC_B;
|
||||
@@ -264,6 +265,15 @@ void si5351c_configure_clock_control(
|
||||
data[5] = SI5351C_CLK_POWERDOWN;
|
||||
data[6] = SI5351C_CLK_POWERDOWN;
|
||||
}
|
||||
#ifdef PRALINE
|
||||
data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(pll) |
|
||||
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
|
||||
SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA);
|
||||
data[3] = clkout_ctrl;
|
||||
data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(pll) |
|
||||
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
|
||||
SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV;
|
||||
#endif
|
||||
si5351c_write(drv, data, sizeof(data));
|
||||
}
|
||||
|
||||
@@ -274,11 +284,19 @@ void si5351c_configure_clock_control(
|
||||
void si5351c_enable_clock_outputs(si5351c_driver_t* const drv)
|
||||
{
|
||||
/* Enable CLK outputs 0, 1, 2, 4, 5 only. */
|
||||
/* Praline: enable 0, 4, 5 only. */
|
||||
/* 7: Clock to CPU is deactivated as it is not used and creates noise */
|
||||
/* 3: External clock output is deactivated by default */
|
||||
|
||||
#ifndef PRALINE
|
||||
uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) |
|
||||
SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) |
|
||||
SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7);
|
||||
#else
|
||||
uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) |
|
||||
SI5351C_CLK_DISABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) |
|
||||
SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7);
|
||||
#endif
|
||||
uint8_t clkout = 3;
|
||||
|
||||
/* HackRF One r9 has only three clock generator outputs. */
|
||||
@@ -356,8 +374,8 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable)
|
||||
{
|
||||
clkout_enabled = (enable > 0);
|
||||
|
||||
//FIXME this should be somewhere else
|
||||
uint8_t clkout = 3;
|
||||
|
||||
/* HackRF One r9 has only three clock generator outputs. */
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
clkout = 2;
|
||||
@@ -371,6 +389,26 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable)
|
||||
|
||||
void si5351c_init(si5351c_driver_t* const drv)
|
||||
{
|
||||
/* Read revision ID */
|
||||
selftest.si5351_rev_id = si5351c_read_single(drv, 0) & SI5351C_REVID;
|
||||
|
||||
/* Read back interrupt status mask register, flip the mask bits and verify. */
|
||||
uint8_t int_mask = si5351c_read_single(drv, 2);
|
||||
int_mask ^= 0xF8;
|
||||
si5351c_write_single(drv, 2, int_mask);
|
||||
selftest.si5351_readback_ok = (si5351c_read_single(drv, 2) == int_mask);
|
||||
if (!selftest.si5351_readback_ok) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
/* Do the same with them flipped back. */
|
||||
int_mask ^= 0xF8;
|
||||
si5351c_write_single(drv, 2, int_mask);
|
||||
selftest.si5351_readback_ok &= (si5351c_read_single(drv, 2) == int_mask);
|
||||
if (!selftest.si5351_readback_ok) {
|
||||
selftest.report.pass = false;
|
||||
}
|
||||
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
/* CLKIN_EN */
|
||||
scu_pinmux(SCU_H1R9_CLKIN_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
|
||||
|
||||
@@ -56,7 +56,8 @@ extern "C" {
|
||||
#define SI5351C_CLK_IDRV_6MA 2
|
||||
#define SI5351C_CLK_IDRV_8MA 3
|
||||
|
||||
#define SI5351C_LOS (1 << 4)
|
||||
#define SI5351C_LOS (1 << 4)
|
||||
#define SI5351C_REVID 0x03
|
||||
|
||||
enum pll_sources {
|
||||
PLL_SOURCE_UNINITIALIZED = -1,
|
||||
|
||||
@@ -39,7 +39,7 @@ void spi_ssp_start(spi_bus_t* const bus, const void* const _config)
|
||||
|
||||
SSP_CR1(bus->obj) = 0;
|
||||
SSP_CPSR(bus->obj) = config->clock_prescale_rate;
|
||||
SSP_CR0(bus->obj) = (config->serial_clock_rate << 8) | SSP_CPOL_0_CPHA_0 |
|
||||
SSP_CR0(bus->obj) = (config->serial_clock_rate << 8) | config->spi_mode |
|
||||
SSP_FRAME_SPI | config->data_bits;
|
||||
SSP_CR1(bus->obj) =
|
||||
SSP_SLAVE_OUT_ENABLE | SSP_MASTER | SSP_ENABLE | SSP_MODE_NORMAL;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
typedef struct ssp_config_t {
|
||||
ssp_datasize_t data_bits;
|
||||
ssp_cpol_cpha_t spi_mode;
|
||||
uint8_t serial_clock_rate;
|
||||
uint8_t clock_prescale_rate;
|
||||
gpio_t gpio_select;
|
||||
|
||||
395
firmware/common/tune_config.h
Normal file
395
firmware/common/tune_config.h
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright 2024-2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
*
|
||||
* This file is part of HackRF.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __TUNE_CONFIG_H__
|
||||
#define __TUNE_CONFIG_H__
|
||||
|
||||
#ifdef PRALINE
|
||||
#include "fpga.h"
|
||||
|
||||
typedef struct {
|
||||
uint16_t rf_range_end_mhz;
|
||||
uint16_t if_mhz;
|
||||
bool high_lo;
|
||||
fpga_quarter_shift_mode_t shift;
|
||||
} tune_config_t;
|
||||
|
||||
// clang-format off
|
||||
/* tuning table optimized for TX */
|
||||
static const tune_config_t praline_tune_config_tx[] = {
|
||||
{ 2100, 2375, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2105, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2115, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2130, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2150, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2160, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2175, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2190, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2195, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2210, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2248, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2265, 2525, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2300, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2320, 2525, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2580, 0, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3000, 2325, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3140, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3200, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3280, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3340, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3420, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3480, 2525, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3500, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3595, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3625, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3670, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3710, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3760, 2525, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3790, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3860, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3915, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4000, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4055, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4125, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4700, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4800, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5000, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5260, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5465, 2525, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5560, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5720, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5860, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5970, 2575, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6000, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6500, 2325, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6750, 2375, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6850, 2425, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6950, 2475, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 7000, 2525, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 7251, 2575, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 0, 0, false, 0 },
|
||||
};
|
||||
|
||||
/* tuning table optimized for 20 Msps interleaved RX sweep mode */
|
||||
static const tune_config_t praline_tune_config_rx_sweep[] = {
|
||||
{ 140, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 424, 2570, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 557, 2520, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 593, 2380, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 776, 2360, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 846, 2570, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 926, 2500, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 1055, 2380, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 1175, 2360, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 1391, 2340, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 1529, 2570, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 1671, 2520, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 1979, 2380, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2150, 2330, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2160, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2170, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2179, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2184, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2187, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2194, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2203, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2212, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2222, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2231, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2233, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2237, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2241, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2245, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2250, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2252, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2258, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2261, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2266, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2271, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2275, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2280, 2500, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2284, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2285, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2289, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2293, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2294, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2298, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2301, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2302, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2307, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2308, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2312, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2316, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2317, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2320, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2580, 0, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2585, 2360, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2588, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2594, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2606, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2617, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2627, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2638, 2360, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2649, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2659, 2380, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2664, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2675, 2360, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2686, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2697, 2380, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2705, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2716, 2360, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2728, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2739, 2380, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2757, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2779, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2790, 2360, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2801, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2812, 2380, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2821, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2831, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2852, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2874, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2897, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2913, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2925, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2937, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2948, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2960, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2975, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 2988, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3002, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3014, 2360, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3027, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3041, 2500, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3052, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3064, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3082, 2500, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3107, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3132, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3157, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3170, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3192, 2500, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3216, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3270, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3319, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3341, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3370, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3400, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3430, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3464, 2520, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3491, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3519, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3553, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3595, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3638, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3665, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3685, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3726, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3790, 2370, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 3910, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4014, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4123, 2380, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4191, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4349, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4452, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4579, 2500, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4707, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4831, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4851, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4871, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4891, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4911, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4931, 2550, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 4951, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5044, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5065, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5174, 2330, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5285, 2380, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5449, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5574, 2510, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5717, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 5892, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6096, 2350, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6254, 2560, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6625, 2340, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6764, 2540, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 6930, 2530, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 7251, 2570, false, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 0, 0, false, 0 },
|
||||
};
|
||||
|
||||
/* tuning table optimized for RX */
|
||||
static const tune_config_t praline_tune_config_rx[] = {
|
||||
{ 0, 2360, true, FPGA_QUARTER_SHIFT_MODE_NONE },
|
||||
{ 50, 2320, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 100, 2320, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 140, 2320, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 406, 2560, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 511, 2380, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 578, 2560, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 741, 2340, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 861, 2560, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 921, 2560, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 1049, 2340, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 1169, 2380, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 1360, 2340, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 1544, 2560, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 1675, 2560, true, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 1992, 2380, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2070, 2340, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2150, 2360, true, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2168, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2185, 2580, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2202, 2580, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2205, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2216, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2223, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2234, 2580, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2240, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2251, 2580, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2258, 2580, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2265, 2540, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2271, 2580, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2273, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2275, 2580, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2280, 2500, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2284, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2289, 2580, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2293, 2540, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2298, 2520, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2300, 2580, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2302, 2540, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2309, 2560, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2311, 2580, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2314, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2315, 2540, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2320, 2580, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2380, 0, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2440, 0, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2500, 0, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2580, 0, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2583, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2584, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2587, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2593, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2607, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2609, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2615, 2360, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2627, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2629, 2360, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2631, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2644, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2649, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2651, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2654, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2665, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2669, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2672, 2360, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2682, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2687, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2692, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2695, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2705, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2707, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2712, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2717, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2728, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2730, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2734, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2758, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2780, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2787, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2802, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2809, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2822, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2831, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2854, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2875, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2898, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2918, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2936, 2520, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2944, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 2959, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2976, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 2985, 2500, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3003, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3009, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3027, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3034, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3050, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3069, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3094, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3119, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3144, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3169, 2560, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3180, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3204, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3232, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3292, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3340, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3369, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3399, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3429, 2380, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3464, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3489, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3512, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3551, 2500, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3582, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3611, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3639, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3729, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 3817, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 3942, 2360, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 4049, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4134, 2500, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 4194, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4353, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4449, 2360, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 4562, 2500, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4672, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4769, 2540, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4849, 2560, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 4889, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4929, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 4969, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 5009, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 5049, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 5092, 2360, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 5209, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 5298, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 5468, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 5582, 2520, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 5702, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 5888, 2520, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 6092, 2340, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 6240, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 6609, 2340, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 6752, 2380, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 6930, 2520, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 7000, 2560, false, FPGA_QUARTER_SHIFT_MODE_UP },
|
||||
{ 7070, 2560, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 7251, 2580, false, FPGA_QUARTER_SHIFT_MODE_DOWN },
|
||||
{ 0, 0, false, 0 },
|
||||
};
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
#endif /*__TUNE_CONFIG_H__*/
|
||||
@@ -25,34 +25,52 @@
|
||||
#include "hackrf_ui.h"
|
||||
#include "hackrf_core.h"
|
||||
#include "mixer.h"
|
||||
#include "max2831.h"
|
||||
#include "max2837.h"
|
||||
#include "max2839.h"
|
||||
#include "sgpio.h"
|
||||
#include "operacake.h"
|
||||
#include "platform_detect.h"
|
||||
|
||||
#define FREQ_ONE_MHZ (1000ULL * 1000)
|
||||
#ifndef PRALINE
|
||||
|
||||
#define MIN_LP_FREQ_MHZ (0)
|
||||
#define MAX_LP_FREQ_MHZ (2170ULL)
|
||||
#define MIN_LP_FREQ_MHZ (0)
|
||||
#define MAX_LP_FREQ_MHZ (2170ULL)
|
||||
|
||||
#define ABS_MIN_BYPASS_FREQ_MHZ (2000ULL)
|
||||
#define MIN_BYPASS_FREQ_MHZ (MAX_LP_FREQ_MHZ)
|
||||
#define MAX_BYPASS_FREQ_MHZ (2740ULL)
|
||||
#define ABS_MAX_BYPASS_FREQ_MHZ (3000ULL)
|
||||
#define ABS_MIN_BYPASS_FREQ_MHZ (2000ULL)
|
||||
#define MIN_BYPASS_FREQ_MHZ (MAX_LP_FREQ_MHZ)
|
||||
#define MAX_BYPASS_FREQ_MHZ (2740ULL)
|
||||
#define ABS_MAX_BYPASS_FREQ_MHZ (3000ULL)
|
||||
|
||||
#define MIN_HP_FREQ_MHZ (MAX_BYPASS_FREQ_MHZ)
|
||||
#define MID1_HP_FREQ_MHZ (3600ULL)
|
||||
#define MID2_HP_FREQ_MHZ (5100ULL)
|
||||
#define MAX_HP_FREQ_MHZ (7250ULL)
|
||||
#define MIN_HP_FREQ_MHZ (MAX_BYPASS_FREQ_MHZ)
|
||||
#define MID1_HP_FREQ_MHZ (3600ULL)
|
||||
#define MID2_HP_FREQ_MHZ (5100ULL)
|
||||
#define MAX_HP_FREQ_MHZ (7250ULL)
|
||||
|
||||
#define MIN_LO_FREQ_HZ (84375000ULL)
|
||||
#define MAX_LO_FREQ_HZ (5400000000ULL)
|
||||
#define MIN_LO_FREQ_HZ (84375000ULL)
|
||||
#define MAX_LO_FREQ_HZ (5400000000ULL)
|
||||
|
||||
#else
|
||||
|
||||
#define MIN_LP_FREQ_MHZ (0)
|
||||
#define MAX_LP_FREQ_MHZ (2320ULL)
|
||||
|
||||
#define ABS_MIN_BYPASS_FREQ_MHZ (2000ULL)
|
||||
#define MIN_BYPASS_FREQ_MHZ (MAX_LP_FREQ_MHZ)
|
||||
#define MAX_BYPASS_FREQ_MHZ (2580ULL)
|
||||
#define ABS_MAX_BYPASS_FREQ_MHZ (3000ULL)
|
||||
|
||||
#define MIN_HP_FREQ_MHZ (MAX_BYPASS_FREQ_MHZ)
|
||||
#define MAX_HP_FREQ_MHZ (7250ULL)
|
||||
|
||||
#define MIN_LO_FREQ_HZ (84375000ULL)
|
||||
#define MAX_LO_FREQ_HZ (5400000000ULL)
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef PRALINE
|
||||
static uint32_t max2837_freq_nominal_hz = 2560000000;
|
||||
|
||||
uint64_t freq_cache = 100000000;
|
||||
|
||||
/*
|
||||
* Set freq/tuning between 0MHz to 7250 MHz (less than 16bits really used)
|
||||
* hz between 0 to 999999 Hz (not checked)
|
||||
@@ -72,12 +90,12 @@ bool set_freq(const uint64_t freq)
|
||||
max283x_set_mode(&max283x, MAX283x_MODE_STANDBY);
|
||||
if (freq_mhz < MAX_LP_FREQ_MHZ) {
|
||||
rf_path_set_filter(&rf_path, RF_PATH_FILTER_LOW_PASS);
|
||||
#ifdef RAD1O
|
||||
#ifdef RAD1O
|
||||
max2837_freq_nominal_hz = 2300 * FREQ_ONE_MHZ;
|
||||
#else
|
||||
#else
|
||||
/* IF is graduated from 2650 MHz to 2340 MHz */
|
||||
max2837_freq_nominal_hz = (2650 * FREQ_ONE_MHZ) - (freq / 7);
|
||||
#endif
|
||||
#endif
|
||||
mixer_freq_hz = max2837_freq_nominal_hz + freq;
|
||||
/* Set Freq and read real freq */
|
||||
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
|
||||
@@ -115,15 +133,76 @@ bool set_freq(const uint64_t freq)
|
||||
}
|
||||
max283x_set_mode(&max283x, prior_max283x_mode);
|
||||
if (success) {
|
||||
freq_cache = freq;
|
||||
hackrf_ui()->set_frequency(freq);
|
||||
#ifdef HACKRF_ONE
|
||||
#ifdef HACKRF_ONE
|
||||
operacake_set_range(freq_mhz);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool tuning_set_frequency(
|
||||
const tune_config_t* cfg,
|
||||
const uint64_t freq,
|
||||
const uint32_t offset)
|
||||
{
|
||||
uint64_t mixer_freq_hz;
|
||||
uint64_t real_mixer_freq_hz;
|
||||
|
||||
if (freq > (MAX_HP_FREQ_MHZ * FREQ_ONE_MHZ)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t freq_mhz = freq / FREQ_ONE_MHZ;
|
||||
|
||||
uint64_t rf = freq;
|
||||
if (cfg->shift == FPGA_QUARTER_SHIFT_MODE_DOWN) {
|
||||
if (offset > rf) {
|
||||
rf = offset - rf;
|
||||
} else {
|
||||
rf = rf - offset;
|
||||
}
|
||||
} else if (cfg->shift == FPGA_QUARTER_SHIFT_MODE_UP) {
|
||||
rf = rf + offset;
|
||||
}
|
||||
|
||||
max2831_mode_t prior_max2831_mode = max2831_mode(&max283x);
|
||||
max2831_set_mode(&max283x, MAX2831_MODE_STANDBY);
|
||||
|
||||
if (cfg->if_mhz == 0) {
|
||||
rf_path_set_filter(&rf_path, RF_PATH_FILTER_BYPASS);
|
||||
max2831_set_frequency(&max283x, rf);
|
||||
sgpio_cpld_set_mixer_invert(&sgpio_config, 0);
|
||||
} else if (cfg->if_mhz > freq_mhz) {
|
||||
rf_path_set_filter(&rf_path, RF_PATH_FILTER_LOW_PASS);
|
||||
if (cfg->high_lo) {
|
||||
mixer_freq_hz = FREQ_ONE_MHZ * cfg->if_mhz + rf;
|
||||
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
|
||||
max2831_set_frequency(&max283x, real_mixer_freq_hz - rf);
|
||||
sgpio_cpld_set_mixer_invert(&sgpio_config, 1);
|
||||
} else {
|
||||
mixer_freq_hz = FREQ_ONE_MHZ * cfg->if_mhz - rf;
|
||||
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
|
||||
max2831_set_frequency(&max283x, real_mixer_freq_hz + rf);
|
||||
sgpio_cpld_set_mixer_invert(&sgpio_config, 0);
|
||||
}
|
||||
} else {
|
||||
rf_path_set_filter(&rf_path, RF_PATH_FILTER_HIGH_PASS);
|
||||
mixer_freq_hz = rf - FREQ_ONE_MHZ * cfg->if_mhz;
|
||||
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
|
||||
max2831_set_frequency(&max283x, rf - real_mixer_freq_hz);
|
||||
sgpio_cpld_set_mixer_invert(&sgpio_config, 0);
|
||||
}
|
||||
|
||||
max2831_set_mode(&max283x, prior_max2831_mode);
|
||||
hackrf_ui()->set_frequency(freq);
|
||||
operacake_set_range(freq_mhz);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool set_freq_explicit(
|
||||
const uint64_t if_freq_hz,
|
||||
const uint64_t lo_freq_hz,
|
||||
@@ -144,7 +223,11 @@ bool set_freq_explicit(
|
||||
}
|
||||
|
||||
rf_path_set_filter(&rf_path, path);
|
||||
#ifdef PRALINE
|
||||
max2831_set_frequency(&max283x, if_freq_hz);
|
||||
#else
|
||||
max283x_set_frequency(&max283x, if_freq_hz);
|
||||
#endif
|
||||
if (lo_freq_hz > if_freq_hz) {
|
||||
sgpio_cpld_set_mixer_invert(&sgpio_config, 1);
|
||||
} else {
|
||||
|
||||
@@ -25,14 +25,24 @@
|
||||
#define __TUNING_H__
|
||||
|
||||
#include "rf_path.h"
|
||||
#include "tune_config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define FREQ_ONE_MHZ (1000ULL * 1000)
|
||||
|
||||
bool set_freq(const uint64_t freq);
|
||||
bool set_freq_explicit(
|
||||
const uint64_t if_freq_hz,
|
||||
const uint64_t lo_freq_hz,
|
||||
const rf_path_filter_t path);
|
||||
|
||||
#ifdef PRALINE
|
||||
bool tuning_set_frequency(
|
||||
const tune_config_t* cfg,
|
||||
const uint64_t freq,
|
||||
const uint32_t offset);
|
||||
#endif
|
||||
|
||||
#endif /*__TUNING_H__*/
|
||||
|
||||
@@ -520,7 +520,7 @@ static void copy_setup(usb_setup_t* const dst, const volatile uint8_t* const src
|
||||
dst->length_h = src[7];
|
||||
}
|
||||
|
||||
void usb_endpoint_init(const usb_endpoint_t* const endpoint)
|
||||
void usb_endpoint_init(const usb_endpoint_t* const endpoint, const bool enable_zlp)
|
||||
{
|
||||
usb_endpoint_flush(endpoint);
|
||||
|
||||
@@ -537,10 +537,15 @@ void usb_endpoint_init(const usb_endpoint_t* const endpoint)
|
||||
// TODO: There are more capabilities to adjust based on the endpoint
|
||||
// descriptor.
|
||||
usb_queue_head_t* const qh = usb_queue_head(endpoint->address);
|
||||
qh->capabilities = USB_QH_CAPABILITIES_MULT(0) | USB_QH_CAPABILITIES_ZLT |
|
||||
qh->capabilities = USB_QH_CAPABILITIES_MULT(0) |
|
||||
USB_QH_CAPABILITIES_MPL(max_packet_size) |
|
||||
((transfer_type == USB_TRANSFER_TYPE_CONTROL) ? USB_QH_CAPABILITIES_IOS :
|
||||
0);
|
||||
if (enable_zlp) {
|
||||
qh->capabilities &= ~USB_QH_CAPABILITIES_ZLT;
|
||||
} else {
|
||||
qh->capabilities |= USB_QH_CAPABILITIES_ZLT;
|
||||
}
|
||||
qh->current_dtd_pointer = 0;
|
||||
qh->next_dtd_pointer = USB_TD_NEXT_DTD_POINTER_TERMINATE;
|
||||
qh->total_bytes = USB_TD_DTD_TOKEN_TOTAL_BYTES(0) | USB_TD_DTD_TOKEN_MULTO(0);
|
||||
|
||||
@@ -50,7 +50,7 @@ void usb_set_address_deferred(
|
||||
|
||||
usb_endpoint_t* usb_endpoint_from_address(const uint_fast8_t endpoint_address);
|
||||
|
||||
void usb_endpoint_init(const usb_endpoint_t* const endpoint);
|
||||
void usb_endpoint_init(const usb_endpoint_t* const endpoint, const bool enable_zlp);
|
||||
|
||||
void usb_endpoint_stall(const usb_endpoint_t* const endpoint);
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ void w25q80bv_setup(w25q80bv_driver_t* const drv)
|
||||
do {
|
||||
device_id = w25q80bv_get_device_id(drv);
|
||||
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
|
||||
device_id != W25Q16DV_DEVICE_ID_RES);
|
||||
device_id != W25Q16DV_DEVICE_ID_RES &&
|
||||
device_id != W25Q32JV_DEVICE_ID_RES);
|
||||
}
|
||||
|
||||
uint8_t w25q80bv_get_status(w25q80bv_driver_t* const drv)
|
||||
@@ -129,7 +130,8 @@ void w25q80bv_chip_erase(w25q80bv_driver_t* const drv)
|
||||
do {
|
||||
device_id = w25q80bv_get_device_id(drv);
|
||||
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
|
||||
device_id != W25Q16DV_DEVICE_ID_RES);
|
||||
device_id != W25Q16DV_DEVICE_ID_RES &&
|
||||
device_id != W25Q32JV_DEVICE_ID_RES);
|
||||
|
||||
w25q80bv_wait_while_busy(drv);
|
||||
w25q80bv_write_enable(drv);
|
||||
@@ -182,7 +184,8 @@ void w25q80bv_program(
|
||||
do {
|
||||
device_id = w25q80bv_get_device_id(drv);
|
||||
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
|
||||
device_id != W25Q16DV_DEVICE_ID_RES);
|
||||
device_id != W25Q16DV_DEVICE_ID_RES &&
|
||||
device_id != W25Q32JV_DEVICE_ID_RES);
|
||||
|
||||
/* do nothing if we would overflow the flash */
|
||||
if ((len > drv->num_bytes) || (addr > drv->num_bytes) ||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#define W25Q80BV_DEVICE_ID_RES 0x13 /* Expected device_id for W25Q80BV */
|
||||
#define W25Q16DV_DEVICE_ID_RES 0x14 /* Expected device_id for W25Q16DV */
|
||||
#define W25Q32JV_DEVICE_ID_RES 0x15 /* Expected device_id for W25Q32JV */
|
||||
#include "spi_bus.h"
|
||||
#include "gpio.h"
|
||||
|
||||
|
||||
357
firmware/fpga/amaranth_future/fixed.py
Normal file
357
firmware/fpga/amaranth_future/fixed.py
Normal file
@@ -0,0 +1,357 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# Copyright (c) 2024 S. Holzapfel <me@sebholzapfel.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# from https://github.com/apfaudio/tiliqua/blob/main/gateware/src/amaranth_future/fixed.py
|
||||
# which is also derived from https://github.com/amaranth-lang/amaranth/pull/1005
|
||||
# slightly modified
|
||||
|
||||
from amaranth import hdl
|
||||
from amaranth.utils import bits_for
|
||||
|
||||
from dsp.round import convergent_round
|
||||
|
||||
__all__ = ["Shape", "SQ", "UQ", "Value", "Const"]
|
||||
|
||||
class Shape(hdl.ShapeCastable):
|
||||
def __init__(self, i_or_f_width, f_width = None, /, *, signed):
|
||||
if f_width is None:
|
||||
self.i_width, self.f_width = 0, i_or_f_width
|
||||
else:
|
||||
self.i_width, self.f_width = i_or_f_width, f_width
|
||||
|
||||
self.signed = bool(signed)
|
||||
|
||||
@staticmethod
|
||||
def cast(shape, f_width=0):
|
||||
if not isinstance(shape, hdl.Shape):
|
||||
raise TypeError(f"Object {shape!r} cannot be converted to a fixed.Shape")
|
||||
|
||||
# i_width is what's left after subtracting f_width and sign bit, but can't be negative.
|
||||
i_width = max(0, shape.width - shape.signed - f_width)
|
||||
|
||||
return Shape(i_width, f_width, signed = shape.signed)
|
||||
|
||||
def as_shape(self):
|
||||
return hdl.Shape(self.signed + self.i_width + self.f_width, self.signed)
|
||||
|
||||
def __call__(self, target):
|
||||
return Value(self, target)
|
||||
|
||||
def const(self, value):
|
||||
if value is None:
|
||||
value = 0
|
||||
return Const(value, self)._target
|
||||
|
||||
def from_bits(self, raw):
|
||||
c = Const(0, self)
|
||||
c._value = raw
|
||||
return c
|
||||
|
||||
def max(self):
|
||||
c = Const(0, self)
|
||||
c._value = c._max_value()
|
||||
return c
|
||||
|
||||
def min(self):
|
||||
c = Const(0, self)
|
||||
c._value = c._min_value()
|
||||
return c
|
||||
|
||||
def __repr__(self):
|
||||
return f"fixed.Shape({self.i_width}, {self.f_width}, signed={self.signed})"
|
||||
|
||||
|
||||
class SQ(Shape):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args, signed = True)
|
||||
|
||||
|
||||
class UQ(Shape):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args, signed = False)
|
||||
|
||||
|
||||
class Value(hdl.ValueCastable):
|
||||
def __init__(self, shape, target):
|
||||
self._shape = shape
|
||||
self._target = target
|
||||
|
||||
@staticmethod
|
||||
def cast(value, f_width=0):
|
||||
return Shape.cast(value.shape(), f_width)(value)
|
||||
|
||||
def round(self, f_width=0):
|
||||
# If we're increasing precision, extend with more fractional bits.
|
||||
if f_width > self.f_width:
|
||||
return Shape(self.i_width, f_width, signed = self.signed)(hdl.Cat(hdl.Const(0, f_width - self.f_width), self.raw()))
|
||||
|
||||
# If we're reducing precision, perform convergent rounding (round to even).
|
||||
elif f_width < self.f_width:
|
||||
return Shape(self.i_width, f_width, signed = self.signed)( convergent_round(self.raw(), self.f_width - f_width) )
|
||||
|
||||
return self
|
||||
|
||||
def truncate(self, f_width=0):
|
||||
return Shape(self.i_width, f_width, signed = self.signed)(self.raw()[self.f_width - f_width:])
|
||||
|
||||
@property
|
||||
def i_width(self):
|
||||
return self._shape.i_width
|
||||
|
||||
@property
|
||||
def f_width(self):
|
||||
return self._shape.f_width
|
||||
|
||||
@property
|
||||
def signed(self):
|
||||
return self._shape.signed
|
||||
|
||||
def as_value(self):
|
||||
return self._target
|
||||
|
||||
def raw(self):
|
||||
"""
|
||||
Adding an `s ( )` signedness wrapper in `as_value` when needed
|
||||
breaks lib.wiring for some reason. In the future, raw() and
|
||||
`as_value()` should be combined.
|
||||
"""
|
||||
if self.signed:
|
||||
return self._target.as_signed()
|
||||
return self._target
|
||||
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def eq(self, other):
|
||||
# Regular values are assigned directly to the underlying value.
|
||||
if isinstance(other, hdl.Value):
|
||||
return self.raw().eq(other)
|
||||
|
||||
# int and float are cast to fixed.Const.
|
||||
elif isinstance(other, int) or isinstance(other, float):
|
||||
other = Const(other, self.shape())
|
||||
|
||||
# Other value types are unsupported.
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
|
||||
# Match precision.
|
||||
other = other.round(self.f_width)
|
||||
|
||||
return self.raw().eq(other.raw())
|
||||
|
||||
def __mul__(self, other):
|
||||
# Regular values are cast to fixed.Value
|
||||
if isinstance(other, hdl.Value):
|
||||
other = Value.cast(other)
|
||||
|
||||
# int are cast to fixed.Const
|
||||
elif isinstance(other, int):
|
||||
other = Const(other)
|
||||
|
||||
# Other value types are unsupported.
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
|
||||
return Value.cast(self.raw() * other.raw(), self.f_width + other.f_width)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self.__mul__(other)
|
||||
|
||||
def __add__(self, other):
|
||||
# Regular values are cast to fixed.Value
|
||||
if isinstance(other, hdl.Value):
|
||||
other = Value.cast(other)
|
||||
|
||||
# int are cast to fixed.Const
|
||||
elif isinstance(other, int):
|
||||
other = Const(other)
|
||||
|
||||
# Other value types are unsupported.
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
|
||||
f_width = max(self.f_width, other.f_width)
|
||||
|
||||
return Value.cast(self.round(f_width).raw() + other.round(f_width).raw(), f_width)
|
||||
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
# Regular values are cast to fixed.Value
|
||||
if isinstance(other, hdl.Value):
|
||||
other = Value.cast(other)
|
||||
|
||||
# int are cast to fixed.Const
|
||||
elif isinstance(other, int):
|
||||
other = Const(other)
|
||||
|
||||
# Other value types are unsupported.
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
|
||||
f_width = max(self.f_width, other.f_width)
|
||||
|
||||
return Value.cast(self.round(f_width).raw() - other.round(f_width).raw(), f_width)
|
||||
|
||||
def __rsub__(self, other):
|
||||
return -self.__sub__(other)
|
||||
|
||||
def __pos__(self):
|
||||
return self
|
||||
|
||||
def __neg__(self):
|
||||
return Value.cast(-self.raw(), self.f_width)
|
||||
|
||||
def __abs__(self):
|
||||
return Value.cast(abs(self.raw()), self.f_width)
|
||||
|
||||
def __lshift__(self, other):
|
||||
if isinstance(other, int):
|
||||
if other < 0:
|
||||
raise ValueError("Shift amount cannot be negative")
|
||||
|
||||
if other > self.f_width:
|
||||
return Value.cast(hdl.Cat(hdl.Const(0, other - self.f_width), self.raw()))
|
||||
else:
|
||||
return Value.cast(self.raw(), self.f_width - other)
|
||||
|
||||
elif not isinstance(other, hdl.Value):
|
||||
raise TypeError("Shift amount must be an integer value")
|
||||
|
||||
if other.signed:
|
||||
raise TypeError("Shift amount must be unsigned")
|
||||
|
||||
return Value.cast(self.raw() << other, self.f_width)
|
||||
|
||||
def __rshift__(self, other):
|
||||
if isinstance(other, int):
|
||||
if other < 0:
|
||||
raise ValueError("Shift amount cannot be negative")
|
||||
|
||||
return Value.cast(self.raw(), self.f_width + other)
|
||||
|
||||
elif not isinstance(other, hdl.Value):
|
||||
raise TypeError("Shift amount must be an integer value")
|
||||
|
||||
if other.signed:
|
||||
raise TypeError("Shift amount must be unsigned")
|
||||
|
||||
# Extend f_width by maximal shift amount.
|
||||
f_width = self.f_width + 2**other.width - 1
|
||||
|
||||
return Value.cast(self.round(f_width).raw() >> other, f_width)
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, hdl.Value):
|
||||
other = Value.cast(other)
|
||||
elif isinstance(other, int):
|
||||
other = Const(other)
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
f_width = max(self.f_width, other.f_width)
|
||||
return self.round(f_width).raw() < other.round(f_width).raw()
|
||||
|
||||
def __ge__(self, other):
|
||||
return ~self.__lt__(other)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, hdl.Value):
|
||||
other = Value.cast(other)
|
||||
elif isinstance(other, int):
|
||||
other = Const(other)
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
f_width = max(self.f_width, other.f_width)
|
||||
return self.round(f_width).raw() == other.round(f_width).raw()
|
||||
|
||||
def __repr__(self):
|
||||
return f"(fixedpoint {'SQ' if self.signed else 'UQ'}{self.i_width}.{self.f_width} {self._target!r})"
|
||||
|
||||
|
||||
class Const(Value):
|
||||
def __init__(self, value, shape=None):
|
||||
|
||||
if isinstance(value, float) or isinstance(value, int):
|
||||
num, den = value.as_integer_ratio()
|
||||
elif isinstance(value, Const):
|
||||
# FIXME: Memory inits seem to construct a fixed.Const with fixed.Const
|
||||
self._shape = value._shape
|
||||
self._value = value._value
|
||||
return
|
||||
else:
|
||||
raise TypeError(f"Object {value!r} cannot be converted to a fixed.Const")
|
||||
|
||||
# Determine smallest possible shape if not already selected.
|
||||
if shape is None:
|
||||
f_width = bits_for(den) - 1
|
||||
i_width = max(0, bits_for(abs(num)) - f_width)
|
||||
shape = Shape(i_width, f_width, signed = num < 0)
|
||||
|
||||
# Scale value to given precision.
|
||||
if 2**shape.f_width > den:
|
||||
num *= 2**shape.f_width // den
|
||||
elif 2**shape.f_width < den:
|
||||
num = round(num / (den // 2**shape.f_width))
|
||||
value = num
|
||||
|
||||
self._shape = shape
|
||||
|
||||
if value > self._max_value():
|
||||
print("WARN fixed.Const: clamp", value, "to", self._max_value())
|
||||
value = self._max_value()
|
||||
if value < self._min_value():
|
||||
print("WARN fixed.Const: clamp", value, "to", self._min_value())
|
||||
value = self._min_value()
|
||||
|
||||
self._value = value
|
||||
|
||||
def _max_value(self):
|
||||
return 2**(self._shape.i_width +
|
||||
self._shape.f_width) - 1
|
||||
|
||||
def _min_value(self):
|
||||
if self._shape.signed:
|
||||
return -1 * 2**(self._shape.i_width +
|
||||
self._shape.f_width)
|
||||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def _target(self):
|
||||
return hdl.Const(self._value, self._shape.as_shape())
|
||||
|
||||
def as_integer_ratio(self):
|
||||
return self._value, 2**self.f_width
|
||||
|
||||
def as_float(self):
|
||||
if self._value > self._max_value():
|
||||
v = self._min_value() + self._value - self._max_value() - 1
|
||||
else:
|
||||
v = self._value
|
||||
return v / 2**self.f_width
|
||||
|
||||
# TODO: Operators
|
||||
|
||||
def __mul__(self, other):
|
||||
# Regular values are cast to fixed.Value
|
||||
if isinstance(other, hdl.Value):
|
||||
other = Value.cast(other)
|
||||
|
||||
# int are cast to fixed.Const
|
||||
elif isinstance(other, int):
|
||||
other = Const(other)
|
||||
|
||||
# Other value types are unsupported.
|
||||
elif not isinstance(other, Value):
|
||||
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
|
||||
|
||||
return Value.cast(self.raw() * other.raw(), self.f_width + other.f_width)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self.__mul__(other)
|
||||
90
firmware/fpga/board.py
Normal file
90
firmware/fpga/board.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2024 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from amaranth import Elaboratable, Signal, Instance, Module, ClockDomain
|
||||
from amaranth.build import Resource, Pins, Clock, Attrs
|
||||
from amaranth.vendor import LatticeICE40Platform
|
||||
from amaranth_boards.resources import SPIResource
|
||||
|
||||
__all__ = ["PralinePlatform", "ClockDomainGenerator"]
|
||||
|
||||
|
||||
class PralinePlatform(LatticeICE40Platform):
|
||||
device = "iCE40UP5K"
|
||||
package = "SG48"
|
||||
|
||||
default_clk = "SB_HFOSC" # 48 MHz internal oscillator
|
||||
hfosc_div = 0 # Do not divide
|
||||
|
||||
resources = [
|
||||
Resource("fpga_clk", 0, Pins("39", dir="i"),
|
||||
Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")),
|
||||
|
||||
# ADC/DAC interfaces.
|
||||
Resource("afe_clk", 0, Pins("35", dir="i"),
|
||||
Clock(40e6), Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("dd", 0, Pins("38 37 36 34 32 31 28 27 26 25", dir="o"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("da", 0, Pins("46 45 44 43 42 41 40 39", dir="i"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
|
||||
# SGPIO interface.
|
||||
Resource("host_clk", 0, Pins("20", dir="o"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("host_data", 0, Pins("21 19 6 13 10 3 4 18", dir="io"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("q_invert", 0, Pins("9", dir="i"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("direction", 0, Pins("12", dir="i"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("disable", 0, Pins("23", dir="i"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("capture_en", 0, Pins("11", dir="o"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("trigger_in", 0, Pins("48", dir="i"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
Resource("trigger_out", 0, Pins("2", dir="o"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
|
||||
# SPI can be used after configuration.
|
||||
SPIResource("spi", 0, role="peripheral",
|
||||
cs_n="16", clk="15", copi="17", cipo="14",
|
||||
attrs=Attrs(IO_STANDARD="SB_LVCMOS"),
|
||||
),
|
||||
]
|
||||
|
||||
connectors = []
|
||||
|
||||
|
||||
class ClockDomainGenerator(Elaboratable):
|
||||
|
||||
@staticmethod
|
||||
def lut_delay(m, signal, *, depth):
|
||||
signal_out = signal
|
||||
for i in range(depth):
|
||||
signal_in = signal_out
|
||||
signal_out = Signal()
|
||||
m.submodules += Instance("SB_LUT4",
|
||||
p_LUT_INIT=0xAAAA, # Buffer configuration
|
||||
i_I0=signal_in,
|
||||
o_O=signal_out,
|
||||
)
|
||||
return signal_out
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Define clock domains.
|
||||
m.domains.gck1 = cd_gck1 = ClockDomain(name="gck1", reset_less=True) # analog front-end clock.
|
||||
|
||||
# We need to delay `gck1` clock by at least 8ns, not possible with the PLL alone.
|
||||
# Each LUT introduces a minimum propagation delay of 9ns (best case).
|
||||
delayed_gck1 = self.lut_delay(m, platform.request("afe_clk").i, depth=2)
|
||||
m.d.comb += cd_gck1.clk.eq(delayed_gck1)
|
||||
platform.add_clock_constraint(delayed_gck1, 40e6)
|
||||
|
||||
return m
|
||||
68
firmware/fpga/build.py
Normal file
68
firmware/fpga/build.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import lz4.block
|
||||
import struct
|
||||
import os
|
||||
|
||||
from board import PralinePlatform
|
||||
from top.standard import Top as standard
|
||||
from top.half_precision import Top as half_precision
|
||||
from top.ext_precision_rx import Top as ext_precision_rx
|
||||
from top.ext_precision_tx import Top as ext_precision_tx
|
||||
|
||||
|
||||
BLOCK_SIZE = 4096 # 4 KiB blocks
|
||||
OUTPUT_FILE = "praline_fpga.bin"
|
||||
|
||||
def compress_blockwise(input_stream, output_stream):
|
||||
# For every block...
|
||||
while block := f_in.read(BLOCK_SIZE):
|
||||
|
||||
# Compress the block using raw LZ4 block compression.
|
||||
compressed_block = lz4.block.compress(block, store_size=False,
|
||||
mode="high_compression", compression=12)
|
||||
|
||||
# Write the compressed block length and its contents to the output file.
|
||||
f_out.write(struct.pack("<H", len(compressed_block)))
|
||||
f_out.write(compressed_block)
|
||||
|
||||
# Write end marker (block of size 0)
|
||||
f_out.write(struct.pack("<H", 0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
fpga_images = {
|
||||
"0_standard": standard(),
|
||||
"1_halfprec": half_precision(),
|
||||
"2_extprec_rx": ext_precision_rx(),
|
||||
"3_extprec_tx": ext_precision_tx(),
|
||||
}
|
||||
|
||||
# Build bitstreams first.
|
||||
for name, image in fpga_images.items():
|
||||
PralinePlatform().build(image, name=name)
|
||||
|
||||
# Pack all the bitstreams.
|
||||
with open(f"build/{OUTPUT_FILE}", "wb") as f_out:
|
||||
f_out.write(struct.pack("<I", len(fpga_images))) # number of bitstreams
|
||||
f_out.seek(4 * len(fpga_images), os.SEEK_CUR) # reserve 4-byte slots for offsets
|
||||
offsets = []
|
||||
|
||||
# Write compressed bitstreams in our custom raw LZ4 block format.
|
||||
for name, image in fpga_images.items():
|
||||
img_path = f"build/{name}.bin"
|
||||
offsets.append(f_out.tell())
|
||||
with open(img_path, 'rb') as f_in:
|
||||
compress_blockwise(f_in, f_out)
|
||||
|
||||
# Write offsets table, right after the number of bitstreams.
|
||||
f_out.seek(4, os.SEEK_SET)
|
||||
for offset in offsets:
|
||||
f_out.write(struct.pack("<I", offset))
|
||||
|
||||
BIN
firmware/fpga/build/praline_fpga.bin
Normal file
BIN
firmware/fpga/build/praline_fpga.bin
Normal file
Binary file not shown.
0
firmware/fpga/dsp/__init__.py
Normal file
0
firmware/fpga/dsp/__init__.py
Normal file
743
firmware/fpga/dsp/cic.py
Normal file
743
firmware/fpga/dsp/cic.py
Normal file
@@ -0,0 +1,743 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from math import floor, log2, ceil, comb
|
||||
|
||||
from amaranth import Module, Signal, Const, signed, ResetInserter, DomainRenamer, C
|
||||
from amaranth.utils import bits_for
|
||||
|
||||
from amaranth.lib import wiring, stream, data
|
||||
from amaranth.lib.wiring import In, Out, connect
|
||||
|
||||
from dsp.round import convergent_round
|
||||
|
||||
|
||||
class CICInterpolator(wiring.Component):
|
||||
def __init__(self, M, stages, rates, width_in, width_out=None, num_channels=1, always_ready=False, domain="sync"):
|
||||
self.M = M
|
||||
self.stages = stages
|
||||
self.rates = rates
|
||||
self.width_in = width_in
|
||||
self.num_channels = num_channels
|
||||
if width_out is None:
|
||||
width_out = width_in + self.bit_growths()[-1]
|
||||
self.width_out = width_out
|
||||
self._domain = domain
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"factor": In(range(bits_for(max(rates)))),
|
||||
})
|
||||
|
||||
def bit_growths(self):
|
||||
bit_growths = cic_growth(N=self.stages, M=self.M, R=max(self.rates))
|
||||
return bit_growths
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
always_ready = self.output.signature.always_ready
|
||||
|
||||
# Detect interpolation factor changes to provide an internal reset signal.
|
||||
factor_last = Signal.like(self.factor)
|
||||
factor_change = Signal()
|
||||
m.d.sync += factor_last.eq(self.factor)
|
||||
m.d.sync += factor_change.eq(factor_last != self.factor)
|
||||
factor_reset = ResetInserter(factor_change)
|
||||
|
||||
# Calculated bit growths only used below for integrator stages.
|
||||
bit_growths = iter(self.bit_growths())
|
||||
|
||||
stages = []
|
||||
|
||||
# When M=1, we can replace the inner CIC stage with an equivalent zero-order hold integrator.
|
||||
inner_zoh = self.M == 1
|
||||
|
||||
# Comb stages.
|
||||
width = self.width_in
|
||||
for i in range(self.stages - int(inner_zoh)):
|
||||
next_width = self.width_in + next(bit_growths)
|
||||
stage = factor_reset(CombStage(self.M, width, width_out=next_width, num_channels=self.num_channels, always_ready=always_ready))
|
||||
m.submodules[f"comb{i}"] = stage
|
||||
stages += [ stage ]
|
||||
width = next_width
|
||||
|
||||
# Upsampling.
|
||||
if list(self.rates) != [1]:
|
||||
if inner_zoh:
|
||||
_ = next(bit_growths), next(bit_growths) # drop comb and integrator growths
|
||||
stage = factor_reset(Upsampler(self.num_channels * width, max(self.rates), zero_order_hold=inner_zoh, variable=True, always_ready=always_ready))
|
||||
m.submodules["upsampler"] = stage
|
||||
m.d.sync += stage.factor.eq(1 << self.factor)
|
||||
stages += [ stage ]
|
||||
|
||||
# Integrator stages.
|
||||
for i in range(self.stages - int(inner_zoh)):
|
||||
width_out = self.width_in + next(bit_growths)
|
||||
stage = SignExtend(width, width_out, num_channels=self.num_channels, always_ready=always_ready)
|
||||
m.submodules[f"signextend{i}"] = stage
|
||||
stages += [ stage ]
|
||||
stage = factor_reset(IntegratorStage(width_out, width_out, num_channels=self.num_channels, always_ready=always_ready))
|
||||
m.submodules[f"integrator{i}"] = stage
|
||||
stages += [ stage ]
|
||||
width = width_out
|
||||
|
||||
# Variable gain stage.
|
||||
min_shift = self.width_in + cic_growth(N=self.stages, M=self.M, R=min(self.rates))[-1] - self.width_out # at min rate
|
||||
shift_per_rate = { int(log2(rate)): min_shift + (self.stages-1)*i for i, rate in enumerate(self.rates) }
|
||||
stage = factor_reset(ProgrammableShift(width, width_out=self.width_out, num_channels=self.num_channels, shift_map=shift_per_rate, always_ready=always_ready))
|
||||
m.submodules["gain"] = stage
|
||||
if len(self.rates) > 1:
|
||||
m.d.sync += stage.shift.eq(self.factor)
|
||||
stages += [ stage ]
|
||||
width = self.width_out
|
||||
|
||||
# Connect all stages to build the final filter.
|
||||
# For the upsampling CIC, we can only drop bits at the last stage.
|
||||
last = wiring.flipped(self.input)
|
||||
for stage in stages:
|
||||
connect(m, last, stage.input)
|
||||
last = stage.output
|
||||
connect(m, last, wiring.flipped(self.output))
|
||||
|
||||
if self._domain != "sync":
|
||||
m = DomainRenamer(self._domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CICDecimator(wiring.Component):
|
||||
def __init__(self, M, stages, rates, width_in, width_out=None, num_channels=1, always_ready=False, domain="sync"):
|
||||
self.M = M
|
||||
self.stages = stages
|
||||
self.rates = rates
|
||||
self.width_in = width_in
|
||||
self.num_channels = num_channels
|
||||
self._domain = domain
|
||||
if width_out is None:
|
||||
width_out = width_in + ceil(stages * log2(max(rates) * M))
|
||||
self.width_out = width_out
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"factor": In(range(bits_for(max(rates)))),
|
||||
})
|
||||
|
||||
def truncation_summary(self):
|
||||
rates = min(self.rates)
|
||||
return cic_truncation(N=self.stages, R=rates, M=self.M,
|
||||
Bin=self.width_in, Bout=self.width_out)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
stages = []
|
||||
|
||||
always_ready = self.output.signature.always_ready
|
||||
|
||||
full_width = self.width_in + ceil(self.stages * log2(max(self.rates) * self.M))
|
||||
stage_widths = ( full_width - n for n in self.truncation_summary() )
|
||||
|
||||
# Sign extend stage
|
||||
last_width = self.width_in
|
||||
stage_width = next(stage_widths)
|
||||
stage = SignExtend(last_width, stage_width, num_channels=self.num_channels, always_ready=always_ready)
|
||||
m.submodules["signextend"] = stage
|
||||
stages += [ stage ]
|
||||
last_width = stage_width
|
||||
|
||||
# Integrator stages
|
||||
for i in range(self.stages):
|
||||
stage_width = next(stage_widths)
|
||||
stage = IntegratorStage(last_width, stage_width, num_channels=self.num_channels, always_ready=always_ready)
|
||||
m.submodules[f"integrator{i}"] = stage
|
||||
stages += [ stage ]
|
||||
last_width = stage_width
|
||||
|
||||
# Downsampling
|
||||
if list(self.rates) != [1]:
|
||||
stage = Downsampler(self.num_channels * last_width, max(self.rates), variable=True, always_ready=always_ready)
|
||||
m.submodules["downsampler"] = stage
|
||||
m.d.sync += stage.factor.eq(1 << self.factor)
|
||||
stages += [ stage ]
|
||||
|
||||
# Comb stages
|
||||
for i in range(self.stages):
|
||||
stage_width = next(stage_widths)
|
||||
stage = CombStage(self.M, last_width, stage_width, num_channels=self.num_channels, always_ready=always_ready)
|
||||
m.submodules[f"comb{i}"] = stage
|
||||
stages += [ stage ]
|
||||
last_width = stage_width
|
||||
|
||||
# Gain stage
|
||||
|
||||
# Ensure filter gain is at least the gain from width conversion.
|
||||
min_growth = ceil(self.stages * log2(min(self.rates) * self.M))
|
||||
if min_growth < self.width_out - self.width_in:
|
||||
growth = self.width_out - self.width_in - min_growth
|
||||
stage = WidthConverter(last_width, last_width+growth, num_channels=self.num_channels, always_ready=always_ready)
|
||||
m.submodules["gain0"] = stage
|
||||
stages += [ stage ]
|
||||
last_width = last_width + growth
|
||||
|
||||
if len(self.rates) > 1:
|
||||
shift_per_rate = { int(log2(rate)): self.stages*i for i, rate in enumerate(self.rates) }
|
||||
stage = ProgrammableShift(last_width, width_out=self.width_out, num_channels=self.num_channels, shift_map=shift_per_rate, always_ready=always_ready)
|
||||
m.submodules["gain"] = stage
|
||||
m.d.sync += stage.shift.eq(self.factor)
|
||||
stages += [stage]
|
||||
last_width = self.width_out
|
||||
|
||||
# Connect stages, rounding/truncating where needed
|
||||
last = wiring.flipped(self.input)
|
||||
for stage in stages:
|
||||
connect(m, last, stage.input)
|
||||
last = stage.output
|
||||
connect(m, last, wiring.flipped(self.output))
|
||||
|
||||
if self._domain != "sync":
|
||||
m = DomainRenamer(self._domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class ProgrammableShift(wiring.Component):
|
||||
def __init__(self, width_in, shift_map, width_out=None, num_channels=1, always_ready=False):
|
||||
self.num_channels = num_channels
|
||||
self.width_in = width_in
|
||||
self.width_out = width_out or width_in
|
||||
self.shift_map = shift_map
|
||||
if len(self.shift_map) == 1:
|
||||
self.shift = C(list(self.shift_map.keys())[0])
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(self.width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(self.width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
} | ({"shift": In(range(max(shift_map.keys())+1))} if len(shift_map)>1 else {}))
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Implement the map itself (should it be done outside?)
|
||||
max_shift = max(self.shift_map.values())
|
||||
|
||||
value_scaled = [ Signal(signed(self.width_in + max_shift)) for _ in range(self.num_channels) ]
|
||||
scaled_valid = Signal()
|
||||
scaled_ready = Signal()
|
||||
|
||||
with m.If(~scaled_valid | scaled_ready):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += scaled_valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
for c in range(self.num_channels):
|
||||
with m.Switch(self.shift):
|
||||
for k, v in self.shift_map.items():
|
||||
with m.Case(k):
|
||||
m.d.sync += value_scaled[c].eq(self.input.payload[c] << (max_shift - v))
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
m.d.comb += scaled_ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(scaled_valid)
|
||||
with m.If(scaled_valid):
|
||||
for c in range(self.num_channels):
|
||||
if max_shift > 0:
|
||||
# Convergent rounding / round to even.
|
||||
m.d.sync += self.output.payload[c].eq(convergent_round(value_scaled[c], max_shift))
|
||||
# Truncation.
|
||||
#m.d.sync += self.output.payload[c].eq(value_scaled[c][max_shift:])
|
||||
else:
|
||||
m.d.sync += self.output.payload[c].eq(value_scaled[c])
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class SignExtend(wiring.Component):
|
||||
def __init__(self, width_in, width_out, num_channels=1, always_ready=False):
|
||||
self.num_channels = num_channels
|
||||
self.always_ready = always_ready
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
for c in range(self.num_channels):
|
||||
m.d.comb += self.output.p[c].eq(self.input.p[c])
|
||||
m.d.comb += self.output.valid.eq(self.input.valid)
|
||||
if not self.always_ready:
|
||||
m.d.comb += self.input.ready.eq(self.output.ready)
|
||||
return m
|
||||
|
||||
|
||||
class WidthConverter(wiring.Component):
|
||||
def __init__(self, width_in, width_out, num_channels=1, always_ready=False):
|
||||
self.width_in = width_in
|
||||
self.width_out = width_out
|
||||
self.num_channels = num_channels
|
||||
self.always_ready = always_ready
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
shift = self.width_out - self.width_in
|
||||
|
||||
for c in range(self.num_channels):
|
||||
m.d.comb += self.output.p[c].eq(self.input.p[c] << shift)
|
||||
m.d.comb += self.output.valid.eq(self.input.valid)
|
||||
if not self.always_ready:
|
||||
m.d.comb += self.input.ready.eq(self.output.ready)
|
||||
return m
|
||||
|
||||
|
||||
class CombStage(wiring.Component):
|
||||
def __init__(self, M, width_in, width_out=None, num_channels=1, always_ready=False):
|
||||
assert M in (1,2)
|
||||
self.M = M
|
||||
self.width_in = width_in
|
||||
self.width_out = width_out or width_in + 1
|
||||
self.num_channels = num_channels
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(self.width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(self.width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
shift = max(self.width_in - self.width_out, 0)
|
||||
delay = [ Signal.like(self.input.p) for _ in range(self.M) ]
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
m.d.sync += delay[0].eq(self.input.p)
|
||||
m.d.sync += [ delay[i].eq(delay[i-1]) for i in range(1, self.M) ]
|
||||
for c in range(self.num_channels):
|
||||
diff = self.input.p[c] - delay[-1][c]
|
||||
m.d.sync += self.output.p[c].eq(diff if shift == 0 else (diff >> shift))
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class IntegratorStage(wiring.Component):
|
||||
def __init__(self, width_in, width_out, num_channels=1, always_ready=False):
|
||||
self.width_in = width_in
|
||||
self.width_out = width_out
|
||||
self.num_channels = num_channels
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(width_in), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(signed(width_out), num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
shift = max(self.width_in - self.width_out, 0)
|
||||
|
||||
accumulator = Signal.like(self.input.p)
|
||||
for c in range(self.num_channels):
|
||||
m.d.comb += self.output.payload[c].eq(accumulator[c] if shift == 0 else (accumulator[c] >> shift))
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
for c in range(self.num_channels):
|
||||
m.d.sync += accumulator[c].eq(accumulator[c] + self.input.payload[c])
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class Upsampler(wiring.Component):
|
||||
def __init__(self, width, factor, zero_order_hold=False, variable=False, always_ready=False):
|
||||
self.width = width
|
||||
self.zoh = zero_order_hold
|
||||
signature = {
|
||||
"input": In(stream.Signature(width, always_ready=always_ready)),
|
||||
"output": Out(stream.Signature(width, always_ready=always_ready)),
|
||||
}
|
||||
if variable:
|
||||
signature.update({"factor": In(range(factor + 1))})
|
||||
else:
|
||||
self.factor = Const(factor)
|
||||
super().__init__(signature)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
counter = Signal.like(self.factor)
|
||||
ready_stb = Signal(init=1)
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(ready_stb)
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
with m.If(counter == 0):
|
||||
m.d.sync += self.output.payload.eq(self.input.payload)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
m.d.sync += counter.eq(self.factor - 1)
|
||||
m.d.sync += ready_stb.eq(self.factor < 2)
|
||||
with m.Else():
|
||||
if not self.zoh:
|
||||
m.d.sync += self.output.payload.eq(0)
|
||||
m.d.sync += self.output.valid.eq(1)
|
||||
m.d.sync += counter.eq(counter - 1)
|
||||
with m.If(counter == 1):
|
||||
m.d.sync += ready_stb.eq(1)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class Downsampler(wiring.Component):
|
||||
def __init__(self, width, factor, variable=False, always_ready=False):
|
||||
signature = {
|
||||
"input": In(stream.Signature(width, always_ready=always_ready)),
|
||||
"output": Out(stream.Signature(width, always_ready=always_ready)),
|
||||
}
|
||||
if variable:
|
||||
# TODO: optimize bit
|
||||
signature.update({"factor": In(range(factor + 1))})
|
||||
else:
|
||||
self.factor = Const(factor)
|
||||
super().__init__(signature)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
counter = Signal.like(self.factor)
|
||||
|
||||
with m.If(self.input.ready & self.input.valid):
|
||||
with m.If(counter == 0):
|
||||
m.d.sync += counter.eq(self.factor - 1)
|
||||
with m.Else():
|
||||
m.d.sync += counter.eq(counter - 1)
|
||||
|
||||
with m.If(self.output.ready | ~self.output.valid):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid & (counter == 0))
|
||||
with m.If(self.input.valid & (counter == 0)):
|
||||
m.d.sync += self.output.payload.eq(self.input.payload)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
# Refs:
|
||||
# [1]: Eugene Hogenauer, "An Economical Class of Digital Filters For Decimation and Interpolation,"
|
||||
# IEEE Trans. Acoust. Speech and Signal Proc., Vol. ASSP-29, April 1981, pp. 155-162.
|
||||
# [2]: Rick Lyons, "Computing CIC filter register pruning using MATLAB"
|
||||
# https://www.dsprelated.com/showcode/269.php
|
||||
# [3]: Peter Thorwartl, "Implementation of Filters", Part 3, lecture notes.
|
||||
# https://www.so-logic.net/documents/trainings/03_so_implementation_of_filters.pdf
|
||||
|
||||
|
||||
# CIC downsamplers / decimators
|
||||
# How much can we prune / truncate every stage output given a desired output width ?
|
||||
# Calculate how many bits we can discard after each intermediate stage such that the quantization
|
||||
# error introduced is not greater than the one introduced by truncating/rounding at the filter
|
||||
# output.
|
||||
|
||||
def F_sq(N, R, M, i):
|
||||
assert i <= 2*N + 1
|
||||
if i == 2*N + 1: return 1 # eq. (16b) from [1]
|
||||
# h(k) and L (range of k), eq. (9b) from [1]
|
||||
if i <= N:
|
||||
# integrator stage
|
||||
L = N * (R * M - 1) + i - 1
|
||||
def h(k):
|
||||
return sum((-1)**l * comb(N, l) * comb(N-i+k-R*M*l, k-R*M*l)
|
||||
for l in range(k//(R*M)+1))
|
||||
else:
|
||||
# comb stage
|
||||
L = 2*N + 1 - i
|
||||
def h(k):
|
||||
return (-1)**k * comb(2*N+1-i, k)
|
||||
# Compute standard deviation error gain from stage i to output
|
||||
F_i_sq = sum(h(k)**2 for k in range(L+1))
|
||||
return F_i_sq
|
||||
|
||||
def cic_truncation(N, R, M, Bin, Bout=None):
|
||||
full_width = Bin + ceil(N * log2(R * M)) # maximum width at output
|
||||
Bout = Bout or full_width # allow to specify full width
|
||||
B_last = full_width - Bout # number of bits discarded at last stage
|
||||
t = log2(2**(2*B_last)/12) + log2(6 / N) # Last two terms of (21) from [1]
|
||||
truncation = []
|
||||
for stage in range(2*N):
|
||||
ou = F_sq(N, R, M, stage+1)
|
||||
B_i = floor(0.5 * (-log2(ou) + t)) # Eq. (21) from [1]
|
||||
truncation.append(max(0, B_i))
|
||||
truncation.append(max(0, B_last))
|
||||
truncation[0] = 0 # [2]: fix case where input is truncated prior to any filtering
|
||||
return truncation
|
||||
|
||||
# CIC upsamplers / interpolators
|
||||
# How much bit growth there is per intermediate stage?
|
||||
# In the interpolator case, we cannot discard bits in intermediate stages: small errors in the
|
||||
# interpolator stages causes the variance of the error to grow without bound resulting in an
|
||||
# unstable filter.
|
||||
|
||||
def cic_growth(N, R, M):
|
||||
growths = []
|
||||
for i in range(1, 2*N+1):
|
||||
if i <= N:
|
||||
G_i = 2**i # comb stage
|
||||
# special case from [1] when differential delay is 1
|
||||
if M == 1 and i == N:
|
||||
G_i = 2**(N - 1)
|
||||
else:
|
||||
G_i = (2**(2*N-i) * (R*M)**(i-N)) / R # integration stage
|
||||
growths.append(ceil(log2(G_i)))
|
||||
return growths
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Tests
|
||||
#
|
||||
|
||||
import unittest
|
||||
import numpy as np
|
||||
from amaranth.sim import Simulator
|
||||
from collections import namedtuple
|
||||
|
||||
class _TestFilter(unittest.TestCase):
|
||||
|
||||
def _generate_samples(self, count, width, f_width=0):
|
||||
# Generate `count` random samples.
|
||||
rng = np.random.default_rng(0)
|
||||
samples = rng.normal(0, 1, count)
|
||||
|
||||
# Convert to integer.
|
||||
samples = np.round(samples / max(abs(samples)) * (2**(width-1) - 1)).astype(int)
|
||||
assert max(samples) < 2**(width-1) and min(samples) >= -2**(width-1) # sanity check
|
||||
|
||||
if f_width > 0:
|
||||
return samples / (1 << f_width)
|
||||
return samples
|
||||
|
||||
def _filter(self, dut, samples, count, oob=[], outfile=None):
|
||||
|
||||
async def input_process(ctx):
|
||||
if hasattr(dut, "enable"):
|
||||
ctx.set(dut.enable, 1)
|
||||
for name, value in oob.items():
|
||||
ctx.set(getattr(dut, name), value)
|
||||
await ctx.tick()
|
||||
await ctx.tick()
|
||||
|
||||
for sample in samples:
|
||||
ctx.set(dut.input.payload, [sample.item()])
|
||||
ctx.set(dut.input.valid, 1)
|
||||
await ctx.tick().until(dut.input.ready)
|
||||
ctx.set(dut.input.valid, 0)
|
||||
|
||||
filtered = []
|
||||
async def output_process(ctx):
|
||||
if not dut.output.signature.always_ready:
|
||||
ctx.set(dut.output.ready, 1)
|
||||
async for clk, rst, valid, payload in ctx.tick().sample(dut.output.valid, dut.output.payload):
|
||||
if valid:
|
||||
filtered.append(payload[0])
|
||||
if len(filtered) == count:
|
||||
break
|
||||
|
||||
sim = Simulator(dut)
|
||||
sim.add_clock(1/100e6)
|
||||
sim.add_testbench(input_process)
|
||||
sim.add_testbench(output_process)
|
||||
if outfile is not None:
|
||||
with sim.write_vcd(outfile):
|
||||
sim.run()
|
||||
else:
|
||||
sim.run()
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
class TestCICDecimator(_TestFilter):
|
||||
|
||||
def test_filter(self):
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
input_samples = self._generate_samples(num_samples, input_width)
|
||||
|
||||
test = namedtuple('CICDecimatorTest', ['M', 'order', 'rates', 'factor_log', 'width_in', 'width_out', 'outfile'], defaults=(None,)*7)
|
||||
cic_tests = []
|
||||
|
||||
# for different CIC orders...
|
||||
for o in [1,2,3,4]:
|
||||
# test signal with no rate change
|
||||
cic_tests.append(test(M=1, order=o, rates=(1,), factor_log=0, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(1,), factor_log=0, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(1,), factor_log=0, width_in=8, width_out=12))
|
||||
|
||||
# test decimation by 4 with different M values and minimum decimation factors
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=1, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=1, order=o, rates=(4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
|
||||
# different bit widths
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=9))
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=10))
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=0, width_in=8, width_out=12))
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=1, width_in=8, width_out=12))
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=12))
|
||||
|
||||
# test fixed decimation by 32
|
||||
cic_tests.append(test(M=1, order=o, rates=(32,), factor_log=5, width_in=8, width_out=8))
|
||||
|
||||
|
||||
for t in cic_tests:
|
||||
with self.subTest(t):
|
||||
factor_log = t.factor_log
|
||||
factor = 1 << factor_log
|
||||
cic_order = t.order
|
||||
M = t.M
|
||||
|
||||
# Build taps by convolving boxcar filter repeatedly.
|
||||
taps0 = [1 for _ in range(factor*M)]
|
||||
taps = [1]
|
||||
for i in range(cic_order):
|
||||
taps = np.convolve(taps, taps0)
|
||||
|
||||
# Compute the expected result.
|
||||
cic_gain = (factor*M)**cic_order
|
||||
width_gain = 2**(t.width_out - t.width_in)
|
||||
filtered_np = np.convolve(input_samples, taps)
|
||||
filtered_np = filtered_np[::factor] # decimate
|
||||
filtered_np = np.round(filtered_np * width_gain // cic_gain) # scale
|
||||
filtered_np = filtered_np.astype(np .int32).tolist() # convert to python list
|
||||
|
||||
# Simulate DUT
|
||||
dut = CICDecimator(M, cic_order, t.rates, t.width_in, t.width_out, always_ready=True)
|
||||
filtered = self._filter(dut, input_samples, len(input_samples)//factor, oob={"factor":factor_log}, outfile=t.outfile)
|
||||
|
||||
# As we have some rounding error, we expect some samples to differ at most by 1
|
||||
max_diff = np.max(np.abs(np.array(filtered) - np.array(filtered_np[:len(filtered)])))
|
||||
|
||||
self.assertLessEqual(max_diff, 1)
|
||||
#self.assertListEqual(filtered_np[:len(filtered)], filtered)
|
||||
|
||||
|
||||
class TestCICInterpolator(_TestFilter):
|
||||
|
||||
def test_filter(self):
|
||||
num_samples = 1024
|
||||
|
||||
test = namedtuple('CICInterpolatorTest', ['M', 'order', 'rates', 'factor_log', 'width_in', 'width_out', 'outfile'], defaults=(None,)*7)
|
||||
cic_tests = []
|
||||
|
||||
# for different CIC orders...
|
||||
for o in [1,2,3,4]:
|
||||
# test signal bypass
|
||||
cic_tests.append(test(M=1, order=o, rates=(1,), factor_log=0, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=1, order=o, rates=(1,), factor_log=0, width_in=12, width_out=8))
|
||||
|
||||
# test interpolation by 4 with different M values and minimum interpolation factors
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=1, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
cic_tests.append(test(M=1, order=o, rates=(4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
|
||||
|
||||
# different bit widths
|
||||
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=12, width_out=8))
|
||||
cic_tests.append(test(M=2, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=12, width_out=8))
|
||||
cic_tests.append(test(M=1, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=12, width_out=8))
|
||||
|
||||
# test fixed interpolation by 32
|
||||
cic_tests.append(test(M=1, order=o, rates=(32,), factor_log=5, width_in=8, width_out=8))
|
||||
|
||||
cic_tests.append(test(M=1, order=o, rates=(32,), factor_log=5, width_in=12, width_out=8))
|
||||
|
||||
for t in cic_tests:
|
||||
with self.subTest(t):
|
||||
|
||||
input_samples = self._generate_samples(num_samples, t.width_in)
|
||||
|
||||
factor_log = t.factor_log
|
||||
factor = 1 << factor_log
|
||||
cic_order = t.order
|
||||
M = t.M
|
||||
|
||||
# Build taps by convolving boxcar filter repeatedly.
|
||||
taps0 = [1 for _ in range(factor*M)]
|
||||
taps = [1]
|
||||
for i in range(cic_order):
|
||||
taps = np.convolve(taps, taps0)
|
||||
|
||||
# Compute the expected result
|
||||
cic_gain = (factor*M)**cic_order // factor
|
||||
width_gain = 2**(t.width_out - t.width_in)
|
||||
filtered_np = np.zeros(factor * num_samples)
|
||||
filtered_np[::factor] = input_samples
|
||||
filtered_np = np.convolve(filtered_np, taps)
|
||||
filtered_np = np.round(filtered_np * width_gain / cic_gain) # scale
|
||||
filtered_np = filtered_np.astype(np.int32).tolist() # convert to python list
|
||||
|
||||
# Simulate DUT
|
||||
dut = CICInterpolator(M, cic_order, t.rates, t.width_in, t.width_out, always_ready=False)
|
||||
filtered = self._filter(dut, input_samples, len(input_samples)//factor, oob={"factor":factor_log}, outfile=t.outfile)
|
||||
|
||||
self.assertListEqual(filtered_np[:len(filtered)], filtered)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
141
firmware/fpga/dsp/dc_block.py
Normal file
141
firmware/fpga/dsp/dc_block.py
Normal file
@@ -0,0 +1,141 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from amaranth import Module, Signal, Mux, signed, DomainRenamer, Cat, EnableInserter
|
||||
from amaranth.lib import wiring, stream, data
|
||||
from amaranth.lib.wiring import In, Out
|
||||
|
||||
|
||||
class DCBlock(wiring.Component):
|
||||
"""
|
||||
DC blocking filter with dithering
|
||||
|
||||
Removes DC offset using a leaky integrator:
|
||||
y[n] = x[n] - avg[n-1]
|
||||
avg[n] = alpha * y[n] + avg[n-1]
|
||||
where alpha is the leakage coefficient (2**-ratio).
|
||||
"""
|
||||
def __init__(self, width, ratio=12, num_channels=1, always_ready=True, domain="sync"):
|
||||
self.ratio = ratio
|
||||
self.width = width
|
||||
self.num_channels = num_channels
|
||||
self.domain = domain
|
||||
|
||||
sig = stream.Signature(
|
||||
data.ArrayLayout(signed(width), num_channels),
|
||||
always_ready=always_ready
|
||||
)
|
||||
super().__init__({
|
||||
"input": In(sig),
|
||||
"output": Out(sig),
|
||||
"enable": In(1),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Resync control signaling.
|
||||
enable = Signal()
|
||||
m.d.sync += [
|
||||
enable .eq(self.enable),
|
||||
]
|
||||
|
||||
# Fixed-point configuration.
|
||||
ratio = self.ratio
|
||||
input_shape = signed(self.width)
|
||||
ext_precision = signed(self.width + ratio)
|
||||
|
||||
# Shared PRNG for all channels.
|
||||
prng_en = Signal()
|
||||
m.submodules.prng = prng = EnableInserter(prng_en)(Xoroshiro64AOX())
|
||||
prng_bits = prng.output
|
||||
|
||||
# Common signaling.
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
|
||||
with m.If(self.input.ready & self.input.valid):
|
||||
m.d.comb += prng_en.eq(1)
|
||||
|
||||
# Per-channel processing.
|
||||
for c in range(self.num_channels):
|
||||
|
||||
# Signal declarations.
|
||||
sample_in = self.input.p[c]
|
||||
y = Signal(input_shape) # current output
|
||||
y_q = Signal(input_shape) # last output
|
||||
avg = Signal(ext_precision) # current average
|
||||
avg_q = Signal(ext_precision) # last average
|
||||
qavg = Signal(input_shape) # quantized avg
|
||||
qavg_q = Signal(input_shape) # last quantized avg
|
||||
error = Signal(ratio)
|
||||
dither = Signal(ratio) # dither pattern
|
||||
|
||||
# Generate unique dither pattern for each channel.
|
||||
m.d.sync += dither.eq(prng_bits.word_select(c, ratio))
|
||||
|
||||
def saturating_sub(a, b):
|
||||
r = a - b
|
||||
r_sat = Cat((~r[-1]).replicate(self.width-1), r[-1])
|
||||
overflow = r[-1] ^ r[-2] # sign bit of the result different from carry (top 2 bits)
|
||||
return Mux(overflow, r_sat, r)
|
||||
|
||||
with m.If(self.input.valid & self.input.ready):
|
||||
|
||||
m.d.sync += [
|
||||
y_q .eq(y),
|
||||
avg_q .eq(avg),
|
||||
qavg_q .eq(qavg),
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
y .eq(saturating_sub(sample_in, qavg_q)),
|
||||
avg .eq(avg_q + y_q), # update avg
|
||||
|
||||
# Truncate with dithering, discard quantization error.
|
||||
Cat(error, qavg).eq(avg + dither),
|
||||
]
|
||||
|
||||
m.d.sync += self.output.p[c].eq(Mux(enable, y, self.input.p[c]))
|
||||
|
||||
if self.domain != "sync":
|
||||
m = DomainRenamer(self.domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class Xoroshiro64AOX(wiring.Component):
|
||||
""" Variant of xoroshiro64 for faster hardware implementation """
|
||||
""" AOX mod from 'A Fast Hardware Pseudorandom Number Generator Based on xoroshiro128' """
|
||||
output: Out(32)
|
||||
|
||||
def __init__(self, s0=1, s1=0):
|
||||
self.s0 = s0
|
||||
self.s1 = s1
|
||||
super().__init__()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
s0 = Signal(32, init=self.s0)
|
||||
s1 = Signal(32, init=self.s1)
|
||||
|
||||
a = 26
|
||||
b = 9
|
||||
c = 13
|
||||
|
||||
sx = Signal(32)
|
||||
sa = Signal(32)
|
||||
m.d.comb += sx.eq(s0 ^ s1)
|
||||
m.d.comb += sa.eq(s0 & s1)
|
||||
|
||||
m.d.sync += s0.eq(s0.rotate_left(a) ^ sx ^ (sx << b))
|
||||
m.d.sync += s1.eq(sx.rotate_left(c))
|
||||
m.d.sync += self.output.eq(sx ^ (sa.rotate_left(1) | sa.rotate_left(2)))
|
||||
|
||||
return m
|
||||
604
firmware/fpga/dsp/fir.py
Normal file
604
firmware/fpga/dsp/fir.py
Normal file
@@ -0,0 +1,604 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from math import ceil, log2
|
||||
|
||||
from amaranth import Module, Signal, Mux, DomainRenamer
|
||||
from amaranth.lib import wiring, stream, data, memory
|
||||
from amaranth.lib.wiring import In, Out
|
||||
from amaranth.utils import bits_for
|
||||
|
||||
from amaranth_future import fixed
|
||||
|
||||
from dsp.mcm import ShiftAddMCM
|
||||
|
||||
|
||||
class HalfBandDecimator(wiring.Component):
|
||||
def __init__(self, taps, data_shape, shape_out=None, always_ready=False, domain="sync"):
|
||||
midtap = taps[len(taps)//2]
|
||||
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
|
||||
assert midtap == 0.5
|
||||
self.taps = taps
|
||||
self.data_shape = data_shape
|
||||
if shape_out is None:
|
||||
shape_out = data_shape
|
||||
self.shape_out = shape_out
|
||||
self.always_ready = always_ready
|
||||
self._domain = domain
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(data_shape, 2),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape_out, 2),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"enable": In(1),
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def interleave_with_zeros(seq, factor):
|
||||
out = []
|
||||
for n in seq:
|
||||
out.append(n)
|
||||
out.extend([0]*factor)
|
||||
return out[:-factor]
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
always_ready = self.always_ready
|
||||
taps = [ 2 * tap for tap in self.taps ] # scale by 0.5 at the output
|
||||
fir_taps = self.interleave_with_zeros(taps[0::2], 1)
|
||||
|
||||
# Arms
|
||||
m.submodules.fir = fir = FIRFilter(fir_taps, shape=self.data_shape, always_ready=always_ready,
|
||||
num_channels=1, add_tap=len(fir_taps)//2+1)
|
||||
|
||||
with m.FSM():
|
||||
|
||||
with m.State("BYPASS"):
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
m.d.sync += self.output.payload.eq(self.input.payload)
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
|
||||
with m.If(self.enable):
|
||||
m.next = "DECIMATE"
|
||||
|
||||
with m.State("DECIMATE"):
|
||||
|
||||
# Input switching.
|
||||
odd = Signal()
|
||||
input_idx = Signal()
|
||||
even_valid = Signal()
|
||||
even_buffer = Signal.like(self.input.p)
|
||||
q_inputs = Signal.like(self.input.p)
|
||||
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq((~odd & ~even_valid) | fir.input.ready)
|
||||
|
||||
# Even samples are buffered and used as a secondary
|
||||
# carry addition for the FIR filter.
|
||||
# I and Q channels are muxed in time, demuxed later in the output stage.
|
||||
with m.If(self.input.valid & self.input.ready):
|
||||
m.d.sync += odd.eq(~odd)
|
||||
with m.If(~odd):
|
||||
with m.If(~even_valid | fir.input.ready):
|
||||
m.d.sync += even_valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
m.d.sync += even_buffer.eq(self.input.p)
|
||||
|
||||
# Process two I samples and two Q samples in sequence.
|
||||
with m.If(fir.input.ready & fir.input.valid):
|
||||
m.d.sync += input_idx.eq(input_idx ^ 1)
|
||||
|
||||
with m.If(input_idx == 0):
|
||||
m.d.comb += [
|
||||
fir.add_input .eq(even_buffer[0]),
|
||||
fir.input.p .eq(self.input.p[0]),
|
||||
fir.input.valid .eq(self.input.valid & even_valid),
|
||||
]
|
||||
with m.If(fir.input.ready & fir.input.valid):
|
||||
m.d.sync += [
|
||||
q_inputs[0].eq(even_buffer[1]),
|
||||
q_inputs[1].eq(self.input.p[1]),
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += [
|
||||
fir.add_input .eq(q_inputs[0]),
|
||||
fir.input.p .eq(q_inputs[1]),
|
||||
fir.input.valid .eq(1),
|
||||
]
|
||||
|
||||
# Output sum and demux.
|
||||
output_idx = Signal()
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not fir.output.signature.always_ready:
|
||||
m.d.comb += fir.output.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(fir.output.valid & output_idx)
|
||||
with m.If(fir.output.valid):
|
||||
m.d.sync += self.output.p[0].eq(self.output.p[1])
|
||||
m.d.sync += self.output.p[1].eq(fir.output.p[0] * fixed.Const(0.5))
|
||||
m.d.sync += output_idx.eq(output_idx ^ 1)
|
||||
|
||||
# Mode switch logic.
|
||||
with m.If(~self.enable):
|
||||
m.d.sync += input_idx.eq(0)
|
||||
m.d.sync += output_idx.eq(0)
|
||||
m.d.sync += odd.eq(0)
|
||||
m.d.sync += even_valid.eq(0)
|
||||
m.next = "BYPASS"
|
||||
|
||||
if self._domain != "sync":
|
||||
m = DomainRenamer(self._domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class HalfBandInterpolator(wiring.Component):
|
||||
def __init__(self, taps, data_shape, shape_out=None, always_ready=False, num_channels=1, domain="sync"):
|
||||
midtap = taps[len(taps)//2]
|
||||
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
|
||||
assert midtap == 0.5
|
||||
self.taps = taps
|
||||
self.data_shape = data_shape
|
||||
if shape_out is None:
|
||||
shape_out = data_shape
|
||||
self.shape_out = shape_out
|
||||
self.always_ready = always_ready
|
||||
self._domain = domain
|
||||
self.num_channels = num_channels
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(data_shape, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape_out, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"enable": In(1),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
always_ready = self.always_ready
|
||||
|
||||
taps = [ 2 * tap for tap in self.taps ]
|
||||
arm0_taps = taps[0::2]
|
||||
arm1_taps = taps[1::2]
|
||||
delay = arm1_taps.index(1)
|
||||
|
||||
# Arms
|
||||
m.submodules.fir0 = fir0 = FIRFilter(arm0_taps, shape=self.data_shape, shape_out=self.shape_out, always_ready=always_ready, num_channels=self.num_channels)
|
||||
m.submodules.fir1 = fir1 = Delay(delay, shape=self.data_shape, always_ready=always_ready, num_channels=self.num_channels)
|
||||
arms = [fir0, fir1]
|
||||
|
||||
with m.FSM():
|
||||
|
||||
with m.State("BYPASS"):
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
m.d.sync += self.output.payload.eq(self.input.payload)
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
|
||||
with m.If(self.enable):
|
||||
m.next = "INTERPOLATE"
|
||||
|
||||
with m.State("INTERPOLATE"):
|
||||
|
||||
# Mode switch logic.
|
||||
with m.If(~self.enable):
|
||||
m.next = "BYPASS"
|
||||
|
||||
# Input
|
||||
|
||||
for i, arm in enumerate(arms):
|
||||
m.d.comb += arm.input.payload.eq(self.input.payload)
|
||||
m.d.comb += arm.input.valid.eq(self.input.valid & arms[i^1].input.ready)
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(arms[0].input.ready & arms[1].input.ready)
|
||||
|
||||
# Output
|
||||
|
||||
# Arm index selection: switch after every delivered sample
|
||||
arm_index = Signal()
|
||||
|
||||
# Output buffers for each arm.
|
||||
arm_outputs = [arm.output for arm in arms]
|
||||
if self.output.signature.always_ready:
|
||||
buffers = [stream.Signature(arm.payload.shape()).create() for arm in arm_outputs]
|
||||
for arm, buf in zip(arm_outputs, buffers):
|
||||
with m.If(~buf.valid | buf.ready):
|
||||
if not arm.signature.always_ready:
|
||||
m.d.comb += arm.ready.eq(1)
|
||||
m.d.sync += buf.valid.eq(arm.valid)
|
||||
with m.If(arm.valid):
|
||||
m.d.sync += buf.payload.eq(arm.payload)
|
||||
arm_outputs = buffers
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
with m.Switch(arm_index):
|
||||
for i, arm in enumerate(arm_outputs):
|
||||
with m.Case(i):
|
||||
for c in range(self.num_channels):
|
||||
m.d.sync += self.output.payload[c].eq(arm.payload[c])
|
||||
m.d.sync += self.output.valid.eq(arm.valid)
|
||||
if not arm.signature.always_ready:
|
||||
m.d.comb += arm.ready.eq(1)
|
||||
with m.If(arm.valid):
|
||||
m.d.sync += arm_index.eq(arm_index ^ 1)
|
||||
|
||||
if self._domain != "sync":
|
||||
m = DomainRenamer(self._domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class FIRFilter(wiring.Component):
|
||||
|
||||
def __init__(self, taps, shape, shape_out=None, always_ready=False, num_channels=1, add_tap=None):
|
||||
self.taps = list(taps)
|
||||
self.add_tap = add_tap
|
||||
self.shape = shape
|
||||
if shape_out is None:
|
||||
shape_out = self.compute_output_shape()
|
||||
self.shape_out = shape_out
|
||||
self.num_channels = num_channels
|
||||
self.always_ready = always_ready
|
||||
|
||||
sig = {
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(shape, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape_out, num_channels),
|
||||
always_ready=always_ready
|
||||
))
|
||||
}
|
||||
if add_tap is not None:
|
||||
sig |= {"add_input": In(data.ArrayLayout(shape, num_channels))}
|
||||
|
||||
super().__init__(sig)
|
||||
|
||||
def taps_shape(self):
|
||||
taps_as_ratios = [tap.as_integer_ratio() for tap in self.taps]
|
||||
f_width = bits_for(max(tap[1] for tap in taps_as_ratios)) - 1
|
||||
i_width = max(0, bits_for(max(abs(tap[0]) for tap in taps_as_ratios)) - f_width)
|
||||
return fixed.Shape(i_width, f_width, signed=any(tap < 0 for tap in self.taps))
|
||||
|
||||
def compute_output_shape(self):
|
||||
taps_shape = self.taps_shape()
|
||||
signed = self.shape.signed | taps_shape.signed
|
||||
f_width = self.shape.f_width + taps_shape.f_width
|
||||
filter_gain = ceil(log2(sum(self.taps) + (1 if self.add_tap is not None else 0)))
|
||||
i_width = max(0, self.shape.as_shape().width + taps_shape.as_shape().width - signed - f_width + filter_gain)
|
||||
return fixed.Shape(i_width, f_width, signed=signed)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Implement transposed-form FIR because it needs a smaller number of registers.
|
||||
|
||||
# Helper function to create smaller size registers for fixed point ops.
|
||||
def fixed_reg(value, *args, **kwargs):
|
||||
reg = Signal.like(value.raw(), *args, **kwargs)
|
||||
return fixed.Value(value.shape(), reg)
|
||||
|
||||
# Implement constant multipliers.
|
||||
terms = []
|
||||
for i, tap in enumerate(self.taps):
|
||||
tap_fixed = fixed.Const(tap)
|
||||
terms.append(tap_fixed._value)
|
||||
|
||||
m.submodules.mcm = mcm = ShiftAddMCM(self.shape.as_shape().width, terms, num_channels=self.num_channels, always_ready=self.always_ready)
|
||||
wiring.connect(m, wiring.flipped(self.input), mcm.input)
|
||||
|
||||
# Cast outputs to fixed point values.
|
||||
muls = []
|
||||
for i, tap in enumerate(self.taps):
|
||||
tap_fixed = fixed.Const(tap)
|
||||
muls.append([ fixed.Value.cast(mcm.output.p[c][f"{i}"], tap_fixed.f_width + self.shape.f_width) for c in range(self.num_channels) ])
|
||||
|
||||
# Implement adder line.
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not self.always_ready:
|
||||
m.d.comb += mcm.output.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(mcm.output.valid)
|
||||
|
||||
# Carry sum
|
||||
if self.add_tap is not None:
|
||||
add_input_q = Signal.like(self.add_input)
|
||||
m.d.sync += add_input_q.eq(self.add_input)
|
||||
|
||||
for c in range(self.num_channels):
|
||||
|
||||
accum = None
|
||||
for i, tap in enumerate(self.taps[::-1]):
|
||||
|
||||
match (accum, tap):
|
||||
case (None, 0): continue
|
||||
case (None, _): value = muls[::-1][i][c]
|
||||
case (_, 0): value = accum
|
||||
case (_, _): value = muls[::-1][i][c] + accum
|
||||
|
||||
if self.add_tap is not None:
|
||||
if i == self.add_tap:
|
||||
value += add_input_q[c]
|
||||
|
||||
accum = fixed_reg(value, name=f"add_{c}_{i}")
|
||||
|
||||
with m.If(mcm.output.valid & mcm.output.ready):
|
||||
m.d.sync += accum.eq(value)
|
||||
|
||||
m.d.comb += self.output.payload[c].eq(accum)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class Delay(wiring.Component):
|
||||
def __init__(self, delay, shape, always_ready=False, num_channels=1):
|
||||
self.delay = delay
|
||||
self.shape = shape
|
||||
self.num_channels = num_channels
|
||||
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(shape, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
if self.delay < 3:
|
||||
return self.elaborate_regs()
|
||||
return self.elaborate_memory()
|
||||
|
||||
def elaborate_regs(self):
|
||||
m = Module()
|
||||
|
||||
last = self.input.payload
|
||||
for i in range(self.delay + 1):
|
||||
reg = Signal.like(last, name=f"reg_{i}")
|
||||
with m.If(self.input.valid & self.input.ready):
|
||||
m.d.sync += reg.eq(last)
|
||||
last = reg
|
||||
m.d.comb += self.output.payload.eq(last)
|
||||
|
||||
with m.If(self.output.ready | ~self.output.valid):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
|
||||
return m
|
||||
|
||||
def elaborate_memory(self):
|
||||
m = Module()
|
||||
|
||||
m.submodules.mem = mem = memory.Memory(
|
||||
shape=self.input.payload.shape(),
|
||||
depth=self.delay,
|
||||
init=(tuple(0 for _ in range(self.num_channels)) for _ in range(self.delay))
|
||||
)
|
||||
mem_wr = mem.write_port(domain="sync")
|
||||
mem_rd = mem.read_port(domain="sync")
|
||||
|
||||
addr = Signal.like(mem_wr.addr)
|
||||
with m.If(self.input.valid & self.input.ready):
|
||||
m.d.sync += addr.eq(Mux(addr == self.delay-1, 0, addr + 1))
|
||||
|
||||
m.d.comb += [
|
||||
mem_wr.addr .eq(addr),
|
||||
mem_rd.addr .eq(addr),
|
||||
mem_wr.data .eq(self.input.payload),
|
||||
mem_wr.en .eq(self.input.valid & self.input.ready),
|
||||
mem_rd.en .eq(self.input.valid & self.input.ready),
|
||||
self.output.payload .eq(mem_rd.data),
|
||||
]
|
||||
|
||||
with m.If(self.output.ready | ~self.output.valid):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
#
|
||||
# Tests
|
||||
#
|
||||
|
||||
import unittest
|
||||
import numpy as np
|
||||
from amaranth.sim import Simulator
|
||||
|
||||
class _TestFilter(unittest.TestCase):
|
||||
|
||||
rng = np.random.default_rng(0)
|
||||
|
||||
def _generate_samples(self, count, width, f_width=0):
|
||||
# Generate `count` random samples.
|
||||
samples = self.rng.normal(0, 1, count)
|
||||
|
||||
# Convert to integer.
|
||||
samples = np.round(samples / max(abs(samples)) * (2**(width-1) - 1)).astype(int)
|
||||
assert max(samples) < 2**(width-1) and min(samples) >= -2**(width-1) # sanity check
|
||||
|
||||
if f_width > 0:
|
||||
return samples / (1 << f_width)
|
||||
return samples
|
||||
|
||||
def _filter(self, dut, samples, count, num_channels=1, outfile=None, empty_cycles=0):
|
||||
|
||||
async def input_process(ctx):
|
||||
if hasattr(dut, "enable"):
|
||||
ctx.set(dut.enable, 1)
|
||||
await ctx.tick()
|
||||
ctx.set(dut.input.valid, 1)
|
||||
for sample in samples:
|
||||
if num_channels > 1:
|
||||
ctx.set(dut.input.payload, [s.item() for s in sample])
|
||||
else:
|
||||
ctx.set(dut.input.payload, [sample.item()])
|
||||
await ctx.tick().until(dut.input.ready)
|
||||
if empty_cycles > 0:
|
||||
ctx.set(dut.input.valid, 0)
|
||||
await ctx.tick().repeat(empty_cycles)
|
||||
ctx.set(dut.input.valid, 1)
|
||||
ctx.set(dut.input.valid, 0)
|
||||
|
||||
filtered = []
|
||||
async def output_process(ctx):
|
||||
if not dut.output.signature.always_ready:
|
||||
ctx.set(dut.output.ready, 1)
|
||||
while len(filtered) < count:
|
||||
payload, = await ctx.tick().sample(dut.output.payload).until(dut.output.valid)
|
||||
if num_channels > 1:
|
||||
filtered.append([v.as_float() for v in payload])
|
||||
else:
|
||||
filtered.append(payload[0].as_float())
|
||||
if not dut.output.signature.always_ready:
|
||||
ctx.set(dut.output.ready, 0)
|
||||
|
||||
sim = Simulator(dut)
|
||||
sim.add_clock(1/100e6)
|
||||
sim.add_testbench(input_process)
|
||||
sim.add_testbench(output_process)
|
||||
if outfile:
|
||||
with sim.write_vcd(outfile):
|
||||
sim.run()
|
||||
else:
|
||||
sim.run()
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
class TestFIRFilter(_TestFilter):
|
||||
|
||||
def test_filter(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
input_samples = self._generate_samples(num_samples, input_width)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_np = np.convolve(input_samples, taps).tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = FIRFilter(taps, fixed.SQ(15, 0), always_ready=True)
|
||||
filtered = self._filter(dut, input_samples, len(input_samples))
|
||||
|
||||
self.assertListEqual(filtered_np[:len(filtered)], filtered)
|
||||
|
||||
|
||||
class TestHalfBandDecimator(_TestFilter):
|
||||
|
||||
def test_filter_no_backpressure(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
|
||||
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = HalfBandDecimator(taps, data_shape=fixed.SQ(7), shape_out=fixed.SQ(0,16), always_ready=True)
|
||||
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2)
|
||||
filtered_i = [ x[0] for x in filtered ]
|
||||
filtered_q = [ x[1] for x in filtered ]
|
||||
|
||||
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
|
||||
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
|
||||
|
||||
def test_filter_with_spare_cycles(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
|
||||
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = HalfBandDecimator(taps, data_shape=fixed.SQ(7), shape_out=fixed.SQ(0,16), always_ready=True)
|
||||
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2, empty_cycles=3)
|
||||
filtered_i = [ x[0] for x in filtered ]
|
||||
filtered_q = [ x[1] for x in filtered ]
|
||||
|
||||
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
|
||||
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
|
||||
|
||||
def test_filter_with_backpressure(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
|
||||
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = HalfBandDecimator(taps, data_shape=fixed.SQ(7), shape_out=fixed.SQ(0,16), always_ready=False)
|
||||
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2)
|
||||
filtered_i = [ x[0] for x in filtered ]
|
||||
filtered_q = [ x[1] for x in filtered ]
|
||||
|
||||
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
|
||||
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
|
||||
|
||||
class TestHalfBandInterpolator(_TestFilter):
|
||||
|
||||
def test_filter(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
input_samples = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
|
||||
# Compute the expected result
|
||||
input_samples_pad = np.zeros(2*len(input_samples))
|
||||
input_samples_pad[0::2] = 2*input_samples # pad with zeros, adjust gain
|
||||
filtered_np = np.convolve(input_samples_pad, taps).tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = HalfBandInterpolator(taps, data_shape=fixed.SQ(0, 7), shape_out=fixed.SQ(0,16), always_ready=False)
|
||||
filtered = self._filter(dut, input_samples, len(input_samples) * 2)
|
||||
|
||||
self.assertListEqual(filtered_np[:len(filtered)], filtered)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
829
firmware/fpga/dsp/fir_mac16.py
Normal file
829
firmware/fpga/dsp/fir_mac16.py
Normal file
@@ -0,0 +1,829 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from math import ceil, log2
|
||||
|
||||
from amaranth import Module, Signal, Mux, DomainRenamer, ClockSignal, signed
|
||||
from amaranth.lib import wiring, stream, data, memory
|
||||
from amaranth.lib.wiring import In, Out
|
||||
from amaranth.utils import bits_for
|
||||
|
||||
from amaranth_future import fixed
|
||||
|
||||
from dsp.sb_mac16 import SB_MAC16
|
||||
from dsp.fir import Delay
|
||||
|
||||
|
||||
class HalfBandDecimatorMAC16(wiring.Component):
|
||||
def __init__(self, taps, data_shape, overclock_rate=4, shape_out=None, always_ready=False, domain="sync"):
|
||||
midtap = taps[len(taps)//2]
|
||||
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
|
||||
self.taps = taps
|
||||
self.data_shape = data_shape
|
||||
if shape_out is None:
|
||||
shape_out = data_shape
|
||||
self.shape_out = shape_out
|
||||
self.always_ready = always_ready
|
||||
self.overclock_rate = overclock_rate
|
||||
self._domain = domain
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(data_shape, 2),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape_out, 2),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
always_ready = self.always_ready
|
||||
taps = [ 2 * tap for tap in self.taps ] # scale by 0.5 at the output
|
||||
fir_taps = taps[0::2]
|
||||
dly_taps = taps[1::2]
|
||||
delay = dly_taps.index(1) - 1
|
||||
|
||||
# Arms
|
||||
m.submodules.fir = fir = FIRFilterMAC16(fir_taps, shape=self.data_shape, overclock_rate=2*self.overclock_rate, always_ready=always_ready, num_channels=2, carry=self.data_shape)
|
||||
m.submodules.dly = dly = Delay(delay, shape=self.data_shape, always_ready=always_ready, num_channels=2)
|
||||
|
||||
# Input switching.
|
||||
odd = Signal()
|
||||
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(~odd | fir.input.ready)
|
||||
m.d.comb += dly.output.ready.eq(1)
|
||||
|
||||
m.d.comb += [
|
||||
dly.input.p.eq(self.input.p),
|
||||
dly.input.valid.eq(self.input.valid & ~odd),
|
||||
]
|
||||
|
||||
# Even samples are buffered and used as a secondary
|
||||
# carry addition for the FIR filter.
|
||||
with m.If(self.input.valid & self.input.ready):
|
||||
m.d.sync += odd.eq(~odd)
|
||||
|
||||
#
|
||||
for c in range(2):
|
||||
m.d.comb += [
|
||||
fir.sum_carry[c] .eq(dly.output.p[c]), # TODO: optimize shape?
|
||||
fir.input.p[c] .eq(self.input.p[c]),
|
||||
]
|
||||
m.d.comb += fir.input.valid .eq(self.input.valid & odd)
|
||||
|
||||
# Output.
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not fir.output.signature.always_ready:
|
||||
m.d.comb += fir.output.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(fir.output.valid)
|
||||
with m.If(fir.output.valid):
|
||||
m.d.sync += self.output.p[0].eq(fir.output.p[0] * fixed.Const(0.5))
|
||||
m.d.sync += self.output.p[1].eq(fir.output.p[1] * fixed.Const(0.5))
|
||||
|
||||
if self._domain != "sync":
|
||||
m = DomainRenamer(self._domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class HalfBandInterpolatorMAC16(wiring.Component):
|
||||
def __init__(self, taps, data_shape, shape_out=None, overclock_rate=4, always_ready=False, num_channels=1, domain="sync"):
|
||||
midtap = taps[len(taps)//2]
|
||||
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
|
||||
assert midtap == 0.5
|
||||
self.taps = taps
|
||||
self.data_shape = data_shape
|
||||
if shape_out is None:
|
||||
shape_out = data_shape
|
||||
self.shape_out = shape_out
|
||||
self.always_ready = always_ready
|
||||
self._domain = domain
|
||||
self.num_channels = num_channels
|
||||
self.overclock_rate = overclock_rate
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(data_shape, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape_out, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
always_ready = self.always_ready
|
||||
|
||||
taps = [ 2 * tap for tap in self.taps ]
|
||||
arm0_taps = taps[0::2]
|
||||
|
||||
# Arms
|
||||
m.submodules.fir = fir = FIRFilterMAC16(arm0_taps, shape=self.data_shape, shape_out=self.shape_out, overclock_rate=self.overclock_rate, always_ready=always_ready, num_channels=self.num_channels, delayed_port=True)
|
||||
|
||||
busy = Signal()
|
||||
with m.If(fir.input.valid & fir.input.ready):
|
||||
m.d.sync += busy.eq(1)
|
||||
|
||||
# Input
|
||||
m.d.comb += fir.input.payload.eq(self.input.payload)
|
||||
m.d.comb += fir.input.valid.eq(self.input.valid & ~busy)
|
||||
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(fir.input.ready & ~busy)
|
||||
|
||||
# Output
|
||||
|
||||
# Arm index selection: switch after every delivered sample
|
||||
arm_index = Signal()
|
||||
|
||||
delayed = Signal.like(fir.input_delayed)
|
||||
with m.If(fir.output.valid & fir.output.ready):
|
||||
m.d.sync += delayed.eq(fir.input_delayed)
|
||||
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
with m.Switch(arm_index):
|
||||
with m.Case(0):
|
||||
for c in range(self.num_channels):
|
||||
m.d.sync += self.output.payload[c].eq(fir.output.payload[c])
|
||||
m.d.sync += self.output.valid.eq(fir.output.valid)
|
||||
if not fir.output.signature.always_ready:
|
||||
m.d.comb += fir.output.ready.eq(1)
|
||||
with m.If(fir.output.valid):
|
||||
m.d.sync += arm_index.eq(1)
|
||||
with m.Case(1):
|
||||
for c in range(self.num_channels):
|
||||
m.d.sync += self.output.payload[c].eq(delayed[c])
|
||||
m.d.sync += self.output.valid.eq(1)
|
||||
m.d.sync += arm_index.eq(0)
|
||||
m.d.sync += busy.eq(0)
|
||||
|
||||
if self._domain != "sync":
|
||||
m = DomainRenamer(self._domain)(m)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class FIRFilterMAC16(wiring.Component):
|
||||
|
||||
def __init__(self, taps, shape, shape_out=None, always_ready=False, overclock_rate=8, num_channels=1, carry=None, delayed_port=False):
|
||||
self.carry = carry
|
||||
self.taps = list(taps)
|
||||
self.shape = shape
|
||||
if shape_out is None:
|
||||
shape_out = self.compute_output_shape()
|
||||
self.shape_out = shape_out
|
||||
self.num_channels = num_channels
|
||||
self.always_ready = always_ready
|
||||
self.overclock_rate = overclock_rate
|
||||
self.delayed_port = delayed_port
|
||||
|
||||
signature = {
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(shape, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(shape_out, num_channels),
|
||||
always_ready=always_ready
|
||||
)),
|
||||
}
|
||||
if carry is not None:
|
||||
signature.update({
|
||||
"sum_carry": In(data.ArrayLayout(carry, num_channels))
|
||||
})
|
||||
if delayed_port:
|
||||
signature.update({
|
||||
"input_delayed": Out(data.ArrayLayout(shape, num_channels))
|
||||
})
|
||||
super().__init__(signature)
|
||||
|
||||
def taps_shape(self):
|
||||
taps_as_ratios = [tap.as_integer_ratio() for tap in self.taps]
|
||||
f_width = bits_for(max(tap[1] for tap in taps_as_ratios)) - 1
|
||||
i_width = max(0, bits_for(max(abs(tap[0]) for tap in taps_as_ratios)) - f_width)
|
||||
return fixed.Shape(i_width, f_width, signed=any(tap < 0 for tap in self.taps))
|
||||
|
||||
def compute_output_shape(self):
|
||||
taps_shape = self.taps_shape()
|
||||
signed = self.shape.signed | taps_shape.signed
|
||||
f_width = self.shape.f_width + taps_shape.f_width
|
||||
filter_gain = ceil(log2(sum(self.taps)))
|
||||
i_width = max(0, self.shape.as_shape().width + taps_shape.as_shape().width - signed - f_width + filter_gain)
|
||||
if self.carry is not None:
|
||||
f_width = max(f_width, self.carry.f_width)
|
||||
i_width = max(i_width, self.carry.i_width) + 1
|
||||
shape_out = fixed.Shape(i_width, f_width, signed=signed)
|
||||
return shape_out
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Build filter out of FIRFilterSerialMAC16 blocks.
|
||||
overclock_factor = self.overclock_rate
|
||||
|
||||
# Symmetric coefficients special case.
|
||||
symmetric = (self.taps == self.taps[::-1])
|
||||
|
||||
# Even-symmetric case. (N=2*K)
|
||||
# Odd-symmetric case. (N=2*K+1)
|
||||
if symmetric:
|
||||
taps = self.taps[:ceil(len(self.taps)/2)]
|
||||
odd_symmetric = ((len(self.taps) % 2) == 1)
|
||||
else:
|
||||
taps = self.taps
|
||||
|
||||
dsp_block_count = ceil(len(taps) / overclock_factor)
|
||||
|
||||
|
||||
def pipe(signal, length):
|
||||
name = signal.name if hasattr(signal, "name") else "signal"
|
||||
pipe = [ signal ] + [ Signal.like(signal, name=f"{name}_q{i}") for i in range(length) ]
|
||||
for i in range(length):
|
||||
m.d.sync += pipe[i+1].eq(pipe[i])
|
||||
return pipe
|
||||
|
||||
|
||||
if self.carry is not None:
|
||||
sum_carry_q = Signal.like(self.sum_carry)
|
||||
with m.If(self.input.valid & self.input.ready):
|
||||
m.d.sync += sum_carry_q.eq(self.sum_carry)
|
||||
|
||||
for c in range(self.num_channels):
|
||||
|
||||
last = self.input
|
||||
dsp_blocks = []
|
||||
|
||||
for i in range(dsp_block_count):
|
||||
taps_slice = taps[i*overclock_factor:(i+1)*overclock_factor]
|
||||
input_delayed = len(taps_slice)
|
||||
carry = last.output.p.shape() if i > 0 else self.carry
|
||||
|
||||
if (i == dsp_block_count-1) and symmetric and odd_symmetric:
|
||||
taps_slice[-1] /= 2
|
||||
input_delayed -= 1
|
||||
|
||||
dsp = FIRFilterSerialMAC16(taps=taps_slice, shape=self.shape, taps_shape=self.taps_shape(), carry=carry, symmetry=symmetric,
|
||||
input_delayed_cycles=input_delayed, always_ready=self.always_ready)
|
||||
dsp_blocks.append(dsp)
|
||||
|
||||
if i == 0:
|
||||
m.d.comb += [
|
||||
dsp.input.p .eq(self.input.p[c]),
|
||||
dsp.input.valid .eq(self.input.valid & self.input.ready),
|
||||
]
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(dsp.input.ready)
|
||||
if self.carry is not None:
|
||||
m.d.comb += dsp.sum_carry.eq(sum_carry_q[c])
|
||||
else:
|
||||
m.d.comb += [
|
||||
dsp.input.p .eq(pipe(last.input_delayed, last.delay())[-1]),
|
||||
dsp.input.valid .eq(last.output.valid),
|
||||
dsp.sum_carry .eq(last.output.p),
|
||||
]
|
||||
if not last.output.signature.always_ready:
|
||||
m.d.comb += last.output.ready.eq(dsp.input.ready)
|
||||
|
||||
last = dsp
|
||||
|
||||
if self.delayed_port:
|
||||
m.d.comb += self.input_delayed[c].eq(last.input_delayed)
|
||||
|
||||
if symmetric:
|
||||
|
||||
for i in reversed(range(dsp_block_count)):
|
||||
end_block = (i == dsp_block_count-1)
|
||||
m.d.comb += [
|
||||
dsp_blocks[i].rev_input .eq(dsp_blocks[i+1].rev_delayed if not end_block else dsp_blocks[i].input_delayed),
|
||||
]
|
||||
|
||||
m.submodules += dsp_blocks
|
||||
|
||||
m.d.comb += [
|
||||
self.output.payload[c] .eq(last.output.p),
|
||||
self.output.valid .eq(last.output.valid),
|
||||
]
|
||||
if not last.output.signature.always_ready:
|
||||
m.d.comb += last.output.ready.eq(self.output.ready)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class FIRFilterSerialMAC16(wiring.Component):
|
||||
|
||||
def __init__(self, taps, shape, shape_out=None, taps_shape=None, carry=None, symmetry=False, input_delayed_cycles=None, always_ready=False):
|
||||
assert shape.as_shape().width <= 16, "DSP slice inputs have a maximum width of 16 bit."
|
||||
|
||||
self.carry = carry
|
||||
self.taps = list(taps)
|
||||
self.shape = shape
|
||||
self.taps_shape = taps_shape or self.taps_shape()
|
||||
if shape_out is None:
|
||||
shape_out = self.compute_output_shape()
|
||||
self.shape_out = shape_out
|
||||
self.always_ready = always_ready
|
||||
self.symmetry = symmetry
|
||||
if input_delayed_cycles is None:
|
||||
self.input_delayed_cycles = len(self.taps)
|
||||
else:
|
||||
self.input_delayed_cycles = input_delayed_cycles
|
||||
|
||||
signature = {
|
||||
"input": In(stream.Signature(shape, always_ready=always_ready)),
|
||||
"input_delayed": Out(shape),
|
||||
"output": Out(stream.Signature(shape_out, always_ready=always_ready)),
|
||||
}
|
||||
if carry is not None:
|
||||
signature.update({
|
||||
"sum_carry": In(carry)
|
||||
})
|
||||
else:
|
||||
self.sum_carry = 0
|
||||
if symmetry:
|
||||
signature.update({
|
||||
"rev_input": In(shape),
|
||||
"rev_delayed": Out(shape),
|
||||
})
|
||||
super().__init__(signature)
|
||||
|
||||
def taps_shape(self):
|
||||
taps_as_ratios = [tap.as_integer_ratio() for tap in self.taps]
|
||||
f_width = bits_for(max(tap[1] for tap in taps_as_ratios)) - 1
|
||||
i_width = max(0, bits_for(max(abs(tap[0]) for tap in taps_as_ratios)) - f_width)
|
||||
return fixed.Shape(i_width, f_width, signed=any(tap < 0 for tap in self.taps))
|
||||
|
||||
def compute_output_shape(self):
|
||||
taps_shape = self.taps_shape
|
||||
signed = self.shape.signed | taps_shape.signed
|
||||
f_width = self.shape.f_width + taps_shape.f_width
|
||||
filter_gain = ceil(log2(max(1, sum(self.taps))))
|
||||
i_width = max(0, self.shape.as_shape().width + taps_shape.as_shape().width - signed - f_width + filter_gain)
|
||||
if self.carry is not None:
|
||||
f_width = max(f_width, self.carry.f_width)
|
||||
i_width = max(i_width, self.carry.i_width) + 1
|
||||
shape_out = fixed.Shape(i_width, f_width, signed=signed)
|
||||
return shape_out
|
||||
|
||||
def delay(self):
|
||||
return 1 + 1 + 3 + len(self.taps) - 1
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
depth = len(self.taps)
|
||||
counter_in = Signal(range(depth))
|
||||
counter_mult = Signal(range(depth))
|
||||
counter_out = Signal(range(depth))
|
||||
dsp_ready = ~self.output.valid | self.output.ready
|
||||
|
||||
window_valid = Signal()
|
||||
window_ready = dsp_ready
|
||||
multin_valid = Signal()
|
||||
|
||||
|
||||
input_ready = Signal()
|
||||
# Ready to process a sample either when the DSP slice is ready and the samples window is:
|
||||
# - Not valid yet.
|
||||
# - Only valid for 1 more cycle.
|
||||
m.d.comb += input_ready.eq(~window_valid | ((counter_in == depth-1) & window_ready))
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(input_ready)
|
||||
|
||||
window = [ Signal.like(self.input.p, name=f"window_{i}") for i in range(max(depth, self.input_delayed_cycles)) ]
|
||||
|
||||
# Sample window.
|
||||
with m.If(input_ready):
|
||||
m.d.sync += window_valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
m.d.sync += window[0].eq(self.input.p)
|
||||
for i in range(1, len(window)):
|
||||
m.d.sync += window[i].eq(window[i-1])
|
||||
|
||||
m.d.sync += multin_valid.eq(window_valid)
|
||||
|
||||
dsp_a = Signal.like(self.input.p)
|
||||
with m.Switch(counter_in):
|
||||
for i in range(depth):
|
||||
with m.Case(i):
|
||||
m.d.sync += dsp_a.eq(window[i])
|
||||
|
||||
m.d.comb += self.input_delayed.eq(window[self.input_delayed_cycles-1])
|
||||
|
||||
# Sample counter.
|
||||
with m.If(window_ready & window_valid):
|
||||
m.d.sync += counter_in.eq(_incr(counter_in, depth))
|
||||
|
||||
# Symmetry handling.
|
||||
if self.symmetry:
|
||||
|
||||
window_rev = [ Signal.like(self.input.p, name=f"window_rev_{i}") for i in range(depth) ]
|
||||
|
||||
with m.If(input_ready & self.input.valid):
|
||||
m.d.sync += window_rev[0].eq(self.rev_input)
|
||||
m.d.sync += [ window_rev[i].eq(window_rev[i-1]) for i in range(1, len(window_rev)) ]
|
||||
|
||||
m.d.comb += self.rev_delayed.eq(window_rev[-1])
|
||||
|
||||
dsp_a_rev = Signal.like(self.input.p)
|
||||
with m.Switch(counter_in):
|
||||
for i in range(depth):
|
||||
with m.Case(i):
|
||||
m.d.sync += dsp_a_rev.eq(window_rev[depth-1-i])
|
||||
|
||||
|
||||
# Coefficient ROM.
|
||||
taps_shape = self.taps_shape
|
||||
assert taps_shape.as_shape().width <= 16, "DSP slice inputs have a maximum width of 16 bit."
|
||||
coeff_data = memory.MemoryData(
|
||||
shape=taps_shape,
|
||||
depth=depth, # +200 to force BRAM
|
||||
init=(fixed.Const(tap, shape=taps_shape) for tap in self.taps),
|
||||
)
|
||||
m.submodules.coeff_rom = coeff_rom = memory.Memory(data=coeff_data)
|
||||
coeff_rd = coeff_rom.read_port(domain="sync")
|
||||
m.d.comb += coeff_rd.addr.eq(counter_in)
|
||||
|
||||
shape_out = self.compute_output_shape()
|
||||
|
||||
if self.carry:
|
||||
sum_carry_q = Signal.like(self.sum_carry)
|
||||
with m.If(self.input.ready & self.input.valid):
|
||||
m.d.sync += sum_carry_q.eq(self.sum_carry)
|
||||
|
||||
m.submodules.dsp = dsp = iCE40Multiplier()
|
||||
if self.symmetry:
|
||||
m.d.comb += dsp.a.eq(dsp_a + dsp_a_rev)
|
||||
else:
|
||||
m.d.comb += dsp.a.eq(dsp_a)
|
||||
m.d.comb += [
|
||||
dsp.b .eq(coeff_rd.data),
|
||||
shape_out(dsp.p) .eq(sum_carry_q if self.carry is not None else 0),
|
||||
dsp.valid_in .eq(multin_valid & window_ready),
|
||||
dsp.p_load .eq(counter_mult == 0),
|
||||
self.output.p .eq(shape_out(dsp.o)),
|
||||
self.output.valid .eq(dsp.valid_out & (counter_out == depth-1)),
|
||||
]
|
||||
|
||||
# Multiplier input and output counters.
|
||||
with m.If(dsp.valid_in):
|
||||
m.d.sync += counter_mult.eq(_incr(counter_mult, depth))
|
||||
with m.If(dsp.valid_out):
|
||||
m.d.sync += counter_out.eq(_incr(counter_out, depth))
|
||||
|
||||
return m
|
||||
|
||||
|
||||
|
||||
class iCE40Multiplier(wiring.Component):
|
||||
|
||||
a: In(signed(16))
|
||||
b: In(signed(16))
|
||||
valid_in: In(1)
|
||||
|
||||
p: In(signed(32))
|
||||
p_load: In(1)
|
||||
|
||||
o: Out(signed(32))
|
||||
valid_out: Out(1)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
def pipe(signal, length):
|
||||
pipe = [ signal ] + [ Signal.like(signal, name=f"{signal.name}_q{i}") for i in range(length) ]
|
||||
for i in range(length):
|
||||
m.d.sync += pipe[i+1].eq(pipe[i])
|
||||
return pipe
|
||||
|
||||
p_load_v = Signal()
|
||||
|
||||
dsp_delay = 3
|
||||
valid_pipe = pipe(self.valid_in, dsp_delay)
|
||||
m.d.comb += p_load_v.eq(self.p_load & self.valid_in)
|
||||
p_pipe = pipe(self.p, dsp_delay-1)
|
||||
p_load_pipe = pipe(p_load_v, dsp_delay - 1)
|
||||
m.d.comb += self.valid_out.eq(valid_pipe[dsp_delay])
|
||||
|
||||
m.submodules.sb_mac16 = mac = SB_MAC16(
|
||||
C_REG=0,
|
||||
A_REG=1,
|
||||
B_REG=1,
|
||||
D_REG=0,
|
||||
TOP_8x8_MULT_REG=0,
|
||||
BOT_8x8_MULT_REG=0,
|
||||
PIPELINE_16x16_MULT_REG1=0,
|
||||
PIPELINE_16x16_MULT_REG2=1,
|
||||
TOPOUTPUT_SELECT=1,
|
||||
TOPADDSUB_LOWERINPUT=2,
|
||||
TOPADDSUB_UPPERINPUT=1,
|
||||
TOPADDSUB_CARRYSELECT=3,
|
||||
BOTOUTPUT_SELECT=1,
|
||||
BOTADDSUB_LOWERINPUT=2,
|
||||
BOTADDSUB_UPPERINPUT=1,
|
||||
BOTADDSUB_CARRYSELECT=0,
|
||||
MODE_8x8=0,
|
||||
A_SIGNED=1,
|
||||
B_SIGNED=1,
|
||||
)
|
||||
|
||||
m.d.comb += [
|
||||
# Inputs.
|
||||
mac.CLK .eq(ClockSignal("sync")),
|
||||
mac.CE .eq(1),
|
||||
mac.C .eq(Mux(p_load_pipe[2], p_pipe[2][16:], self.o[16:])),
|
||||
mac.A .eq(self.a),
|
||||
mac.B .eq(self.b),
|
||||
mac.D .eq(Mux(p_load_pipe[2], p_pipe[2][:16], self.o[:16])),
|
||||
mac.AHOLD .eq(~valid_pipe[0]), # 0: load
|
||||
mac.BHOLD .eq(~valid_pipe[0]),
|
||||
mac.CHOLD .eq(0),
|
||||
mac.DHOLD .eq(0),
|
||||
mac.OHOLDTOP .eq(~valid_pipe[2]),
|
||||
mac.OHOLDBOT .eq(~valid_pipe[2]),
|
||||
mac.ADDSUBTOP .eq(0),
|
||||
mac.ADDSUBBOT .eq(0),
|
||||
mac.OLOADTOP .eq(0),
|
||||
mac.OLOADBOT .eq(0),
|
||||
|
||||
# Outputs.
|
||||
self.o .eq(mac.O),
|
||||
]
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def _incr(signal, modulo):
|
||||
if modulo == 2 ** len(signal):
|
||||
return signal + 1
|
||||
else:
|
||||
return Mux(signal == modulo - 1, 0, signal + 1)
|
||||
|
||||
#
|
||||
# Tests
|
||||
#
|
||||
|
||||
import unittest
|
||||
import numpy as np
|
||||
from amaranth.sim import Simulator
|
||||
|
||||
class _TestFilter(unittest.TestCase):
|
||||
|
||||
rng = np.random.default_rng(0)
|
||||
|
||||
def _generate_samples(self, count, width, f_width=0):
|
||||
# Generate `count` random samples.
|
||||
samples = self.rng.normal(0, 1, count)
|
||||
|
||||
# Convert to integer.
|
||||
samples = np.round(samples / max(abs(samples)) * (2**(width-1) - 1)).astype(int)
|
||||
assert max(samples) < 2**(width-1) and min(samples) >= -2**(width-1) # sanity check
|
||||
|
||||
if f_width > 0:
|
||||
return samples / (1 << f_width)
|
||||
return samples
|
||||
|
||||
def _filter(self, dut, samples, count, num_channels=1, outfile=None, empty_cycles=0):
|
||||
|
||||
async def input_process(ctx):
|
||||
if hasattr(dut, "enable"):
|
||||
ctx.set(dut.enable, 1)
|
||||
await ctx.tick()
|
||||
|
||||
for i, sample in enumerate(samples):
|
||||
if num_channels > 1:
|
||||
ctx.set(dut.input.payload, [s.item() for s in sample])
|
||||
else:
|
||||
if isinstance(dut.input.payload.shape(), data.ArrayLayout):
|
||||
ctx.set(dut.input.payload, [sample.item()])
|
||||
else:
|
||||
ctx.set(dut.input.payload, sample.item())
|
||||
ctx.set(dut.input.valid, 1)
|
||||
await ctx.tick().until(dut.input.ready)
|
||||
ctx.set(dut.input.valid, 0)
|
||||
if empty_cycles > 0:
|
||||
await ctx.tick().repeat(empty_cycles)
|
||||
|
||||
filtered = []
|
||||
async def output_process(ctx):
|
||||
if not dut.output.signature.always_ready:
|
||||
ctx.set(dut.output.ready, 1)
|
||||
while len(filtered) < count:
|
||||
payload, = await ctx.tick().sample(dut.output.payload).until(dut.output.valid)
|
||||
if num_channels > 1:
|
||||
filtered.append([v.as_float() for v in payload])
|
||||
else:
|
||||
if isinstance(payload.shape(), data.ArrayLayout):
|
||||
filtered.append(payload[0].as_float())
|
||||
else:
|
||||
filtered.append(payload.as_float())
|
||||
if not dut.output.signature.always_ready:
|
||||
ctx.set(dut.output.ready, 0)
|
||||
|
||||
sim = Simulator(dut)
|
||||
sim.add_clock(1/100e6)
|
||||
sim.add_testbench(input_process)
|
||||
sim.add_testbench(output_process)
|
||||
if outfile:
|
||||
with sim.write_vcd(outfile):
|
||||
sim.run()
|
||||
else:
|
||||
sim.run()
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
class TestFIRFilterMAC16(_TestFilter):
|
||||
|
||||
def test_filter_serial(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
input_samples = self._generate_samples(num_samples, input_width)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_np = np.convolve(input_samples, taps).tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = FIRFilterSerialMAC16(taps, fixed.SQ(15, 0), always_ready=False)
|
||||
filtered = self._filter(dut, input_samples, len(input_samples))
|
||||
|
||||
self.assertListEqual(filtered_np[:len(filtered)], filtered)
|
||||
|
||||
def test_filter(self):
|
||||
taps = [-1, 0, 9, 16, 9, 0, -1]
|
||||
taps = [ tap / 32 for tap in taps ]
|
||||
|
||||
num_samples = 1024
|
||||
input_width = 8
|
||||
input_samples = self._generate_samples(num_samples, input_width)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_np = np.convolve(input_samples, taps).tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = FIRFilterMAC16(taps, fixed.SQ(15, 0), always_ready=False)
|
||||
filtered = self._filter(dut, input_samples, len(input_samples))
|
||||
|
||||
self.assertListEqual(filtered_np[:len(filtered)], filtered)
|
||||
|
||||
|
||||
class TestHalfBandDecimatorMAC16(_TestFilter):
|
||||
|
||||
def test_filter(self):
|
||||
|
||||
common_dut_options = dict(
|
||||
data_shape=fixed.SQ(7),
|
||||
shape_out=fixed.SQ(0,31),
|
||||
overclock_rate=4,
|
||||
)
|
||||
|
||||
taps0 = (np.array([-1, 0, 9, 16, 9, 0, -1]) / 32).tolist()
|
||||
taps1 = (np.array([-2, 0, 7, 0, -18, 0, 41, 0, -92, 0, 320, 512, 320, 0, -92, 0, 41, 0, -18, 0, 7, 0, -2]) / 1024).tolist()
|
||||
|
||||
|
||||
inputs = {
|
||||
|
||||
"test_filter_with_backpressure": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, always_ready=False, taps=taps0),
|
||||
"sim_opts": dict(empty_cycles=0),
|
||||
},
|
||||
|
||||
"test_filter_with_backpressure_and_empty_cycles": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, always_ready=False, taps=taps0),
|
||||
"sim_opts": dict(empty_cycles=3),
|
||||
},
|
||||
|
||||
"test_filter_with_backpressure_taps1": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, always_ready=False, taps=taps1),
|
||||
"sim_opts": dict(empty_cycles=0),
|
||||
},
|
||||
|
||||
"test_filter_no_backpressure_and_empty_cycles_taps1": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, always_ready=True, taps=taps0),
|
||||
"sim_opts": dict(empty_cycles=3),
|
||||
},
|
||||
|
||||
"test_filter_no_backpressure": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, always_ready=True, taps=taps1),
|
||||
"sim_opts": dict(empty_cycles=3),
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario in inputs.items():
|
||||
|
||||
with self.subTest(name):
|
||||
taps = scenario["dut_options"]["taps"]
|
||||
num_samples = scenario["num_samples"]
|
||||
|
||||
input_width = 8
|
||||
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
|
||||
# Compute the expected result
|
||||
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
|
||||
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = HalfBandDecimatorMAC16(**scenario["dut_options"])
|
||||
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2, **scenario["sim_opts"])
|
||||
filtered_i = [ x[0] for x in filtered ]
|
||||
filtered_q = [ x[1] for x in filtered ]
|
||||
|
||||
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
|
||||
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
|
||||
|
||||
|
||||
class TestHalfBandInterpolatorMAC16(_TestFilter):
|
||||
|
||||
def test_filter(self):
|
||||
|
||||
common_dut_options = dict(
|
||||
data_shape=fixed.SQ(7),
|
||||
shape_out=fixed.SQ(1,16),
|
||||
overclock_rate=4,
|
||||
)
|
||||
|
||||
taps0 = (np.array([-1, 0, 9, 16, 9, 0, -1]) / 32).tolist()
|
||||
taps1 = (np.array([-2, 0, 7, 0, -18, 0, 41, 0, -92, 0, 320, 512, 320, 0, -92, 0, 41, 0, -18, 0, 7, 0, -2]) / 1024).tolist()
|
||||
|
||||
inputs = {
|
||||
|
||||
"test_filter_with_backpressure": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, always_ready=False, num_channels=2, taps=taps0),
|
||||
"sim_opts": dict(empty_cycles=0),
|
||||
},
|
||||
|
||||
"test_filter_with_backpressure_and_empty_cycles": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=False, taps=taps0),
|
||||
"sim_opts": dict(empty_cycles=3),
|
||||
},
|
||||
|
||||
"test_filter_with_backpressure_taps1": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=False, taps=taps1),
|
||||
"sim_opts": dict(empty_cycles=0),
|
||||
},
|
||||
|
||||
"test_filter_no_backpressure_and_empty_cycles_taps1": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=True, taps=taps0),
|
||||
"sim_opts": dict(empty_cycles=8),
|
||||
},
|
||||
|
||||
"test_filter_no_backpressure": {
|
||||
"num_samples": 1024,
|
||||
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=True, taps=taps1),
|
||||
"sim_opts": dict(empty_cycles=16),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
for name, scenario in inputs.items():
|
||||
with self.subTest(name):
|
||||
taps = scenario["dut_options"]["taps"]
|
||||
num_samples = scenario["num_samples"]
|
||||
|
||||
input_width = 8
|
||||
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
|
||||
|
||||
# Compute the expected result
|
||||
input_samples_pad = np.zeros(2*len(samples_i_in))
|
||||
input_samples_pad[0::2] = 2*samples_i_in # pad with zeros, adjust gain
|
||||
filtered_i_np = np.convolve(input_samples_pad, taps).tolist()
|
||||
input_samples_pad = np.zeros(2*len(samples_q_in))
|
||||
input_samples_pad[0::2] = 2*samples_q_in # pad with zeros, adjust gain
|
||||
filtered_q_np = np.convolve(input_samples_pad, taps).tolist()
|
||||
|
||||
# Simulate DUT
|
||||
dut = HalfBandInterpolatorMAC16(**scenario["dut_options"])
|
||||
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) * 2, num_channels=2, **scenario["sim_opts"])
|
||||
filtered_i = [ x[0] for x in filtered ]
|
||||
filtered_q = [ x[1] for x in filtered ]
|
||||
|
||||
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
|
||||
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
156
firmware/fpga/dsp/mcm.py
Normal file
156
firmware/fpga/dsp/mcm.py
Normal file
@@ -0,0 +1,156 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from amaranth import Module, Signal, signed
|
||||
from amaranth.lib import wiring, stream, data
|
||||
from amaranth.lib.wiring import In, Out
|
||||
from amaranth.utils import bits_for
|
||||
|
||||
|
||||
class ShiftAddMCM(wiring.Component):
|
||||
def __init__(self, width, terms, num_channels=1, always_ready=False):
|
||||
self.terms = terms
|
||||
self.width = width
|
||||
self.num_channels = num_channels
|
||||
super().__init__({
|
||||
"input": In(stream.Signature(
|
||||
data.ArrayLayout(signed(width), num_channels),
|
||||
always_ready=always_ready)),
|
||||
"output": Out(stream.Signature(
|
||||
data.ArrayLayout(
|
||||
data.StructLayout({
|
||||
f"{i}": signed(width + bits_for(term)) for i, term in enumerate(terms)
|
||||
}), num_channels), always_ready=always_ready)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Get unique, odd terms.
|
||||
terms = self.terms
|
||||
unique_terms = defaultdict(list)
|
||||
for i, term in enumerate(terms):
|
||||
if term == 0:
|
||||
continue
|
||||
term_odd, shift = make_odd(term)
|
||||
unique_terms[term_odd] += [(i, shift)]
|
||||
|
||||
# Negated inputs for CSD.
|
||||
input_neg = Signal.like(self.input.p)
|
||||
for c in range(self.num_channels):
|
||||
m.d.comb += input_neg[c].eq(-self.input.p[c])
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
|
||||
for term, outputs in unique_terms.items():
|
||||
|
||||
term_csd = to_csd(term)
|
||||
|
||||
for c in range(self.num_channels):
|
||||
|
||||
n = self.input.p[c]
|
||||
n_neg = input_neg[c]
|
||||
|
||||
result = None
|
||||
for s, t in enumerate(term_csd):
|
||||
if t == 0:
|
||||
continue
|
||||
n_base = n if t == 1 else n_neg
|
||||
shifted_n = n_base if s == 0 else (n_base << s)
|
||||
if result is None:
|
||||
result = shifted_n
|
||||
else:
|
||||
result += shifted_n
|
||||
|
||||
# A single register can feed multiple outputs.
|
||||
result_q = Signal(signed(self.width+bits_for(term-1)), name=f"mul_{term}_{c}")
|
||||
with m.If(self.input.ready & self.input.valid):
|
||||
m.d.sync += result_q.eq(result)
|
||||
|
||||
for out_index, shift in outputs:
|
||||
m.d.comb += self.output.p[c][f"{out_index}"].eq(result_q if shift == 0 else (result_q << shift))
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def make_odd(n):
|
||||
"""Convert number to odd fundamental by right-shifting. Returns (odd_part, shift_amount)"""
|
||||
if n == 0:
|
||||
return 0, 0
|
||||
|
||||
shift = 0
|
||||
while n % 2 == 0:
|
||||
n = n >> 1
|
||||
shift += 1
|
||||
|
||||
return n, shift
|
||||
|
||||
|
||||
def multiply(n, k):
|
||||
if k == 0:
|
||||
return 0
|
||||
|
||||
csd_k = to_csd(k)
|
||||
|
||||
result = None
|
||||
for i, c in enumerate(csd_k):
|
||||
if c == 0:
|
||||
continue
|
||||
shifted_n = n if i == 0 else (n << i)
|
||||
if result is None:
|
||||
if c == 1:
|
||||
result = shifted_n
|
||||
elif c == -1:
|
||||
result = -shifted_n
|
||||
else:
|
||||
if c == 1:
|
||||
result += shifted_n
|
||||
elif c == -1:
|
||||
result -= shifted_n
|
||||
|
||||
return result[:bits_for(k-1)+len(n)].as_signed()
|
||||
|
||||
|
||||
def to_csd(n):
|
||||
""" Convert integer to Canonical Signed Digit representation (LSB first). """
|
||||
if n == 0:
|
||||
return [0]
|
||||
|
||||
sign = n < 0
|
||||
n = abs(n)
|
||||
binary = [ int(b) for b in f"{n:b}" ][::-1]
|
||||
|
||||
# Apply CSD conversion algorithm.
|
||||
binary_padded = binary + [0]
|
||||
carry = 0
|
||||
csd = []
|
||||
for i, bit in enumerate(binary_padded):
|
||||
nextbit = binary_padded[i+1] if i+1 < len(binary_padded) else 0
|
||||
d = bit ^ carry
|
||||
ys = nextbit & d # sign bit
|
||||
yd = ~nextbit & d # data bit
|
||||
csd.append(yd - ys)
|
||||
carry = (bit & nextbit) | ((bit|nextbit)&carry)
|
||||
if sign:
|
||||
csd = [-1*c for c in csd]
|
||||
|
||||
# Remove trailing zeros.
|
||||
while len(csd) > 1 and csd[-1] == 0:
|
||||
csd.pop()
|
||||
|
||||
# Regular binary representation is preferred if the number
|
||||
# of additions was not improved.
|
||||
if sum(binary) <= sum(abs(d) for d in csd) - sign:
|
||||
if sign:
|
||||
return [ -d for d in binary ]
|
||||
return binary
|
||||
|
||||
return csd
|
||||
103
firmware/fpga/dsp/nco.py
Executable file
103
firmware/fpga/dsp/nco.py
Executable file
@@ -0,0 +1,103 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from math import pi, sin, cos
|
||||
|
||||
from amaranth import Module, Signal, Mux, Cat
|
||||
from amaranth.lib import wiring, memory
|
||||
from amaranth.lib.wiring import In, Out
|
||||
|
||||
from util import IQSample
|
||||
|
||||
|
||||
class NCO(wiring.Component):
|
||||
"""
|
||||
Retrieve cos(x), sin(x) using a look-up table.
|
||||
Latency is 2 cycles.
|
||||
|
||||
We only precompute 1/8 of the (cos,sin) cycle, and the top 3 bits of the
|
||||
phase are used to reconstruct the final values with symmetric properties.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
phase_width : int
|
||||
Bit width of the phase accumulator.
|
||||
output_width : int
|
||||
Bit width of the output cos/sin words.
|
||||
|
||||
Signals
|
||||
-------
|
||||
phase : Signal(phase_width), in
|
||||
Input phase.
|
||||
en : Signal(1), in
|
||||
Enable strobe.
|
||||
output : IQSample(output_width), out
|
||||
Returned result for cos(phase), sin(phase).
|
||||
"""
|
||||
|
||||
def __init__(self, phase_width=24, output_width=10):
|
||||
self.phase_width = phase_width
|
||||
self.output_width = output_width
|
||||
super().__init__({
|
||||
"phase": In(phase_width),
|
||||
"en": In(1),
|
||||
"output": Out(IQSample(output_width)),
|
||||
})
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Create internal table with precomputed entries.
|
||||
addr_width = (self.output_width + 1) - 3
|
||||
lut_depth = 1 << addr_width
|
||||
lut_scale = (1 << (self.output_width-1)) - 1
|
||||
lut_phases = [ i * pi / 4 / lut_depth for i in range(lut_depth) ]
|
||||
lut_data = memory.MemoryData(
|
||||
shape=IQSample(self.output_width),
|
||||
depth=lut_depth,
|
||||
init=({"i": round(lut_scale * cos(x)), "q": round(lut_scale * sin(x))} for x in lut_phases)
|
||||
)
|
||||
m.submodules.table = table = memory.Memory(data=lut_data)
|
||||
table_rd = table.read_port(domain="sync")
|
||||
|
||||
# 3 MSBs of the phase word: sign, quadrant, octant.
|
||||
o, q, s = self.phase[-3:]
|
||||
rev_addr = o
|
||||
swap = Signal()
|
||||
neg_cos = Signal()
|
||||
neg_sin = Signal()
|
||||
with m.If(self.en):
|
||||
m.d.sync += [
|
||||
swap .eq(q ^ o),
|
||||
neg_cos .eq(s ^ q),
|
||||
neg_sin .eq(s),
|
||||
]
|
||||
|
||||
# Map phase to the [0,pi/4) range.
|
||||
octant_phase = Signal(addr_width)
|
||||
octant_mask = rev_addr.replicate(len(octant_phase)) # reverse mask
|
||||
m.d.comb += octant_phase.eq(octant_mask ^ self.phase[-addr_width-3:-3])
|
||||
|
||||
# Retrieve precomputed (cos, sin) values from the reduced range.
|
||||
e_s0 = Signal(IQSample(self.output_width))
|
||||
m.d.comb += [
|
||||
table_rd.addr.eq(octant_phase),
|
||||
table_rd.en .eq(self.en),
|
||||
e_s0 .eq(table_rd.data),
|
||||
]
|
||||
|
||||
# Unmap the phase to its original octant.
|
||||
e_s1 = Signal.like(e_s0)
|
||||
e_s2 = Signal.like(e_s1)
|
||||
|
||||
m.d.comb += [
|
||||
Cat(e_s1.i, e_s1.q) .eq(Mux(swap, Cat(e_s0.q, e_s0.i), e_s0)),
|
||||
e_s2.i .eq(Mux(neg_cos, -e_s1.i, e_s1.i)),
|
||||
e_s2.q .eq(Mux(neg_sin, -e_s1.q, e_s1.q)),
|
||||
]
|
||||
m.d.sync += self.output.eq(e_s2)
|
||||
|
||||
return m
|
||||
55
firmware/fpga/dsp/quarter_shift.py
Normal file
55
firmware/fpga/dsp/quarter_shift.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from amaranth import Module, Signal, Mux
|
||||
from amaranth.lib import wiring, stream
|
||||
from amaranth.lib.wiring import In, Out
|
||||
|
||||
from util import IQSample
|
||||
|
||||
class QuarterShift(wiring.Component):
|
||||
input: In(stream.Signature(IQSample(8), always_ready=True))
|
||||
output: Out(stream.Signature(IQSample(8), always_ready=True))
|
||||
enable: In(1)
|
||||
up: In(1)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
index = Signal(range(4))
|
||||
|
||||
with m.If(~self.output.valid | self.output.ready):
|
||||
if not self.input.signature.always_ready:
|
||||
m.d.comb += self.input.ready.eq(1)
|
||||
m.d.sync += self.output.valid.eq(self.input.valid)
|
||||
with m.If(self.input.valid):
|
||||
# Select direction of shift with the `up` signal.
|
||||
with m.If(self.up):
|
||||
m.d.sync += index.eq(index - 1)
|
||||
with m.Else():
|
||||
m.d.sync += index.eq(index + 1)
|
||||
|
||||
# Generate control signals derived from `index`.
|
||||
swap = index[0]
|
||||
inv_q = index[0] ^ index[1]
|
||||
inv_i = index[1]
|
||||
|
||||
# First stage: swap.
|
||||
i = Mux(swap, self.input.p.q, self.input.p.i)
|
||||
q = Mux(swap, self.input.p.i, self.input.p.q)
|
||||
|
||||
# Second stage: sign inversion.
|
||||
i = Mux(inv_i, -i, i)
|
||||
q = Mux(inv_q, -q, q)
|
||||
|
||||
with m.If(self.enable):
|
||||
m.d.sync += self.output.p.i.eq(i)
|
||||
m.d.sync += self.output.p.q.eq(q)
|
||||
with m.Else():
|
||||
m.d.sync += self.output.p.i.eq(self.input.p.i)
|
||||
m.d.sync += self.output.p.q.eq(self.input.p.q)
|
||||
|
||||
return m
|
||||
17
firmware/fpga/dsp/round.py
Normal file
17
firmware/fpga/dsp/round.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
def convergent_round(value, discarded_bits):
|
||||
retained = value[discarded_bits:]
|
||||
discarded = value[:discarded_bits]
|
||||
msb_discarded = discarded[-1]
|
||||
rest_discarded = discarded[:-1]
|
||||
lsb_retained = retained[0]
|
||||
# Round up:
|
||||
# - If discarded > 0.5
|
||||
# - If discarded == 0.5 and retained is odd
|
||||
round_up = msb_discarded & (rest_discarded.any() | lsb_retained)
|
||||
return retained + round_up
|
||||
343
firmware/fpga/dsp/sb_mac16.py
Normal file
343
firmware/fpga/dsp/sb_mac16.py
Normal file
@@ -0,0 +1,343 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from amaranth import Module, Instance, Signal, Mux, Cat
|
||||
from amaranth.lib import wiring
|
||||
from amaranth.lib.wiring import In, Out
|
||||
from amaranth.vendor import SiliconBluePlatform
|
||||
|
||||
class SB_MAC16(wiring.Component):
|
||||
|
||||
# Input ports
|
||||
CLK: In(1)
|
||||
CE: In(1)
|
||||
C: In(16)
|
||||
A: In(16)
|
||||
B: In(16)
|
||||
D: In(16)
|
||||
AHOLD: In(1)
|
||||
BHOLD: In(1)
|
||||
CHOLD: In(1)
|
||||
DHOLD: In(1)
|
||||
IRSTTOP: In(1)
|
||||
IRSTBOT: In(1)
|
||||
ORSTTOP: In(1)
|
||||
ORSTBOT: In(1)
|
||||
OLOADTOP: In(1)
|
||||
OLOADBOT: In(1)
|
||||
ADDSUBTOP: In(1)
|
||||
ADDSUBBOT: In(1)
|
||||
OHOLDTOP: In(1)
|
||||
OHOLDBOT: In(1)
|
||||
CI: In(1)
|
||||
ACCUMCI: In(1)
|
||||
SIGNEXTIN: In(1)
|
||||
|
||||
# Output ports
|
||||
O: Out(32)
|
||||
CO: Out(1)
|
||||
ACCUMCO: Out(1)
|
||||
SIGNEXTOUT: Out(1)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
NEG_TRIGGER=0,
|
||||
C_REG=0,
|
||||
A_REG=0,
|
||||
B_REG=0,
|
||||
D_REG=0,
|
||||
TOP_8x8_MULT_REG=0,
|
||||
BOT_8x8_MULT_REG=0,
|
||||
PIPELINE_16x16_MULT_REG1=0,
|
||||
PIPELINE_16x16_MULT_REG2=0,
|
||||
TOPOUTPUT_SELECT=0,
|
||||
TOPADDSUB_LOWERINPUT=0,
|
||||
TOPADDSUB_UPPERINPUT=0,
|
||||
TOPADDSUB_CARRYSELECT=0,
|
||||
BOTOUTPUT_SELECT=0,
|
||||
BOTADDSUB_LOWERINPUT=0,
|
||||
BOTADDSUB_UPPERINPUT=0,
|
||||
BOTADDSUB_CARRYSELECT=0,
|
||||
MODE_8x8=0,
|
||||
A_SIGNED=0,
|
||||
B_SIGNED=0):
|
||||
|
||||
super().__init__()
|
||||
|
||||
# Parameters
|
||||
self.parameters = dict(
|
||||
NEG_TRIGGER=NEG_TRIGGER,
|
||||
C_REG=C_REG,
|
||||
A_REG=A_REG,
|
||||
B_REG=B_REG,
|
||||
D_REG=D_REG,
|
||||
TOP_8x8_MULT_REG=TOP_8x8_MULT_REG,
|
||||
BOT_8x8_MULT_REG=BOT_8x8_MULT_REG,
|
||||
PIPELINE_16x16_MULT_REG1=PIPELINE_16x16_MULT_REG1,
|
||||
PIPELINE_16x16_MULT_REG2=PIPELINE_16x16_MULT_REG2,
|
||||
TOPOUTPUT_SELECT=TOPOUTPUT_SELECT,
|
||||
TOPADDSUB_LOWERINPUT=TOPADDSUB_LOWERINPUT,
|
||||
TOPADDSUB_UPPERINPUT=TOPADDSUB_UPPERINPUT,
|
||||
TOPADDSUB_CARRYSELECT=TOPADDSUB_CARRYSELECT,
|
||||
BOTOUTPUT_SELECT=BOTOUTPUT_SELECT,
|
||||
BOTADDSUB_LOWERINPUT=BOTADDSUB_LOWERINPUT,
|
||||
BOTADDSUB_UPPERINPUT=BOTADDSUB_UPPERINPUT,
|
||||
BOTADDSUB_CARRYSELECT=BOTADDSUB_CARRYSELECT,
|
||||
MODE_8x8=MODE_8x8,
|
||||
A_SIGNED=A_SIGNED,
|
||||
B_SIGNED=B_SIGNED,
|
||||
)
|
||||
|
||||
|
||||
def elaborate(self, platform):
|
||||
if isinstance(platform, SiliconBluePlatform):
|
||||
return self.elaborate_hard_macro()
|
||||
else:
|
||||
return self.elaborate_simulation()
|
||||
|
||||
|
||||
def elaborate_hard_macro(self):
|
||||
m = Module()
|
||||
m.submodules.sb_mac16 = Instance("SB_MAC16",
|
||||
# Parameters.
|
||||
**{ f"p_{k}": v for k, v in self.parameters.items() },
|
||||
|
||||
# Inputs.
|
||||
i_CLK=self.CLK,
|
||||
i_CE=self.CE,
|
||||
i_C=self.C,
|
||||
i_A=self.A,
|
||||
i_B=self.B,
|
||||
i_D=self.D,
|
||||
i_IRSTTOP=self.IRSTTOP,
|
||||
i_IRSTBOT=self.IRSTBOT,
|
||||
i_ORSTTOP=self.ORSTTOP,
|
||||
i_ORSTBOT=self.ORSTBOT,
|
||||
i_AHOLD=self.AHOLD,
|
||||
i_BHOLD=self.BHOLD,
|
||||
i_CHOLD=self.CHOLD,
|
||||
i_DHOLD=self.DHOLD,
|
||||
i_OHOLDTOP=self.OHOLDTOP,
|
||||
i_OHOLDBOT=self.OHOLDBOT,
|
||||
i_ADDSUBTOP=self.ADDSUBTOP,
|
||||
i_ADDSUBBOT=self.ADDSUBBOT,
|
||||
i_OLOADTOP=self.OLOADTOP,
|
||||
i_OLOADBOT=self.OLOADBOT,
|
||||
i_CI=self.CI,
|
||||
i_ACCUMCI=self.ACCUMCI,
|
||||
i_SIGNEXTIN=self.SIGNEXTIN,
|
||||
|
||||
# Outputs.
|
||||
o_O=self.O,
|
||||
o_CO=self.CO,
|
||||
o_ACCUMCO=self.ACCUMCO,
|
||||
o_SIGNEXTOUT=self.SIGNEXTOUT,
|
||||
)
|
||||
return m
|
||||
|
||||
|
||||
def elaborate_simulation(self):
|
||||
m = Module()
|
||||
|
||||
p = self.parameters
|
||||
|
||||
assert p["NEG_TRIGGER"] == 0, "Falling edge input clock polarity not supported in simulation."
|
||||
|
||||
# Internal wire, compare Figure on page 133 of ICE Technology Library 3.0 and Fig 2 on page 2 of Lattice TN1295-DSP
|
||||
# http://www.latticesemi.com/~/media/LatticeSemi/Documents/TechnicalBriefs/SBTICETechnologyLibrary201608.pdf
|
||||
# https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/AD/DSPFunctionUsageGuideforICE40Devices.ashx
|
||||
iA = Signal(16)
|
||||
iB = Signal(16)
|
||||
iC = Signal(16)
|
||||
iD = Signal(16)
|
||||
iF = Signal(16)
|
||||
iJ = Signal(16)
|
||||
iK = Signal(16)
|
||||
iG = Signal(16)
|
||||
iL = Signal(32)
|
||||
iH = Signal(32)
|
||||
iW = Signal(16)
|
||||
iX = Signal(16)
|
||||
iP = Signal(16)
|
||||
iQ = Signal(16)
|
||||
iY = Signal(16)
|
||||
iZ = Signal(16)
|
||||
iR = Signal(16)
|
||||
iS = Signal(16)
|
||||
HCI = Signal()
|
||||
LCI = Signal()
|
||||
LCO = Signal()
|
||||
|
||||
# Registers
|
||||
rC = Signal(16)
|
||||
rA = Signal(16)
|
||||
rB = Signal(16)
|
||||
rD = Signal(16)
|
||||
rF = Signal(16)
|
||||
rJ = Signal(16)
|
||||
rK = Signal(16)
|
||||
rG = Signal(16)
|
||||
rH = Signal(32)
|
||||
rQ = Signal(16)
|
||||
rS = Signal(16)
|
||||
|
||||
# Regs C and A
|
||||
with m.If(self.IRSTTOP):
|
||||
m.d.sync += [
|
||||
rC.eq(0),
|
||||
rA.eq(0),
|
||||
]
|
||||
with m.Elif(self.CE):
|
||||
with m.If(~self.CHOLD):
|
||||
m.d.sync += rC.eq(self.C)
|
||||
with m.If(~self.AHOLD):
|
||||
m.d.sync += rA.eq(self.A)
|
||||
|
||||
m.d.comb += [
|
||||
iC.eq(rC if p["C_REG"] else self.C),
|
||||
iA.eq(rA if p["A_REG"] else self.A),
|
||||
]
|
||||
|
||||
# Regs B and D
|
||||
with m.If(self.IRSTBOT):
|
||||
m.d.sync += [
|
||||
rB.eq(0),
|
||||
rD.eq(0)
|
||||
]
|
||||
with m.Elif(self.CE):
|
||||
with m.If(~self.BHOLD):
|
||||
m.d.sync += rB.eq(self.B)
|
||||
with m.If(~self.DHOLD):
|
||||
m.d.sync += rD.eq(self.D)
|
||||
|
||||
m.d.comb += [
|
||||
iB.eq(rB if p["B_REG"] else self.B),
|
||||
iD.eq(rD if p["D_REG"] else self.D),
|
||||
]
|
||||
|
||||
# Multiplier Stage
|
||||
p_Ah_Bh = Signal(16)
|
||||
p_Al_Bh = Signal(16)
|
||||
p_Ah_Bl = Signal(16)
|
||||
p_Al_Bl = Signal(16)
|
||||
Ah = Signal(16)
|
||||
Al = Signal(16)
|
||||
Bh = Signal(16)
|
||||
Bl = Signal(16)
|
||||
|
||||
m.d.comb += [
|
||||
Ah.eq(Cat(iA[8:16], Mux(p["A_SIGNED"], iA[15].replicate(8), 0))),
|
||||
Al.eq(Cat(iA[0:8], Mux(p["A_SIGNED"] & p["MODE_8x8"], iA[7].replicate(8), 0))),
|
||||
Bh.eq(Cat(iB[8:16], Mux(p["B_SIGNED"], iB[15].replicate(8), 0))),
|
||||
Bl.eq(Cat(iB[0:8], Mux(p["B_SIGNED"] & p["MODE_8x8"], iB[7].replicate(8), 0))),
|
||||
p_Ah_Bh.eq(Ah * Bh), # F
|
||||
p_Al_Bh.eq(Al[0:8] * Bh), # J
|
||||
p_Ah_Bl.eq(Ah * Bl[0:8]), # K
|
||||
p_Al_Bl.eq(Al * Bl), # G
|
||||
]
|
||||
|
||||
# Regs F and J
|
||||
with m.If(self.IRSTTOP):
|
||||
m.d.sync += [
|
||||
rF.eq(0),
|
||||
rJ.eq(0)
|
||||
]
|
||||
with m.Elif(self.CE):
|
||||
m.d.sync += rF.eq(p_Ah_Bh)
|
||||
if not p["MODE_8x8"]:
|
||||
m.d.sync += rJ.eq(p_Al_Bh)
|
||||
|
||||
m.d.comb += [
|
||||
iF.eq(rF if p["TOP_8x8_MULT_REG"] else p_Ah_Bh),
|
||||
iJ.eq(rJ if p["PIPELINE_16x16_MULT_REG1"] else p_Al_Bh),
|
||||
]
|
||||
|
||||
# Regs K and G
|
||||
with m.If(self.IRSTBOT):
|
||||
m.d.sync += [
|
||||
rK.eq(0),
|
||||
rG.eq(0)
|
||||
]
|
||||
with m.Elif(self.CE):
|
||||
with m.If(~p["MODE_8x8"]):
|
||||
m.d.sync += rK.eq(p_Ah_Bl)
|
||||
m.d.sync += rG.eq(p_Al_Bl)
|
||||
|
||||
m.d.comb += [
|
||||
iK.eq(rK if p["PIPELINE_16x16_MULT_REG1"] else p_Ah_Bl),
|
||||
iG.eq(rG if p["BOT_8x8_MULT_REG"] else p_Al_Bl),
|
||||
]
|
||||
|
||||
# Adder Stage
|
||||
iK_e = Signal(24)
|
||||
iJ_e = Signal(24)
|
||||
m.d.comb += [
|
||||
iK_e.eq(Cat(iK, Mux(p["A_SIGNED"], iK[15].replicate(8), 0))),
|
||||
iJ_e.eq(Cat(iJ, Mux(p["B_SIGNED"], iJ[15].replicate(8), 0))),
|
||||
iL.eq(iG + (iK_e << 8) + (iJ_e << 8) + (iF << 16)),
|
||||
]
|
||||
|
||||
# Reg H
|
||||
with m.If(self.IRSTBOT):
|
||||
m.d.sync += rH.eq(0)
|
||||
with m.Elif(self.CE):
|
||||
if not p["MODE_8x8"]:
|
||||
m.d.sync += rH.eq(iL)
|
||||
|
||||
m.d.comb += iH.eq(rH if p["PIPELINE_16x16_MULT_REG2"] else iL)
|
||||
|
||||
# Hi Output Stage
|
||||
XW = Signal(17)
|
||||
Oh = Signal(16)
|
||||
|
||||
m.d.comb += [
|
||||
iW.eq([iQ, iC][p["TOPADDSUB_UPPERINPUT"]]),
|
||||
iX.eq([iA, iF, iH[16:32], iZ[15].replicate(16)][p["TOPADDSUB_LOWERINPUT"]]),
|
||||
XW.eq(iX + (iW ^ self.ADDSUBTOP.replicate(16)) + HCI),
|
||||
self.ACCUMCO.eq(XW[16]),
|
||||
self.CO.eq(self.ACCUMCO ^ self.ADDSUBTOP),
|
||||
iP.eq(Mux(self.OLOADTOP, iC, XW[0:16] ^ self.ADDSUBTOP.replicate(16))),
|
||||
]
|
||||
|
||||
with m.If(self.ORSTTOP):
|
||||
m.d.sync += rQ.eq(0)
|
||||
with m.Elif(self.CE):
|
||||
with m.If(~self.OHOLDTOP):
|
||||
m.d.sync += rQ.eq(iP)
|
||||
|
||||
m.d.comb += [
|
||||
iQ.eq(rQ),
|
||||
Oh.eq([iP, iQ, iF, iH[16:32]][p["TOPOUTPUT_SELECT"]]),
|
||||
HCI.eq([0, 1, LCO, LCO ^ self.ADDSUBBOT][p["TOPADDSUB_CARRYSELECT"]]),
|
||||
self.SIGNEXTOUT.eq(iX[15]),
|
||||
]
|
||||
|
||||
# Lo Output Stage
|
||||
YZ = Signal(17)
|
||||
Ol = Signal(16)
|
||||
|
||||
m.d.comb += [
|
||||
iY.eq([iS, iD][p["BOTADDSUB_UPPERINPUT"]]),
|
||||
iZ.eq([iB, iG, iH[0:16], self.SIGNEXTIN.replicate(16)][p["BOTADDSUB_LOWERINPUT"]]),
|
||||
YZ.eq(iZ + (iY ^ self.ADDSUBBOT.replicate(16)) + LCI),
|
||||
LCO.eq(YZ[16]),
|
||||
iR.eq(Mux(self.OLOADBOT, iD, YZ[0:16] ^ self.ADDSUBBOT.replicate(16))),
|
||||
]
|
||||
|
||||
with m.If(self.ORSTBOT):
|
||||
m.d.sync += rS.eq(0)
|
||||
with m.Elif(self.CE):
|
||||
with m.If(~self.OHOLDBOT):
|
||||
m.d.sync += rS.eq(iR)
|
||||
|
||||
m.d.comb += [
|
||||
iS.eq(rS),
|
||||
Ol.eq([iR, iS, iG, iH[0:16]][p["BOTOUTPUT_SELECT"]]),
|
||||
LCI.eq([0, 1, self.ACCUMCI, self.CI][p["BOTADDSUB_CARRYSELECT"]]),
|
||||
self.O.eq(Cat(Ol, Oh)),
|
||||
]
|
||||
|
||||
return m
|
||||
1
firmware/fpga/interface/__init__.py
Normal file
1
firmware/fpga/interface/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .max586x import MAX586xInterface
|
||||
66
firmware/fpga/interface/max586x.py
Normal file
66
firmware/fpga/interface/max586x.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from amaranth import Module, Signal, C, Cat
|
||||
from amaranth.lib import io, stream, wiring
|
||||
from amaranth.lib.wiring import Out, In
|
||||
|
||||
from util import IQSample
|
||||
|
||||
class MAX586xInterface(wiring.Component):
|
||||
adc_stream: Out(stream.Signature(IQSample(8), always_ready=True))
|
||||
dac_stream: In(stream.Signature(IQSample(8), always_ready=True))
|
||||
|
||||
adc_capture: In(1)
|
||||
dac_capture: In(1)
|
||||
q_invert: In(1)
|
||||
|
||||
def __init__(self, bb_domain):
|
||||
super().__init__()
|
||||
self._bb_domain = bb_domain
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
adc_stream = self.adc_stream
|
||||
dac_stream = self.dac_stream
|
||||
|
||||
# Generate masks for inverting the Q component based on the q_invert signal.
|
||||
q_invert = Signal()
|
||||
rx_q_mask = Signal(8)
|
||||
tx_q_mask = Signal(10)
|
||||
m.d[self._bb_domain] += q_invert.eq(self.q_invert)
|
||||
with m.If(q_invert):
|
||||
m.d.comb += [
|
||||
rx_q_mask.eq(0x80),
|
||||
tx_q_mask.eq(0x1FF),
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += [
|
||||
rx_q_mask.eq(0x7F),
|
||||
tx_q_mask.eq(0x200),
|
||||
]
|
||||
|
||||
# Capture the ADC signals using a DDR input buffer.
|
||||
m.submodules.adc_in = adc_in = io.DDRBuffer("i", platform.request("da", dir="-"), i_domain=self._bb_domain)
|
||||
m.d.comb += [
|
||||
adc_stream.p.i .eq(adc_in.i[0] ^ 0x80), # I: non-inverted between MAX2837 and MAX5864.
|
||||
adc_stream.p.q .eq(adc_in.i[1] ^ rx_q_mask), # Q: inverted between MAX2837 and MAX5864.
|
||||
adc_stream.valid .eq(self.adc_capture),
|
||||
]
|
||||
|
||||
# Output the transformed data to the DAC using a DDR output buffer.
|
||||
m.submodules.dac_out = dac_out = io.DDRBuffer("o", platform.request("dd", dir="-"), o_domain=self._bb_domain)
|
||||
with m.If(dac_stream.valid):
|
||||
m.d.comb += [
|
||||
dac_out.o[0] .eq(Cat(C(0, 2), dac_stream.p.i) ^ 0x200),
|
||||
dac_out.o[1] .eq(Cat(C(0, 2), dac_stream.p.q) ^ tx_q_mask),
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += [
|
||||
dac_out.o[0] .eq(0x200),
|
||||
dac_out.o[1] .eq(0x200),
|
||||
]
|
||||
|
||||
return m
|
||||
424
firmware/fpga/interface/spi.py
Normal file
424
firmware/fpga/interface/spi.py
Normal file
@@ -0,0 +1,424 @@
|
||||
#
|
||||
# This file is part of HackRF.
|
||||
#
|
||||
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from amaranth import Elaboratable, Module, Instance, Signal, ClockSignal
|
||||
|
||||
# References:
|
||||
# [1] LATTICE ICE™ Technology Library, Version 3.0, August, 2016
|
||||
# [2] iCE40™ LP/HX/LM Family Handbook, HB1011 Version 01.2, November 2013
|
||||
|
||||
class SPIDeviceInterface(Elaboratable):
|
||||
|
||||
def __init__(self, port):
|
||||
# I/O port.
|
||||
self.port = port
|
||||
|
||||
# Data I/O.
|
||||
self.word_in = Signal(8)
|
||||
self.word_out = Signal(8)
|
||||
self.word_in_stb = Signal()
|
||||
|
||||
# Status flags.
|
||||
self.busy = Signal()
|
||||
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
spi_adr = Signal(8, init=0b1000) # address
|
||||
spi_dati = Signal(8) # data input
|
||||
spi_dato = Signal(8) # data output
|
||||
spi_rw = Signal() # selects between read or write (high = write)
|
||||
spi_stb = Signal() # strobe must be asserted to start a read/write
|
||||
spi_ack = Signal() # ack that the transfer is done (read valid, write ack)
|
||||
|
||||
# SB_SPI interface is documented in [1].
|
||||
sb_spi_params = {
|
||||
# SPI port connections.
|
||||
"o_SO": self.port.cipo.o,
|
||||
"o_SOE": self.port.cipo.oe,
|
||||
"i_SI": self.port.copi.i,
|
||||
"i_SCKI": self.port.clk.i,
|
||||
"i_SCSNI": ~self.port.cs.i, # chip select is inverted due to PinsN
|
||||
# Internal signaling.
|
||||
"i_SBCLKI": ClockSignal("sync"),
|
||||
"i_SBSTBI": spi_stb,
|
||||
"i_SBRWI": spi_rw,
|
||||
"o_SBACKO": spi_ack,
|
||||
}
|
||||
sb_spi_params |= { f"i_SBADRI{i}": spi_adr[i] for i in range(8) }
|
||||
sb_spi_params |= { f"i_SBDATI{i}": spi_dati[i] for i in range(8) }
|
||||
sb_spi_params |= { f"o_SBDATO{i}": spi_dato[i] for i in range(8) }
|
||||
|
||||
m.submodules.sb_spi = sb_spi = Instance("SB_SPI", **sb_spi_params)
|
||||
|
||||
# Register addresses (from [2]).
|
||||
SPI_ADDR_SPICR0 = 0b1000 # SPI Control Register 0
|
||||
SPI_ADDR_SPICR1 = 0b1001 # SPI Control Register 1
|
||||
SPI_ADDR_SPICR2 = 0b1010 # SPI Control Register 2
|
||||
SPI_ADDR_SPIBR = 0b1011 # SPI Clock Prescale
|
||||
SPI_ADDR_SPISR = 0b1100 # SPI Status Register
|
||||
SPI_ADDR_SPITXDR = 0b1101 # SPI Transmit Data Register
|
||||
SPI_ADDR_SPIRXDR = 0b1110 # SPI Receive Data Register
|
||||
SPI_ADDR_SPICSR = 0b1111 # SPI Master Chip Select Register
|
||||
|
||||
# Initial values for programming registers ([2]).
|
||||
registers_init = {
|
||||
SPI_ADDR_SPICR2: 0b00000110, # CPOL=1 CPHA=1 mode, MSB first
|
||||
SPI_ADDR_SPICR1: 0b10000000, # Enable SPI
|
||||
}
|
||||
|
||||
# De-assert strobe signals unless explicitly asserted.
|
||||
m.d.sync += spi_stb.eq(0)
|
||||
m.d.sync += self.word_in_stb.eq(0)
|
||||
|
||||
with m.FSM():
|
||||
|
||||
# Register initialization.
|
||||
for i, (address, value) in enumerate(registers_init.items()):
|
||||
with m.State(f"INIT{i}"):
|
||||
m.d.sync += [
|
||||
spi_adr .eq(address),
|
||||
spi_dati .eq(value),
|
||||
spi_stb .eq(1),
|
||||
spi_rw .eq(1),
|
||||
]
|
||||
with m.If(spi_ack):
|
||||
m.d.sync += spi_stb.eq(0)
|
||||
if i+1 < len(registers_init):
|
||||
m.next = f"INIT{i+1}"
|
||||
else:
|
||||
m.next = "WAIT"
|
||||
|
||||
with m.State("WAIT"):
|
||||
m.d.sync += [
|
||||
spi_adr .eq(SPI_ADDR_SPISR),
|
||||
spi_stb .eq(1),
|
||||
spi_rw .eq(0),
|
||||
]
|
||||
with m.If(spi_ack):
|
||||
m.d.sync += spi_stb.eq(0)
|
||||
# bit 3 = RRDY, data is available to read
|
||||
# bit 4 = TRDY, transmit data is empty
|
||||
# bit 6 = BUSY, chip select is asserted (low)
|
||||
# bit 7 = TIP, transfer in progress
|
||||
m.d.sync += self.busy.eq(spi_dato[6])
|
||||
with m.If(spi_dato[7] & spi_dato[4]):
|
||||
m.next = "SPI_TRANSMIT"
|
||||
with m.Elif(spi_dato[3]):
|
||||
m.next = "SPI_READ"
|
||||
|
||||
with m.State("SPI_READ"):
|
||||
m.d.sync += [
|
||||
spi_adr .eq(SPI_ADDR_SPIRXDR),
|
||||
spi_stb .eq(1),
|
||||
spi_rw .eq(0),
|
||||
]
|
||||
with m.If(spi_ack):
|
||||
m.d.sync += [
|
||||
spi_stb .eq(0),
|
||||
self.word_in .eq(spi_dato),
|
||||
self.word_in_stb .eq(1),
|
||||
]
|
||||
m.next = "WAIT"
|
||||
|
||||
with m.State("SPI_TRANSMIT"):
|
||||
m.d.sync += [
|
||||
spi_adr .eq(SPI_ADDR_SPITXDR),
|
||||
spi_dati .eq(self.word_out),
|
||||
spi_stb .eq(1),
|
||||
spi_rw .eq(1),
|
||||
]
|
||||
with m.If(spi_ack):
|
||||
m.d.sync += spi_stb.eq(0)
|
||||
m.next = "WAIT"
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class SPICommandInterface(Elaboratable):
|
||||
""" Wrapper of SPIDeviceInterface that splits data sequences into phases.
|
||||
|
||||
I/O signals:
|
||||
O: command -- the command read from the SPI bus
|
||||
O: command_ready -- a new command is ready
|
||||
|
||||
O: word_received -- the most recent word received
|
||||
O: word_complete -- strobe indicating a new word is present on word_in
|
||||
I: word_to_send -- the word to be loaded; latched in on next word_complete and while cs is low
|
||||
"""
|
||||
|
||||
def __init__(self, port):
|
||||
|
||||
# I/O port.
|
||||
self.interface = SPIDeviceInterface(port)
|
||||
|
||||
# Command I/O.
|
||||
self.command = Signal(8)
|
||||
self.command_ready = Signal()
|
||||
|
||||
# Data I/O
|
||||
self.word_received = Signal(8)
|
||||
self.word_to_send = Signal.like(self.word_received)
|
||||
self.word_complete = Signal()
|
||||
|
||||
|
||||
def elaborate(self, platform):
|
||||
|
||||
m = Module()
|
||||
|
||||
# Attach our SPI interface.
|
||||
m.submodules.interface = interface = self.interface
|
||||
|
||||
# De-assert our control signals unless explicitly asserted.
|
||||
m.d.sync += [
|
||||
self.command_ready.eq(0),
|
||||
self.word_complete.eq(0)
|
||||
]
|
||||
|
||||
m.d.comb += interface.word_out.eq(self.word_to_send)
|
||||
|
||||
with m.FSM():
|
||||
|
||||
with m.State("COMMAND_PHASE"):
|
||||
with m.If(interface.word_in_stb):
|
||||
m.d.sync += [
|
||||
self.command .eq(interface.word_in),
|
||||
self.command_ready .eq(1),
|
||||
]
|
||||
m.next = "DATA_PHASE"
|
||||
|
||||
# Do not advance if chip select is deasserted.
|
||||
with m.If(~interface.busy):
|
||||
m.next = "COMMAND_PHASE"
|
||||
|
||||
with m.State("DATA_PHASE"):
|
||||
with m.If(interface.word_in_stb):
|
||||
m.d.sync += self.word_received.eq(interface.word_in)
|
||||
m.d.sync += self.word_complete.eq(1)
|
||||
m.next = "DUMMY_PHASE"
|
||||
|
||||
# Do not advance if chip select is deasserted.
|
||||
with m.If(~interface.busy):
|
||||
m.next = "COMMAND_PHASE"
|
||||
|
||||
# The SB_SPI block always returns 0xFF for the second byte, so at least one
|
||||
# dummy byte must be added to retrieve valid data. This behavior is shown in
|
||||
# Figure 22-16, "Minimally Specified SPI Transaction Example," from [2].
|
||||
with m.State("DUMMY_PHASE"):
|
||||
with m.If(~interface.busy):
|
||||
m.next = "COMMAND_PHASE"
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class SPIRegisterInterface(Elaboratable):
|
||||
""" SPI device interface that allows for register reads and writes via SPI.
|
||||
The SPI transaction format matches:
|
||||
|
||||
in: WAAAAAAA[...] VVVVVVVV[...] DDDDDDDD[...]
|
||||
out: XXXXXXXX[...] XXXXXXXX[...] RRRRRRRR[...]
|
||||
|
||||
Where:
|
||||
W = write bit; a '1' indicates that the provided value is a write request
|
||||
A = all bits of the address
|
||||
V = value to be written into the register, if W is set
|
||||
R = value to be read from the register
|
||||
|
||||
Other I/O ports are added dynamically with add_register().
|
||||
"""
|
||||
|
||||
def __init__(self, port):
|
||||
"""
|
||||
Parameters:
|
||||
address_size -- the size of an address, in bits; recommended to be one bit
|
||||
less than a binary number, as the write command is formed by adding a one-bit
|
||||
write flag to the start of every address
|
||||
register_size -- The size of any given register, in bits.
|
||||
"""
|
||||
|
||||
self.address_size = 7
|
||||
self.register_size = 8
|
||||
|
||||
#
|
||||
# Internal details.
|
||||
#
|
||||
|
||||
# Instantiate an SPI command transciever submodule.
|
||||
self.interface = SPICommandInterface(port)
|
||||
|
||||
# Create a new, empty dictionary mapping registers to their signals.
|
||||
self.registers = {}
|
||||
|
||||
# Create signals for each of our register control signals.
|
||||
self._is_write = Signal()
|
||||
self._address = Signal(self.address_size)
|
||||
|
||||
|
||||
def _ensure_register_is_unused(self, address):
|
||||
""" Checks to make sure a register address isn't in use before issuing it. """
|
||||
|
||||
if address in self.registers:
|
||||
raise ValueError("can't add more than one register with address 0x{:x}!".format(address))
|
||||
|
||||
|
||||
def add_sfr(self, address, *, read=None, write_signal=None, write_strobe=None, read_strobe=None):
|
||||
""" Adds a special function register to the given command interface.
|
||||
|
||||
Parameters:
|
||||
address -- the register's address, as a big-endian integer
|
||||
read -- a Signal or integer constant representing the
|
||||
value to be read at the given address; if not provided, the default
|
||||
value will be read
|
||||
read_strobe -- a Signal that is asserted when a read is completed; if not provided,
|
||||
the relevant strobe will be left unconnected
|
||||
write_signal -- a Signal set to the value to be written when a write is requested;
|
||||
if not provided, writes will be ignored
|
||||
write_strobe -- a Signal that goes high when a value is available for a write request
|
||||
"""
|
||||
|
||||
assert address < (2 ** self.address_size)
|
||||
self._ensure_register_is_unused(address)
|
||||
|
||||
# Add the register to our collection.
|
||||
self.registers[address] = {
|
||||
'read': read,
|
||||
'write_signal': write_signal,
|
||||
'write_strobe': write_strobe,
|
||||
'read_strobe': read_strobe,
|
||||
'elaborate': None,
|
||||
}
|
||||
|
||||
|
||||
def add_read_only_register(self, address, *, read, read_strobe=None):
|
||||
""" Adds a read-only register.
|
||||
|
||||
Parameters:
|
||||
address -- the register's address, as a big-endian integer
|
||||
read -- a Signal or integer constant representing the
|
||||
value to be read at the given address; if not provided, the default
|
||||
value will be read
|
||||
read_strobe -- a Signal that is asserted when a read is completed; if not provided,
|
||||
the relevant strobe will be left unconnected
|
||||
"""
|
||||
self.add_sfr(address, read=read, read_strobe=read_strobe)
|
||||
|
||||
|
||||
|
||||
def add_register(self, address, *, value_signal=None, size=None, name=None, read_strobe=None,
|
||||
write_strobe=None, init=0):
|
||||
""" Adds a standard, memory-backed register.
|
||||
|
||||
Parameters:
|
||||
address -- the register's address, as a big-endian integer
|
||||
value_signal -- the signal that will store the register's value; if omitted
|
||||
a storage register will be created automatically
|
||||
size -- if value_signal isn't provided, this sets the size of the created register
|
||||
init -- if value_signal isn't provided, this sets the reset value of the created register
|
||||
read_strobe -- a Signal to be asserted when the register is read; ignored if not provided
|
||||
write_strobe -- a Signal to be asserted when the register is written; ignored if not provided
|
||||
|
||||
Returns:
|
||||
value_signal -- a signal that stores the register's value; which may be the value_signal arg,
|
||||
or may be a signal created during execution
|
||||
"""
|
||||
self._ensure_register_is_unused(address)
|
||||
|
||||
# Generate a name for the register, if we don't already have one.
|
||||
name = name if name else "register_{:x}".format(address)
|
||||
|
||||
# Generate a backing store for the register, if we don't already have one.
|
||||
if value_signal is None:
|
||||
size = self.register_size if (size is None) else size
|
||||
value_signal = Signal(size, name=name, init=init)
|
||||
|
||||
# If we don't have a write strobe signal, create an internal one.
|
||||
if write_strobe is None:
|
||||
write_strobe = Signal(name=name + "_write_strobe")
|
||||
|
||||
# Create our register-value-input and our write strobe.
|
||||
write_value = Signal.like(value_signal, name=name + "_write_value")
|
||||
|
||||
# Create a generator for a the fragments that will manage the register's memory.
|
||||
def _elaborate_memory_register(m):
|
||||
with m.If(write_strobe):
|
||||
m.d.sync += value_signal.eq(write_value)
|
||||
|
||||
# Add the register to our collection.
|
||||
self.registers[address] = {
|
||||
'read': value_signal,
|
||||
'write_signal': write_value,
|
||||
'write_strobe': write_strobe,
|
||||
'read_strobe': read_strobe,
|
||||
'elaborate': _elaborate_memory_register,
|
||||
}
|
||||
|
||||
return value_signal
|
||||
|
||||
|
||||
def _elaborate_register(self, m, register_address, connections):
|
||||
""" Generates the hardware connections that handle a given register. """
|
||||
|
||||
#
|
||||
# Elaborate our register hardware.
|
||||
#
|
||||
|
||||
# Create a signal that goes high iff the given register is selected.
|
||||
register_selected = Signal(name="register_address_matches_{:x}".format(register_address))
|
||||
m.d.comb += register_selected.eq(self._address == register_address)
|
||||
|
||||
# Our write signal is always connected to word_received; but it's only meaningful
|
||||
# when write_strobe is asserted.
|
||||
if connections['write_signal'] is not None:
|
||||
m.d.comb += connections['write_signal'].eq(self.interface.word_received)
|
||||
|
||||
# If we have a write strobe, assert it iff:
|
||||
# - this register is selected
|
||||
# - the relevant command is a write command
|
||||
# - we've just finished receiving the command's argument
|
||||
if connections['write_strobe'] is not None:
|
||||
m.d.comb += [
|
||||
connections['write_strobe'].eq(self._is_write & self.interface.word_complete & register_selected)
|
||||
]
|
||||
|
||||
# Create essentially the same connection with the read strobe.
|
||||
if connections['read_strobe'] is not None:
|
||||
m.d.comb += [
|
||||
connections['read_strobe'].eq(~self._is_write & self.interface.word_complete & register_selected)
|
||||
]
|
||||
|
||||
# If we have any additional code that assists in elaborating this register, run it.
|
||||
if connections['elaborate']:
|
||||
connections['elaborate'](m)
|
||||
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Attach our SPI interface.
|
||||
m.submodules.interface = self.interface
|
||||
|
||||
# Split the command into our "write" and "address" signals.
|
||||
m.d.comb += [
|
||||
self._is_write.eq(self.interface.command[-1]),
|
||||
self._address .eq(self.interface.command[:-1])
|
||||
]
|
||||
|
||||
# Create the control/write logic for each of our registers.
|
||||
for address, connections in self.registers.items():
|
||||
self._elaborate_register(m, address, connections)
|
||||
|
||||
# Build the logic to select the 'to_send' value, which is selected
|
||||
# from all of our registers according to the selected register address.
|
||||
with m.Switch(self._address):
|
||||
for address, connections in self.registers.items():
|
||||
if connections['read'] is not None:
|
||||
with m.Case(address):
|
||||
# Hook up the word-to-send signal to the read value for the relevant
|
||||
# register.
|
||||
m.d.comb += self.interface.word_to_send.eq(connections['read'])
|
||||
|
||||
return m
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user