init
This commit is contained in:
3
lib_sw_pll/.xmos_ignore_source_check
Normal file
3
lib_sw_pll/.xmos_ignore_source_check
Normal file
@@ -0,0 +1,3 @@
|
||||
python/sw_pll/pll_calc.py
|
||||
register_setup.h
|
||||
fractions.h
|
||||
62
lib_sw_pll/CHANGELOG.rst
Normal file
62
lib_sw_pll/CHANGELOG.rst
Normal file
@@ -0,0 +1,62 @@
|
||||
lib_sw_pll change log
|
||||
=====================
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* FIXED: Enable PLL output after delay to allow it to settle
|
||||
* FIXED: Fixed frequency settings for 11,289,600Hz
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* ADDED: Support for XCommon CMake build system
|
||||
* ADDED: Reset PI controller state API
|
||||
* ADDED: Fixed frequency (non phase-locked) clock PLL API
|
||||
* FIXED: Init resets PI controller state
|
||||
* FIXED: Now compiles from XC using XCommon
|
||||
* ADDED: Guard source code with __XS3A__ to allow library inclusion in non-
|
||||
xcore-ai projects
|
||||
* CHANGED: Reduce PLL initialisation stabilisation delay from 10 ms to 500 us
|
||||
* ADDED: Split SDM init function to allow separation across tiles
|
||||
* FIXED: Use non-ACK write to PLL in Sigma Delta Modulator
|
||||
|
||||
2.0.0
|
||||
-----
|
||||
|
||||
* ADDED: Double integral term to controller
|
||||
* ADDED: Sigma Delta Modulator option for PLL
|
||||
* CHANGED: Refactored Python model into analogous objects
|
||||
|
||||
1.1.0
|
||||
-----
|
||||
|
||||
* ADDED: Function to reset the constants and PI controller state at runtime
|
||||
* CHANGED: Framework repositories used by the examples
|
||||
|
||||
1.0.0
|
||||
-----
|
||||
|
||||
* ADDED: Low-level error input API
|
||||
* FIXED: Divide by zero exception when not using ref clk compensation
|
||||
|
||||
0.3.0
|
||||
-----
|
||||
|
||||
* ADDED: Documentation
|
||||
* ADDED: Simulator can now generate a modulated test tone to measure jitter
|
||||
* CHANGED: Updated tools version to 15.2.1
|
||||
|
||||
0.2.0
|
||||
-----
|
||||
|
||||
* REMOVED: support for Kii term (speed optimisation)
|
||||
* CHANGED: used pre-calculated divide to improve cycle usage
|
||||
* CHANGED: use of const in API
|
||||
* FIXED: possible overflow where mclk_inc * refclk_inc is > 32b
|
||||
|
||||
0.1.0
|
||||
-----
|
||||
|
||||
* ADDED: initial version
|
||||
|
||||
27
lib_sw_pll/CMakeLists.txt
Normal file
27
lib_sw_pll/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
## Disable in-source build.
|
||||
if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
message(FATAL_ERROR "In-source build is not allowed! Please specify a build folder.\n\tex:cmake -B build")
|
||||
endif()
|
||||
|
||||
## Project declaration
|
||||
project(lib_sw_pll)
|
||||
|
||||
## Enable languages for project
|
||||
enable_language(CXX C ASM)
|
||||
|
||||
## Add library subdirectories
|
||||
add_subdirectory(lib_sw_pll)
|
||||
|
||||
## Add top level project targets
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
include(examples/examples.cmake)
|
||||
|
||||
add_subdirectory(modules/fwk_core)
|
||||
add_subdirectory(modules/fwk_io)
|
||||
add_subdirectory(tests/test_app)
|
||||
add_subdirectory(tests/test_app_low_level_api)
|
||||
add_subdirectory(tests/test_app_sdm_dco)
|
||||
add_subdirectory(tests/test_app_sdm_ctrl)
|
||||
endif()
|
||||
84
lib_sw_pll/LICENSE.rst
Normal file
84
lib_sw_pll/LICENSE.rst
Normal file
@@ -0,0 +1,84 @@
|
||||
*******************************
|
||||
XMOS PUBLIC LICENCE: Version 1
|
||||
*******************************
|
||||
|
||||
Subject to the conditions and limitations below, permission is hereby granted by XMOS LIMITED (“XMOS”), free of charge, to any person or entity obtaining a copy of the XMOS Software.
|
||||
|
||||
**1. Definitions**
|
||||
|
||||
**“Applicable Patent Rights”** means: (a) where XMOS is the grantor of the rights, (i) claims of patents that are now or in future owned by or assigned to XMOS and (ii) that cover subject matter contained in the Software, but only to the extent it is necessary to use, reproduce or distribute the Software without infringement; and (b) where you are the grantor of the rights, (i) claims of patents that are now or in future owned by or assigned to you and (ii) that cover the subject matter contained in your Derivatives, taken alone or in combination with the Software.
|
||||
|
||||
**“Compiled Code”** means any compiled, binary, machine readable or executable version of the Source Code.
|
||||
|
||||
**“Contributor”** means any person or entity that creates or contributes to the creation of Derivatives.
|
||||
|
||||
**“Derivatives”** means any addition to, deletion from and/or change to the substance, structure of the Software, any previous Derivatives, the combination of the Derivatives and the Software and/or any respective portions thereof.
|
||||
|
||||
**“Source Code”** means the human readable code that is suitable for making modifications but excluding any Compiled Code.
|
||||
|
||||
**“Software”** means the software and associated documentation files which XMOS makes available and which contain a notice identifying the software as original XMOS software and referring to the software being subject to the terms of this XMOS Public Licence.
|
||||
|
||||
This Licence refers to XMOS Software and does not relate to any XMOS hardware or devices which are protected by intellectual property rights (including patent and trade marks) which may be sold to you under a separate agreement.
|
||||
|
||||
|
||||
**2. Licence**
|
||||
|
||||
**Permitted Uses, Conditions and Restrictions.** Subject to the conditions below, XMOS grants you a worldwide, royalty free, non-exclusive licence, to the extent of any Patent Rights to do the following:
|
||||
|
||||
2.1 **Unmodified Software.** You may use, copy, display, publish, distribute and make available unmodified copies of the Software:
|
||||
|
||||
2.1.1 for personal or academic, non-commercial purposes; or
|
||||
|
||||
2.1.2 for commercial purposes provided the Software is at all times used on a device designed, licensed or developed by XMOS and, provided that in each instance (2.1.1 and 2.1.2):
|
||||
|
||||
(a) you must retain and reproduce in all copies of the Software the copyright and proprietary notices and disclaimers of XMOS as they appear in the Software, and keep intact all notices and disclaimers in the Software files that refer to this Licence; and
|
||||
|
||||
(b) you must include a copy of this Licence with every copy of the Software and documentation you publish, distribute and make available and you may not offer or impose any terms on such Software that alter or restrict this Licence or the intent of such Licence, except as permitted below (Additional Terms).
|
||||
|
||||
The licence above does not include any Compiled Code which XMOS may make available under a separate support and licence agreement.
|
||||
|
||||
2.2 **Derivatives.** You may create and modify Derivatives and use, copy, display, publish, distribute and make available Derivatives:
|
||||
|
||||
2.2.1 for personal or academic, non-commercial purposes; or
|
||||
|
||||
2.2.2 for commercial purposes, provided the Derivatives are at all times used on a device designed, licensed or developed by XMOS and, provided that in each instance (2.2.1 and 2.2.2):
|
||||
|
||||
(a) you must comply with the terms of clause 2.1 with respect to the Derivatives;
|
||||
|
||||
(b) you must copy (to the extent it doesn’t already exist) the notice below in each file of the Derivatives, and ensure all the modified files carry prominent notices stating that you have changed the files and the date of any change; and
|
||||
|
||||
(c) if you sublicence, distribute or otherwise make the Software and/or the Derivatives available for commercial purposes, you must provide that the Software and Derivatives are at all times used on a device designed, licensed or developed by XMOS.
|
||||
|
||||
Without limitation to these terms and clause 3 below, the Source Code and Compiled Code to your Derivatives may at your discretion (but without obligation) be released, copied, displayed, published, distributed and made available; and if you elect to do so, it must be under the terms of this Licence including the terms of the licence at clauses 2.2.1, 2.2.2 and clause 3 below.
|
||||
|
||||
2.3 **Distribution of Executable Versions.** If you distribute or make available Derivatives, you must include a prominent notice in the code itself as well as in all related documentation, stating that the Source Code of the Software from which the Derivatives are based is available under the terms of this Licence, with information on how and where to obtain such Source Code.
|
||||
|
||||
**3. Your Grant of Rights.** In consideration and as a condition to this Licence, you grant to any person or entity receiving or distributing any Derivatives, a non-exclusive, royalty free, perpetual, irrevocable license under your Applicable Patent Rights and all other intellectual property rights owned or controlled by you, to use, copy, display, publish, distribute and make available your Derivatives of the same scope and extent as XMOS’s licence under clause 2.2 above.
|
||||
|
||||
**4. Combined Products.** You may create a combined product by combining Software, Derivatives and other code not covered by this Licence as a single application or product. In such instance, you must comply with the requirements of this Licence for any portion of the Software and/or Derivatives.
|
||||
|
||||
**5. Additional Terms.** You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations and/or other rights consistent with the term of this Licence (“Additional Terms”) to any legitimate recipients of the Software and/or Derivatives. The terms on which you provide such Additional Terms are on your sole responsibility and you shall indemnify, defend and hold XMOS harmless against any claims asserted against XMOS.
|
||||
|
||||
**6. New Versions.** XMOS may publish revised and/or new versions of this Licence from time to time to accommodate changes to the Licence terms, new versions, updates and bug fixes of the Software. Each version will be given a distinguishing version number. Once Software has been published under a particular version of this Licence, you may continue to use it under the terms of that version. You may also choose to use the latest version of the Software under any subsequent version published by XMOS. Only XMOS shall have the right to modify these terms.
|
||||
|
||||
**7. IPR and Ownership**
|
||||
Any rights, including all intellectual property rights and all trademarks not expressly granted herein are reserved in full by the authors or copyright holders. Any requests for additional permissions by XMOS including any rights to use XMOS trademarks, should be made (without obligation) to XMOS at **support@xmos.com**
|
||||
|
||||
Nothing herein shall limit any rights that XMOS is otherwise entitled to under the doctrines of patent exhaustion, implied license, or legal estoppel. Neither the name of the authors, the copyright holders or any contributors may be used to endorse or promote any Derivatives from this Software without specific written permission. Any attempt to deal with the Software which does not comply with this Licence shall be void and shall automatically terminate any rights granted under this licence (including any licence of any intellectual property rights granted herein).
|
||||
Subject to the licences granted under this Licence any Contributor retains all rights, title and interest in and to any Derivatives made by Contributor subject to the underlying rights of XMOS in the Software. XMOS shall retain all rights, title and interest in the Software and any Derivatives made by XMOS (“XMOS Derivatives”). XMOS Derivatives will not automatically be subject to this Licence and XMOS shall be entitled to licence such rights on any terms (without obligation) as it sees fit.
|
||||
|
||||
**8. Termination**
|
||||
|
||||
8.1 This Licence will automatically terminate immediately, without notice to you, if:
|
||||
|
||||
(a) you fail to comply with the terms of this Licence; and/or
|
||||
|
||||
(b) you directly or indirectly commence any action for patent or intellectual property right infringement against XMOS, or any parent, group, affiliate or subsidiary of XMOS; provided XMOS did not first commence an action or patent infringement against you in that instance; and/or
|
||||
|
||||
(c) the terms of this Licence are held by any court of competent jurisdiction to be unenforceable in whole or in part.
|
||||
|
||||
**9. Critical Applications.** Unless XMOS has agreed in writing with you an agreement specifically governing use of the Goods in military, aerospace, automotive or medically related functions (collectively and individually hereinafter referred to as "Special Use"), any permitted use of the Software excludes Special Use. Notwithstanding any agreement between XMOS and you for Special Use, Special Use shall be at your own risk, and you shall fully indemnify XMOS against any damages, losses, costs and claims (direct and indirect) arising out of any Special Use.
|
||||
|
||||
**10. NO WARRANTY OR SUPPORT.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL XMOS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, WARRANTY, CIVIL TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE INCLUDING GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND NOT WITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE. IN SOME JURISDICTIONS PARTIES ARE UNABLE TO LIMIT LIABILTY IN THIS WAY, IF THIS APPLIES TO YOUR JURISDICTION THIS LIABILITY CLAUSE ABOVE MAY NOT APPLY. NOTWITHSTANDING THE ABOVE, IN NO EVENT SHALL XMOS’s TOTAL LIABILITY TO YOU FOR ALL DAMAGES, LOSS OR OTHERWISE EXCEED $50.
|
||||
|
||||
**11. Governing Law and Jurisdiction.** This Licence constitutes the entire agreement between the parties with respect to the subject matter hereof. The Licence shall be governed by the laws of England and the conflict of laws and UN Convention on Contracts for the International Sale of Goods, shall not apply.
|
||||
92
lib_sw_pll/README.rst
Normal file
92
lib_sw_pll/README.rst
Normal file
@@ -0,0 +1,92 @@
|
||||
lib_sw_pll
|
||||
==========
|
||||
|
||||
:Version: 2.2.0
|
||||
:Vendor: XMOS
|
||||
This library contains software that, together with the on-chip application PLL, provides a PLL that will generate a clock that is phase-locked to an input clock.
|
||||
|
||||
It supports both Look Up Table (LUT) and Sigma Delta Modulated (SDM) Digitally Controlled Oscillators (DCO), a Phase Frequency Detector (PFD) and
|
||||
configurable Proportional Integral (PI) controllers which together form a hybrid Software/Hardware Phase Locked Loop (PLL).
|
||||
|
||||
Examples are provided showing a master clock locking to a low frequency input reference clock and also to an I2S slave interface.
|
||||
|
||||
In addition, an API providing a range of fixed clocks supporting common master clock frequencies between 11.2896 MHz and 49.152 MHz is available
|
||||
in cases where phase locking is not required.
|
||||
|
||||
*********************************
|
||||
Building and running the examples
|
||||
*********************************
|
||||
|
||||
Ensure a correctly configured installation of the XMOS tools and open an XTC command shell. Please check that the XMOS tools are correctly
|
||||
sourced by running the following command::
|
||||
|
||||
$ xcc
|
||||
xcc: no input files
|
||||
|
||||
.. note::
|
||||
Instructions for installing and configuring the XMOS tools appear on `the XMOS web site <https://www.xmos.ai/software-tools/>`_.
|
||||
|
||||
Clone the lib_sw_pll repository::
|
||||
|
||||
git clone git@github.com:xmos/lib_sw_pll.git
|
||||
cd lib_sw_pll
|
||||
|
||||
Place the fwk_core and fwk_io repositories in the modules directory of lib_sw_pll. These are required dependencies for the example apps.
|
||||
To do so, from the root of lib_sw_pll (where this read me file exists) type::
|
||||
|
||||
mkdir modules
|
||||
cd modules
|
||||
git clone --recurse-submodules git@github.com:xmos/fwk_core.git
|
||||
git clone --recurse-submodules git@github.com:xmos/fwk_io.git
|
||||
cd ..
|
||||
|
||||
.. note::
|
||||
The fwk_core and fwk_io repositories have not been sub-moduled into this Git repository because only the examples depend upon them.
|
||||
|
||||
Run the following commands in the lib_sw_pll root folder to build the firmware.
|
||||
|
||||
On linux::
|
||||
|
||||
cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake
|
||||
cd build
|
||||
make simple_lut simple_sdm i2s_slave_lut
|
||||
|
||||
On Windows::
|
||||
|
||||
cmake -G "Ninja" -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake
|
||||
cd build
|
||||
ninja simple_lut simple_sdm i2s_slave_lut
|
||||
|
||||
To run the firmware, first connect LRCLK and BCLK (connects the test clock output to the PLL reference input)
|
||||
and run the following command where <my_example> can be *simple_lut* or *simple_sdm* which use the XCORE-AI-EXPLORER board
|
||||
or *i2s_slave_lut* which uses the XK-VOICE-SQ66 board::
|
||||
|
||||
xrun --xscope <my_example>.xe
|
||||
|
||||
For simple_xxx.xe, to see the PLL lock, put one scope probe on either LRCLK/BCLK (reference input) and the other on PORT_I2S_DAC_DATA to see the
|
||||
recovered clock which has been hardware divided back down to the same rate as the input reference clock.
|
||||
|
||||
For i2s_slave_lut.xe you will need to connect a 48kHz I2S master to the LRCLK, BCLK pins. You may then observe the I2S input data being
|
||||
looped back to the output and the MCLK being generated. A divided version of MCLK is output on PORT_I2S_DATA2 which allows
|
||||
direct comparison of the input reference (LRCLK) with the recovered clock at the same, and locked, frequency.
|
||||
|
||||
*********************************
|
||||
Generating new PLL configurations
|
||||
*********************************
|
||||
|
||||
Please see `doc/rst/sw_pll.rst` for further details on how to design and build new sw_pll configurations. This covers the tradeoff between lock range,
|
||||
oscillator noise and resource usage.
|
||||
Required Software (dependencies)
|
||||
================================
|
||||
|
||||
* None
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can find the documentation for this software in the /doc directory of the package.
|
||||
|
||||
Support
|
||||
=======
|
||||
|
||||
This package is supported by XMOS Ltd. Issues can be raised against the software at: http://www.xmos.com/support
|
||||
3
lib_sw_pll/examples/examples.cmake
Normal file
3
lib_sw_pll/examples/examples.cmake
Normal file
@@ -0,0 +1,3 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/simple_lut/simple_lut.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/simple_sdm/simple_sdm.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/i2s_slave_lut/i2s_slave_lut.cmake)
|
||||
41
lib_sw_pll/examples/i2s_slave_lut/i2s_slave_lut.cmake
Normal file
41
lib_sw_pll/examples/i2s_slave_lut/i2s_slave_lut.cmake
Normal file
@@ -0,0 +1,41 @@
|
||||
#**********************
|
||||
# Gather Sources
|
||||
#**********************
|
||||
file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/src/*.xc)
|
||||
set(APP_INCLUDES
|
||||
${CMAKE_CURRENT_LIST_DIR}/src
|
||||
)
|
||||
|
||||
#**********************
|
||||
# Flags
|
||||
#**********************
|
||||
set(APP_COMPILER_FLAGS
|
||||
-Os
|
||||
-g
|
||||
-report
|
||||
-fxscope
|
||||
-mcmodel=large
|
||||
-Wno-xcore-fptrgroup
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/config.xscope
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/xvf3800_qf60.xn
|
||||
)
|
||||
|
||||
set(APP_COMPILE_DEFINITIONS
|
||||
)
|
||||
|
||||
set(APP_LINK_OPTIONS
|
||||
-report
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/config.xscope
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/xvf3800_qf60.xn
|
||||
)
|
||||
|
||||
#**********************
|
||||
# Tile Targets
|
||||
#**********************
|
||||
add_executable(i2s_slave_lut)
|
||||
target_sources(i2s_slave_lut PUBLIC ${APP_SOURCES})
|
||||
target_include_directories(i2s_slave_lut PUBLIC ${APP_INCLUDES})
|
||||
target_compile_definitions(i2s_slave_lut PRIVATE ${APP_COMPILE_DEFINITIONS})
|
||||
target_compile_options(i2s_slave_lut PRIVATE ${APP_COMPILER_FLAGS})
|
||||
target_link_options(i2s_slave_lut PRIVATE ${APP_LINK_OPTIONS})
|
||||
target_link_libraries(i2s_slave_lut PUBLIC lib_sw_pll lib_i2s)
|
||||
24
lib_sw_pll/examples/i2s_slave_lut/src/config.xscope
Normal file
24
lib_sw_pll/examples/i2s_slave_lut/src/config.xscope
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ======================================================= -->
|
||||
<!-- The 'ioMode' attribute on the xSCOPEconfig -->
|
||||
<!-- element can take the following values: -->
|
||||
<!-- "none", "basic", "timed" -->
|
||||
<!-- -->
|
||||
<!-- The 'type' attribute on Probe -->
|
||||
<!-- elements can take the following values: -->
|
||||
<!-- "STARTSTOP", "CONTINUOUS", "DISCRETE", "STATEMACHINE" -->
|
||||
<!-- -->
|
||||
<!-- The 'datatype' attribute on Probe -->
|
||||
<!-- elements can take the following values: -->
|
||||
<!-- "NONE", "UINT", "INT", "FLOAT" -->
|
||||
<!-- ======================================================= -->
|
||||
|
||||
<xSCOPEconfig ioMode="basic" enabled="true">
|
||||
|
||||
<!-- For example: -->
|
||||
<!-- <Probe name="I2S_RX_0" type="CONTINUOUS" datatype="INT" units="Value" enabled="true"/> -->
|
||||
|
||||
|
||||
<!-- From the target code, call: xscope_int(PROBE_NAME, value); -->
|
||||
</xSCOPEconfig>
|
||||
417
lib_sw_pll/examples/i2s_slave_lut/src/fractions.h
Normal file
417
lib_sw_pll/examples/i2s_slave_lut/src/fractions.h
Normal file
@@ -0,0 +1,417 @@
|
||||
// Header file listing fraction options searched
|
||||
// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register.
|
||||
short frac_values_80[413] = {
|
||||
0x0F16, // Index: 0 Fraction: 16/23 = 0.6957
|
||||
0x364E, // Index: 1 Fraction: 55/79 = 0.6962
|
||||
0x2637, // Index: 2 Fraction: 39/56 = 0.6964
|
||||
0x1620, // Index: 3 Fraction: 23/33 = 0.6970
|
||||
0x344B, // Index: 4 Fraction: 53/76 = 0.6974
|
||||
0x1D2A, // Index: 5 Fraction: 30/43 = 0.6977
|
||||
0x2434, // Index: 6 Fraction: 37/53 = 0.6981
|
||||
0x2B3E, // Index: 7 Fraction: 44/63 = 0.6984
|
||||
0x3248, // Index: 8 Fraction: 51/73 = 0.6986
|
||||
0x0609, // Index: 9 Fraction: 7/10 = 0.7000
|
||||
0x354C, // Index: 10 Fraction: 54/77 = 0.7013
|
||||
0x2E42, // Index: 11 Fraction: 47/67 = 0.7015
|
||||
0x2738, // Index: 12 Fraction: 40/57 = 0.7018
|
||||
0x202E, // Index: 13 Fraction: 33/47 = 0.7021
|
||||
0x1924, // Index: 14 Fraction: 26/37 = 0.7027
|
||||
0x2C3F, // Index: 15 Fraction: 45/64 = 0.7031
|
||||
0x121A, // Index: 16 Fraction: 19/27 = 0.7037
|
||||
0x3146, // Index: 17 Fraction: 50/71 = 0.7042
|
||||
0x1E2B, // Index: 18 Fraction: 31/44 = 0.7045
|
||||
0x2A3C, // Index: 19 Fraction: 43/61 = 0.7049
|
||||
0x364D, // Index: 20 Fraction: 55/78 = 0.7051
|
||||
0x0B10, // Index: 21 Fraction: 12/17 = 0.7059
|
||||
0x344A, // Index: 22 Fraction: 53/75 = 0.7067
|
||||
0x2839, // Index: 23 Fraction: 41/58 = 0.7069
|
||||
0x1C28, // Index: 24 Fraction: 29/41 = 0.7073
|
||||
0x2D40, // Index: 25 Fraction: 46/65 = 0.7077
|
||||
0x1017, // Index: 26 Fraction: 17/24 = 0.7083
|
||||
0x374E, // Index: 27 Fraction: 56/79 = 0.7089
|
||||
0x2636, // Index: 28 Fraction: 39/55 = 0.7091
|
||||
0x151E, // Index: 29 Fraction: 22/31 = 0.7097
|
||||
0x3044, // Index: 30 Fraction: 49/69 = 0.7101
|
||||
0x1A25, // Index: 31 Fraction: 27/38 = 0.7105
|
||||
0x1F2C, // Index: 32 Fraction: 32/45 = 0.7111
|
||||
0x2433, // Index: 33 Fraction: 37/52 = 0.7115
|
||||
0x293A, // Index: 34 Fraction: 42/59 = 0.7119
|
||||
0x2E41, // Index: 35 Fraction: 47/66 = 0.7121
|
||||
0x3348, // Index: 36 Fraction: 52/73 = 0.7123
|
||||
0x384F, // Index: 37 Fraction: 57/80 = 0.7125
|
||||
0x0406, // Index: 38 Fraction: 5/7 = 0.7143
|
||||
0x3449, // Index: 39 Fraction: 53/74 = 0.7162
|
||||
0x2F42, // Index: 40 Fraction: 48/67 = 0.7164
|
||||
0x2A3B, // Index: 41 Fraction: 43/60 = 0.7167
|
||||
0x2534, // Index: 42 Fraction: 38/53 = 0.7170
|
||||
0x202D, // Index: 43 Fraction: 33/46 = 0.7174
|
||||
0x1B26, // Index: 44 Fraction: 28/39 = 0.7179
|
||||
0x3246, // Index: 45 Fraction: 51/71 = 0.7183
|
||||
0x161F, // Index: 46 Fraction: 23/32 = 0.7188
|
||||
0x2838, // Index: 47 Fraction: 41/57 = 0.7193
|
||||
0x1118, // Index: 48 Fraction: 18/25 = 0.7200
|
||||
0x3043, // Index: 49 Fraction: 49/68 = 0.7206
|
||||
0x1E2A, // Index: 50 Fraction: 31/43 = 0.7209
|
||||
0x2B3C, // Index: 51 Fraction: 44/61 = 0.7213
|
||||
0x384E, // Index: 52 Fraction: 57/79 = 0.7215
|
||||
0x0C11, // Index: 53 Fraction: 13/18 = 0.7222
|
||||
0x2E40, // Index: 54 Fraction: 47/65 = 0.7231
|
||||
0x212E, // Index: 55 Fraction: 34/47 = 0.7234
|
||||
0x364B, // Index: 56 Fraction: 55/76 = 0.7237
|
||||
0x141C, // Index: 57 Fraction: 21/29 = 0.7241
|
||||
0x3144, // Index: 58 Fraction: 50/69 = 0.7246
|
||||
0x1C27, // Index: 59 Fraction: 29/40 = 0.7250
|
||||
0x2432, // Index: 60 Fraction: 37/51 = 0.7255
|
||||
0x2C3D, // Index: 61 Fraction: 45/62 = 0.7258
|
||||
0x3448, // Index: 62 Fraction: 53/73 = 0.7260
|
||||
0x070A, // Index: 63 Fraction: 8/11 = 0.7273
|
||||
0x3245, // Index: 64 Fraction: 51/70 = 0.7286
|
||||
0x2A3A, // Index: 65 Fraction: 43/59 = 0.7288
|
||||
0x222F, // Index: 66 Fraction: 35/48 = 0.7292
|
||||
0x1A24, // Index: 67 Fraction: 27/37 = 0.7297
|
||||
0x2D3E, // Index: 68 Fraction: 46/63 = 0.7302
|
||||
0x1219, // Index: 69 Fraction: 19/26 = 0.7308
|
||||
0x3042, // Index: 70 Fraction: 49/67 = 0.7313
|
||||
0x1D28, // Index: 71 Fraction: 30/41 = 0.7317
|
||||
0x2837, // Index: 72 Fraction: 41/56 = 0.7321
|
||||
0x3346, // Index: 73 Fraction: 52/71 = 0.7324
|
||||
0x0A0E, // Index: 74 Fraction: 11/15 = 0.7333
|
||||
0x394E, // Index: 75 Fraction: 58/79 = 0.7342
|
||||
0x2E3F, // Index: 76 Fraction: 47/64 = 0.7344
|
||||
0x2330, // Index: 77 Fraction: 36/49 = 0.7347
|
||||
0x1821, // Index: 78 Fraction: 25/34 = 0.7353
|
||||
0x2634, // Index: 79 Fraction: 39/53 = 0.7358
|
||||
0x3447, // Index: 80 Fraction: 53/72 = 0.7361
|
||||
0x0D12, // Index: 81 Fraction: 14/19 = 0.7368
|
||||
0x3A4F, // Index: 82 Fraction: 59/80 = 0.7375
|
||||
0x2C3C, // Index: 83 Fraction: 45/61 = 0.7377
|
||||
0x1E29, // Index: 84 Fraction: 31/42 = 0.7381
|
||||
0x2F40, // Index: 85 Fraction: 48/65 = 0.7385
|
||||
0x1016, // Index: 86 Fraction: 17/23 = 0.7391
|
||||
0x3548, // Index: 87 Fraction: 54/73 = 0.7397
|
||||
0x2431, // Index: 88 Fraction: 37/50 = 0.7400
|
||||
0x384C, // Index: 89 Fraction: 57/77 = 0.7403
|
||||
0x131A, // Index: 90 Fraction: 20/27 = 0.7407
|
||||
0x2A39, // Index: 91 Fraction: 43/58 = 0.7414
|
||||
0x161E, // Index: 92 Fraction: 23/31 = 0.7419
|
||||
0x3041, // Index: 93 Fraction: 49/66 = 0.7424
|
||||
0x1922, // Index: 94 Fraction: 26/35 = 0.7429
|
||||
0x3649, // Index: 95 Fraction: 55/74 = 0.7432
|
||||
0x1C26, // Index: 96 Fraction: 29/39 = 0.7436
|
||||
0x1F2A, // Index: 97 Fraction: 32/43 = 0.7442
|
||||
0x222E, // Index: 98 Fraction: 35/47 = 0.7447
|
||||
0x2532, // Index: 99 Fraction: 38/51 = 0.7451
|
||||
0x2836, // Index: 100 Fraction: 41/55 = 0.7455
|
||||
0x2B3A, // Index: 101 Fraction: 44/59 = 0.7458
|
||||
0x2E3E, // Index: 102 Fraction: 47/63 = 0.7460
|
||||
0x3142, // Index: 103 Fraction: 50/67 = 0.7463
|
||||
0x3446, // Index: 104 Fraction: 53/71 = 0.7465
|
||||
0x374A, // Index: 105 Fraction: 56/75 = 0.7467
|
||||
0x3A4E, // Index: 106 Fraction: 59/79 = 0.7468
|
||||
0x0203, // Index: 107 Fraction: 3/4 = 0.7500
|
||||
0x394C, // Index: 108 Fraction: 58/77 = 0.7532
|
||||
0x3648, // Index: 109 Fraction: 55/73 = 0.7534
|
||||
0x3344, // Index: 110 Fraction: 52/69 = 0.7536
|
||||
0x3040, // Index: 111 Fraction: 49/65 = 0.7538
|
||||
0x2D3C, // Index: 112 Fraction: 46/61 = 0.7541
|
||||
0x2A38, // Index: 113 Fraction: 43/57 = 0.7544
|
||||
0x2734, // Index: 114 Fraction: 40/53 = 0.7547
|
||||
0x2430, // Index: 115 Fraction: 37/49 = 0.7551
|
||||
0x212C, // Index: 116 Fraction: 34/45 = 0.7556
|
||||
0x1E28, // Index: 117 Fraction: 31/41 = 0.7561
|
||||
0x3A4D, // Index: 118 Fraction: 59/78 = 0.7564
|
||||
0x1B24, // Index: 119 Fraction: 28/37 = 0.7568
|
||||
0x3445, // Index: 120 Fraction: 53/70 = 0.7571
|
||||
0x1820, // Index: 121 Fraction: 25/33 = 0.7576
|
||||
0x2E3D, // Index: 122 Fraction: 47/62 = 0.7581
|
||||
0x151C, // Index: 123 Fraction: 22/29 = 0.7586
|
||||
0x2835, // Index: 124 Fraction: 41/54 = 0.7593
|
||||
0x3B4E, // Index: 125 Fraction: 60/79 = 0.7595
|
||||
0x1218, // Index: 126 Fraction: 19/25 = 0.7600
|
||||
0x3546, // Index: 127 Fraction: 54/71 = 0.7606
|
||||
0x222D, // Index: 128 Fraction: 35/46 = 0.7609
|
||||
0x3242, // Index: 129 Fraction: 51/67 = 0.7612
|
||||
0x0F14, // Index: 130 Fraction: 16/21 = 0.7619
|
||||
0x3C4F, // Index: 131 Fraction: 61/80 = 0.7625
|
||||
0x2C3A, // Index: 132 Fraction: 45/59 = 0.7627
|
||||
0x1C25, // Index: 133 Fraction: 29/38 = 0.7632
|
||||
0x2936, // Index: 134 Fraction: 42/55 = 0.7636
|
||||
0x3647, // Index: 135 Fraction: 55/72 = 0.7639
|
||||
0x0C10, // Index: 136 Fraction: 13/17 = 0.7647
|
||||
0x303F, // Index: 137 Fraction: 49/64 = 0.7656
|
||||
0x232E, // Index: 138 Fraction: 36/47 = 0.7660
|
||||
0x3A4C, // Index: 139 Fraction: 59/77 = 0.7662
|
||||
0x161D, // Index: 140 Fraction: 23/30 = 0.7667
|
||||
0x3748, // Index: 141 Fraction: 56/73 = 0.7671
|
||||
0x202A, // Index: 142 Fraction: 33/43 = 0.7674
|
||||
0x2A37, // Index: 143 Fraction: 43/56 = 0.7679
|
||||
0x3444, // Index: 144 Fraction: 53/69 = 0.7681
|
||||
0x090C, // Index: 145 Fraction: 10/13 = 0.7692
|
||||
0x3849, // Index: 146 Fraction: 57/74 = 0.7703
|
||||
0x2E3C, // Index: 147 Fraction: 47/61 = 0.7705
|
||||
0x242F, // Index: 148 Fraction: 37/48 = 0.7708
|
||||
0x1A22, // Index: 149 Fraction: 27/35 = 0.7714
|
||||
0x2B38, // Index: 150 Fraction: 44/57 = 0.7719
|
||||
0x3C4E, // Index: 151 Fraction: 61/79 = 0.7722
|
||||
0x1015, // Index: 152 Fraction: 17/22 = 0.7727
|
||||
0x394A, // Index: 153 Fraction: 58/75 = 0.7733
|
||||
0x2834, // Index: 154 Fraction: 41/53 = 0.7736
|
||||
0x171E, // Index: 155 Fraction: 24/31 = 0.7742
|
||||
0x3646, // Index: 156 Fraction: 55/71 = 0.7746
|
||||
0x1E27, // Index: 157 Fraction: 31/40 = 0.7750
|
||||
0x2530, // Index: 158 Fraction: 38/49 = 0.7755
|
||||
0x2C39, // Index: 159 Fraction: 45/58 = 0.7759
|
||||
0x3342, // Index: 160 Fraction: 52/67 = 0.7761
|
||||
0x3A4B, // Index: 161 Fraction: 59/76 = 0.7763
|
||||
0x0608, // Index: 162 Fraction: 7/9 = 0.7778
|
||||
0x3B4C, // Index: 163 Fraction: 60/77 = 0.7792
|
||||
0x3443, // Index: 164 Fraction: 53/68 = 0.7794
|
||||
0x2D3A, // Index: 165 Fraction: 46/59 = 0.7797
|
||||
0x2631, // Index: 166 Fraction: 39/50 = 0.7800
|
||||
0x1F28, // Index: 167 Fraction: 32/41 = 0.7805
|
||||
0x3848, // Index: 168 Fraction: 57/73 = 0.7808
|
||||
0x181F, // Index: 169 Fraction: 25/32 = 0.7812
|
||||
0x2A36, // Index: 170 Fraction: 43/55 = 0.7818
|
||||
0x3C4D, // Index: 171 Fraction: 61/78 = 0.7821
|
||||
0x1116, // Index: 172 Fraction: 18/23 = 0.7826
|
||||
0x2E3B, // Index: 173 Fraction: 47/60 = 0.7833
|
||||
0x1C24, // Index: 174 Fraction: 29/37 = 0.7838
|
||||
0x2732, // Index: 175 Fraction: 40/51 = 0.7843
|
||||
0x3240, // Index: 176 Fraction: 51/65 = 0.7846
|
||||
0x3D4E, // Index: 177 Fraction: 62/79 = 0.7848
|
||||
0x0A0D, // Index: 178 Fraction: 11/14 = 0.7857
|
||||
0x3A4A, // Index: 179 Fraction: 59/75 = 0.7867
|
||||
0x2F3C, // Index: 180 Fraction: 48/61 = 0.7869
|
||||
0x242E, // Index: 181 Fraction: 37/47 = 0.7872
|
||||
0x3E4F, // Index: 182 Fraction: 63/80 = 0.7875
|
||||
0x1920, // Index: 183 Fraction: 26/33 = 0.7879
|
||||
0x2833, // Index: 184 Fraction: 41/52 = 0.7885
|
||||
0x3746, // Index: 185 Fraction: 56/71 = 0.7887
|
||||
0x0E12, // Index: 186 Fraction: 15/19 = 0.7895
|
||||
0x303D, // Index: 187 Fraction: 49/62 = 0.7903
|
||||
0x212A, // Index: 188 Fraction: 34/43 = 0.7907
|
||||
0x3442, // Index: 189 Fraction: 53/67 = 0.7910
|
||||
0x1217, // Index: 190 Fraction: 19/24 = 0.7917
|
||||
0x3C4C, // Index: 191 Fraction: 61/77 = 0.7922
|
||||
0x2934, // Index: 192 Fraction: 42/53 = 0.7925
|
||||
0x161C, // Index: 193 Fraction: 23/29 = 0.7931
|
||||
0x313E, // Index: 194 Fraction: 50/63 = 0.7937
|
||||
0x1A21, // Index: 195 Fraction: 27/34 = 0.7941
|
||||
0x3948, // Index: 196 Fraction: 58/73 = 0.7945
|
||||
0x1E26, // Index: 197 Fraction: 31/39 = 0.7949
|
||||
0x222B, // Index: 198 Fraction: 35/44 = 0.7955
|
||||
0x2630, // Index: 199 Fraction: 39/49 = 0.7959
|
||||
0x2A35, // Index: 200 Fraction: 43/54 = 0.7963
|
||||
0x2E3A, // Index: 201 Fraction: 47/59 = 0.7966
|
||||
0x323F, // Index: 202 Fraction: 51/64 = 0.7969
|
||||
0x3644, // Index: 203 Fraction: 55/69 = 0.7971
|
||||
0x3A49, // Index: 204 Fraction: 59/74 = 0.7973
|
||||
0x3E4E, // Index: 205 Fraction: 63/79 = 0.7975
|
||||
0x0304, // Index: 206 Fraction: 4/5 = 0.8000
|
||||
0x3C4B, // Index: 207 Fraction: 61/76 = 0.8026
|
||||
0x3846, // Index: 208 Fraction: 57/71 = 0.8028
|
||||
0x3441, // Index: 209 Fraction: 53/66 = 0.8030
|
||||
0x303C, // Index: 210 Fraction: 49/61 = 0.8033
|
||||
0x2C37, // Index: 211 Fraction: 45/56 = 0.8036
|
||||
0x2832, // Index: 212 Fraction: 41/51 = 0.8039
|
||||
0x242D, // Index: 213 Fraction: 37/46 = 0.8043
|
||||
0x2028, // Index: 214 Fraction: 33/41 = 0.8049
|
||||
0x3D4C, // Index: 215 Fraction: 62/77 = 0.8052
|
||||
0x1C23, // Index: 216 Fraction: 29/36 = 0.8056
|
||||
0x3542, // Index: 217 Fraction: 54/67 = 0.8060
|
||||
0x181E, // Index: 218 Fraction: 25/31 = 0.8065
|
||||
0x2D38, // Index: 219 Fraction: 46/57 = 0.8070
|
||||
0x1419, // Index: 220 Fraction: 21/26 = 0.8077
|
||||
0x3A48, // Index: 221 Fraction: 59/73 = 0.8082
|
||||
0x252E, // Index: 222 Fraction: 38/47 = 0.8085
|
||||
0x3643, // Index: 223 Fraction: 55/68 = 0.8088
|
||||
0x1014, // Index: 224 Fraction: 17/21 = 0.8095
|
||||
0x3F4E, // Index: 225 Fraction: 64/79 = 0.8101
|
||||
0x2E39, // Index: 226 Fraction: 47/58 = 0.8103
|
||||
0x1D24, // Index: 227 Fraction: 30/37 = 0.8108
|
||||
0x2A34, // Index: 228 Fraction: 43/53 = 0.8113
|
||||
0x3744, // Index: 229 Fraction: 56/69 = 0.8116
|
||||
0x0C0F, // Index: 230 Fraction: 13/16 = 0.8125
|
||||
0x3C4A, // Index: 231 Fraction: 61/75 = 0.8133
|
||||
0x2F3A, // Index: 232 Fraction: 48/59 = 0.8136
|
||||
0x222A, // Index: 233 Fraction: 35/43 = 0.8140
|
||||
0x3845, // Index: 234 Fraction: 57/70 = 0.8143
|
||||
0x151A, // Index: 235 Fraction: 22/27 = 0.8148
|
||||
0x3440, // Index: 236 Fraction: 53/65 = 0.8154
|
||||
0x1E25, // Index: 237 Fraction: 31/38 = 0.8158
|
||||
0x2730, // Index: 238 Fraction: 40/49 = 0.8163
|
||||
0x303B, // Index: 239 Fraction: 49/60 = 0.8167
|
||||
0x3946, // Index: 240 Fraction: 58/71 = 0.8169
|
||||
0x080A, // Index: 241 Fraction: 9/11 = 0.8182
|
||||
0x3A47, // Index: 242 Fraction: 59/72 = 0.8194
|
||||
0x313C, // Index: 243 Fraction: 50/61 = 0.8197
|
||||
0x2831, // Index: 244 Fraction: 41/50 = 0.8200
|
||||
0x1F26, // Index: 245 Fraction: 32/39 = 0.8205
|
||||
0x3642, // Index: 246 Fraction: 55/67 = 0.8209
|
||||
0x161B, // Index: 247 Fraction: 23/28 = 0.8214
|
||||
0x3B48, // Index: 248 Fraction: 60/73 = 0.8219
|
||||
0x242C, // Index: 249 Fraction: 37/45 = 0.8222
|
||||
0x323D, // Index: 250 Fraction: 51/62 = 0.8226
|
||||
0x404E, // Index: 251 Fraction: 65/79 = 0.8228
|
||||
0x0D10, // Index: 252 Fraction: 14/17 = 0.8235
|
||||
0x3C49, // Index: 253 Fraction: 61/74 = 0.8243
|
||||
0x2E38, // Index: 254 Fraction: 47/57 = 0.8246
|
||||
0x2027, // Index: 255 Fraction: 33/40 = 0.8250
|
||||
0x333E, // Index: 256 Fraction: 52/63 = 0.8254
|
||||
0x1216, // Index: 257 Fraction: 19/23 = 0.8261
|
||||
0x3D4A, // Index: 258 Fraction: 62/75 = 0.8267
|
||||
0x2A33, // Index: 259 Fraction: 43/52 = 0.8269
|
||||
0x171C, // Index: 260 Fraction: 24/29 = 0.8276
|
||||
0x343F, // Index: 261 Fraction: 53/64 = 0.8281
|
||||
0x1C22, // Index: 262 Fraction: 29/35 = 0.8286
|
||||
0x3E4B, // Index: 263 Fraction: 63/76 = 0.8289
|
||||
0x2128, // Index: 264 Fraction: 34/41 = 0.8293
|
||||
0x262E, // Index: 265 Fraction: 39/47 = 0.8298
|
||||
0x2B34, // Index: 266 Fraction: 44/53 = 0.8302
|
||||
0x303A, // Index: 267 Fraction: 49/59 = 0.8305
|
||||
0x3540, // Index: 268 Fraction: 54/65 = 0.8308
|
||||
0x3A46, // Index: 269 Fraction: 59/71 = 0.8310
|
||||
0x3F4C, // Index: 270 Fraction: 64/77 = 0.8312
|
||||
0x0405, // Index: 271 Fraction: 5/6 = 0.8333
|
||||
0x414E, // Index: 272 Fraction: 66/79 = 0.8354
|
||||
0x3C48, // Index: 273 Fraction: 61/73 = 0.8356
|
||||
0x3742, // Index: 274 Fraction: 56/67 = 0.8358
|
||||
0x323C, // Index: 275 Fraction: 51/61 = 0.8361
|
||||
0x2D36, // Index: 276 Fraction: 46/55 = 0.8364
|
||||
0x2830, // Index: 277 Fraction: 41/49 = 0.8367
|
||||
0x232A, // Index: 278 Fraction: 36/43 = 0.8372
|
||||
0x424F, // Index: 279 Fraction: 67/80 = 0.8375
|
||||
0x1E24, // Index: 280 Fraction: 31/37 = 0.8378
|
||||
0x3843, // Index: 281 Fraction: 57/68 = 0.8382
|
||||
0x191E, // Index: 282 Fraction: 26/31 = 0.8387
|
||||
0x2E37, // Index: 283 Fraction: 47/56 = 0.8393
|
||||
0x1418, // Index: 284 Fraction: 21/25 = 0.8400
|
||||
0x3944, // Index: 285 Fraction: 58/69 = 0.8406
|
||||
0x242B, // Index: 286 Fraction: 37/44 = 0.8409
|
||||
0x343E, // Index: 287 Fraction: 53/63 = 0.8413
|
||||
0x0F12, // Index: 288 Fraction: 16/19 = 0.8421
|
||||
0x3A45, // Index: 289 Fraction: 59/70 = 0.8429
|
||||
0x2A32, // Index: 290 Fraction: 43/51 = 0.8431
|
||||
0x1A1F, // Index: 291 Fraction: 27/32 = 0.8438
|
||||
0x404C, // Index: 292 Fraction: 65/77 = 0.8442
|
||||
0x252C, // Index: 293 Fraction: 38/45 = 0.8444
|
||||
0x3039, // Index: 294 Fraction: 49/58 = 0.8448
|
||||
0x3B46, // Index: 295 Fraction: 60/71 = 0.8451
|
||||
0x0A0C, // Index: 296 Fraction: 11/13 = 0.8462
|
||||
0x3C47, // Index: 297 Fraction: 61/72 = 0.8472
|
||||
0x313A, // Index: 298 Fraction: 50/59 = 0.8475
|
||||
0x262D, // Index: 299 Fraction: 39/46 = 0.8478
|
||||
0x424E, // Index: 300 Fraction: 67/79 = 0.8481
|
||||
0x1B20, // Index: 301 Fraction: 28/33 = 0.8485
|
||||
0x2C34, // Index: 302 Fraction: 45/53 = 0.8491
|
||||
0x3D48, // Index: 303 Fraction: 62/73 = 0.8493
|
||||
0x1013, // Index: 304 Fraction: 17/20 = 0.8500
|
||||
0x3842, // Index: 305 Fraction: 57/67 = 0.8507
|
||||
0x272E, // Index: 306 Fraction: 40/47 = 0.8511
|
||||
0x3E49, // Index: 307 Fraction: 63/74 = 0.8514
|
||||
0x161A, // Index: 308 Fraction: 23/27 = 0.8519
|
||||
0x333C, // Index: 309 Fraction: 52/61 = 0.8525
|
||||
0x1C21, // Index: 310 Fraction: 29/34 = 0.8529
|
||||
0x3F4A, // Index: 311 Fraction: 64/75 = 0.8533
|
||||
0x2228, // Index: 312 Fraction: 35/41 = 0.8537
|
||||
0x282F, // Index: 313 Fraction: 41/48 = 0.8542
|
||||
0x2E36, // Index: 314 Fraction: 47/55 = 0.8545
|
||||
0x343D, // Index: 315 Fraction: 53/62 = 0.8548
|
||||
0x3A44, // Index: 316 Fraction: 59/69 = 0.8551
|
||||
0x404B, // Index: 317 Fraction: 65/76 = 0.8553
|
||||
0x0506, // Index: 318 Fraction: 6/7 = 0.8571
|
||||
0x424D, // Index: 319 Fraction: 67/78 = 0.8590
|
||||
0x3C46, // Index: 320 Fraction: 61/71 = 0.8592
|
||||
0x363F, // Index: 321 Fraction: 55/64 = 0.8594
|
||||
0x3038, // Index: 322 Fraction: 49/57 = 0.8596
|
||||
0x2A31, // Index: 323 Fraction: 43/50 = 0.8600
|
||||
0x242A, // Index: 324 Fraction: 37/43 = 0.8605
|
||||
0x434E, // Index: 325 Fraction: 68/79 = 0.8608
|
||||
0x1E23, // Index: 326 Fraction: 31/36 = 0.8611
|
||||
0x3740, // Index: 327 Fraction: 56/65 = 0.8615
|
||||
0x181C, // Index: 328 Fraction: 25/29 = 0.8621
|
||||
0x444F, // Index: 329 Fraction: 69/80 = 0.8625
|
||||
0x2B32, // Index: 330 Fraction: 44/51 = 0.8627
|
||||
0x3E48, // Index: 331 Fraction: 63/73 = 0.8630
|
||||
0x1215, // Index: 332 Fraction: 19/22 = 0.8636
|
||||
0x323A, // Index: 333 Fraction: 51/59 = 0.8644
|
||||
0x1F24, // Index: 334 Fraction: 32/37 = 0.8649
|
||||
0x2C33, // Index: 335 Fraction: 45/52 = 0.8654
|
||||
0x3942, // Index: 336 Fraction: 58/67 = 0.8657
|
||||
0x0C0E, // Index: 337 Fraction: 13/15 = 0.8667
|
||||
0x3A43, // Index: 338 Fraction: 59/68 = 0.8676
|
||||
0x2D34, // Index: 339 Fraction: 46/53 = 0.8679
|
||||
0x2025, // Index: 340 Fraction: 33/38 = 0.8684
|
||||
0x343C, // Index: 341 Fraction: 53/61 = 0.8689
|
||||
0x1316, // Index: 342 Fraction: 20/23 = 0.8696
|
||||
0x424C, // Index: 343 Fraction: 67/77 = 0.8701
|
||||
0x2E35, // Index: 344 Fraction: 47/54 = 0.8704
|
||||
0x1A1E, // Index: 345 Fraction: 27/31 = 0.8710
|
||||
0x3C45, // Index: 346 Fraction: 61/70 = 0.8714
|
||||
0x2126, // Index: 347 Fraction: 34/39 = 0.8718
|
||||
0x282E, // Index: 348 Fraction: 41/47 = 0.8723
|
||||
0x2F36, // Index: 349 Fraction: 48/55 = 0.8727
|
||||
0x363E, // Index: 350 Fraction: 55/63 = 0.8730
|
||||
0x3D46, // Index: 351 Fraction: 62/71 = 0.8732
|
||||
0x444E, // Index: 352 Fraction: 69/79 = 0.8734
|
||||
0x0607, // Index: 353 Fraction: 7/8 = 0.8750
|
||||
0x3F48, // Index: 354 Fraction: 64/73 = 0.8767
|
||||
0x3840, // Index: 355 Fraction: 57/65 = 0.8769
|
||||
0x3138, // Index: 356 Fraction: 50/57 = 0.8772
|
||||
0x2A30, // Index: 357 Fraction: 43/49 = 0.8776
|
||||
0x2328, // Index: 358 Fraction: 36/41 = 0.8780
|
||||
0x4049, // Index: 359 Fraction: 65/74 = 0.8784
|
||||
0x1C20, // Index: 360 Fraction: 29/33 = 0.8788
|
||||
0x3239, // Index: 361 Fraction: 51/58 = 0.8793
|
||||
0x1518, // Index: 362 Fraction: 22/25 = 0.8800
|
||||
0x3A42, // Index: 363 Fraction: 59/67 = 0.8806
|
||||
0x2429, // Index: 364 Fraction: 37/42 = 0.8810
|
||||
0x333A, // Index: 365 Fraction: 52/59 = 0.8814
|
||||
0x424B, // Index: 366 Fraction: 67/76 = 0.8816
|
||||
0x0E10, // Index: 367 Fraction: 15/17 = 0.8824
|
||||
0x434C, // Index: 368 Fraction: 68/77 = 0.8831
|
||||
0x343B, // Index: 369 Fraction: 53/60 = 0.8833
|
||||
0x252A, // Index: 370 Fraction: 38/43 = 0.8837
|
||||
0x3C44, // Index: 371 Fraction: 61/69 = 0.8841
|
||||
0x1619, // Index: 372 Fraction: 23/26 = 0.8846
|
||||
0x353C, // Index: 373 Fraction: 54/61 = 0.8852
|
||||
0x1E22, // Index: 374 Fraction: 31/35 = 0.8857
|
||||
0x454E, // Index: 375 Fraction: 70/79 = 0.8861
|
||||
0x262B, // Index: 376 Fraction: 39/44 = 0.8864
|
||||
0x2E34, // Index: 377 Fraction: 47/53 = 0.8868
|
||||
0x363D, // Index: 378 Fraction: 55/62 = 0.8871
|
||||
0x3E46, // Index: 379 Fraction: 63/71 = 0.8873
|
||||
0x464F, // Index: 380 Fraction: 71/80 = 0.8875
|
||||
0x0708, // Index: 381 Fraction: 8/9 = 0.8889
|
||||
0x4048, // Index: 382 Fraction: 65/73 = 0.8904
|
||||
0x383F, // Index: 383 Fraction: 57/64 = 0.8906
|
||||
0x3036, // Index: 384 Fraction: 49/55 = 0.8909
|
||||
0x282D, // Index: 385 Fraction: 41/46 = 0.8913
|
||||
0x2024, // Index: 386 Fraction: 33/37 = 0.8919
|
||||
0x3940, // Index: 387 Fraction: 58/65 = 0.8923
|
||||
0x181B, // Index: 388 Fraction: 25/28 = 0.8929
|
||||
0x424A, // Index: 389 Fraction: 67/75 = 0.8933
|
||||
0x292E, // Index: 390 Fraction: 42/47 = 0.8936
|
||||
0x3A41, // Index: 391 Fraction: 59/66 = 0.8939
|
||||
0x1012, // Index: 392 Fraction: 17/19 = 0.8947
|
||||
0x3B42, // Index: 393 Fraction: 60/67 = 0.8955
|
||||
0x2A2F, // Index: 394 Fraction: 43/48 = 0.8958
|
||||
0x444C, // Index: 395 Fraction: 69/77 = 0.8961
|
||||
0x191C, // Index: 396 Fraction: 26/29 = 0.8966
|
||||
0x3C43, // Index: 397 Fraction: 61/68 = 0.8971
|
||||
0x2226, // Index: 398 Fraction: 35/39 = 0.8974
|
||||
0x2B30, // Index: 399 Fraction: 44/49 = 0.8980
|
||||
0x343A, // Index: 400 Fraction: 53/59 = 0.8983
|
||||
0x3D44, // Index: 401 Fraction: 62/69 = 0.8986
|
||||
0x464E, // Index: 402 Fraction: 71/79 = 0.8987
|
||||
0x0809, // Index: 403 Fraction: 9/10 = 0.9000
|
||||
0x3F46, // Index: 404 Fraction: 64/71 = 0.9014
|
||||
0x363C, // Index: 405 Fraction: 55/61 = 0.9016
|
||||
0x2D32, // Index: 406 Fraction: 46/51 = 0.9020
|
||||
0x2428, // Index: 407 Fraction: 37/41 = 0.9024
|
||||
0x4047, // Index: 408 Fraction: 65/72 = 0.9028
|
||||
0x1B1E, // Index: 409 Fraction: 28/31 = 0.9032
|
||||
0x2E33, // Index: 410 Fraction: 47/52 = 0.9038
|
||||
0x4148, // Index: 411 Fraction: 66/73 = 0.9041
|
||||
0x1214, // Index: 412 Fraction: 19/21 = 0.9048
|
||||
};
|
||||
229
lib_sw_pll/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c
Normal file
229
lib_sw_pll/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright 2022-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <xscope.h>
|
||||
#include <xs1.h>
|
||||
#include <xcore/assert.h>
|
||||
|
||||
#include "sw_pll.h"
|
||||
#include "i2s.h"
|
||||
|
||||
#define MCLK_FREQUENCY 12288000
|
||||
#define I2S_FREQUENCY 48000
|
||||
#define PLL_RATIO (MCLK_FREQUENCY / I2S_FREQUENCY)
|
||||
#define BCLKS_PER_LRCLK 64
|
||||
#define CONTROL_LOOP_COUNT 512
|
||||
#define PPM_RANGE 150
|
||||
|
||||
|
||||
#define NUM_I2S_CHANNELS 2
|
||||
#define NUM_I2S_LINES ((NUM_I2S_CHANNELS + 1) / 2)
|
||||
|
||||
// These are generated from sw_pll_sim.py
|
||||
#include "fractions.h"
|
||||
#include "register_setup.h"
|
||||
|
||||
void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider, port_t p_lrclk)
|
||||
{
|
||||
xassert(divider < 512);
|
||||
// Connect clock block with divide to mclk
|
||||
clock_enable(clk_recovered_ref_clk);
|
||||
clock_set_source_port(clk_recovered_ref_clk, p_mclk);
|
||||
clock_set_divide(clk_recovered_ref_clk, divider / 2);
|
||||
printf("Divider: %u\n", divider);
|
||||
|
||||
// Output the divided mclk on a port
|
||||
port_enable(p_recovered_ref_clk);
|
||||
port_set_clock(p_recovered_ref_clk, clk_recovered_ref_clk);
|
||||
port_set_out_clock(p_recovered_ref_clk);
|
||||
|
||||
// Wait for LR_CLK transition so they are synched on scope
|
||||
port_enable(p_lrclk);
|
||||
port_set_trigger_in_equal(p_lrclk, 0);
|
||||
(void) port_in(p_lrclk);
|
||||
port_set_trigger_in_equal(p_lrclk, 1);
|
||||
(void) port_in(p_lrclk);
|
||||
|
||||
clock_start(clk_recovered_ref_clk);
|
||||
}
|
||||
|
||||
typedef struct i2s_callback_args_t {
|
||||
bool did_restart; // Set by init
|
||||
int curr_lock_status;
|
||||
int32_t loopback_samples[NUM_I2S_CHANNELS];
|
||||
port_t p_mclk_count; // Used for keeping track of MCLK output for sw_pll
|
||||
port_t p_bclk_count; // Used for keeping track of BCLK input for sw_pll
|
||||
xclock_t i2s_ck_bclk;
|
||||
sw_pll_state_t *sw_pll; // Pointer to sw_pll state (if used)
|
||||
|
||||
} i2s_callback_args_t;
|
||||
|
||||
|
||||
uint32_t random = 0x80085; //Initial seed
|
||||
void pseudo_rand_uint32(uint32_t *r){
|
||||
#define CRC_POLY (0xEB31D82E)
|
||||
asm volatile("crc32 %0, %2, %3" : "=r" (*r) : "0" (*r), "r" (-1), "r" (CRC_POLY));
|
||||
}
|
||||
|
||||
I2S_CALLBACK_ATTR
|
||||
static void i2s_init(void *app_data, i2s_config_t *i2s_config){
|
||||
printf("I2S init\n");
|
||||
i2s_callback_args_t *cb_args = app_data;
|
||||
|
||||
i2s_config->mode = I2S_MODE_I2S;
|
||||
i2s_config->mclk_bclk_ratio = (MCLK_FREQUENCY / I2S_FREQUENCY);
|
||||
|
||||
cb_args->did_restart = true;
|
||||
}
|
||||
|
||||
I2S_CALLBACK_ATTR
|
||||
static i2s_restart_t i2s_restart_check(void *app_data){
|
||||
i2s_callback_args_t *cb_args = app_data;
|
||||
|
||||
// Add random jitter
|
||||
hwtimer_t tmr = hwtimer_alloc();
|
||||
pseudo_rand_uint32(&random);
|
||||
hwtimer_delay(tmr, random & 0x3f);
|
||||
hwtimer_free(tmr);
|
||||
|
||||
static uint16_t old_mclk_pt = 0;
|
||||
static uint16_t old_bclk_pt = 0;
|
||||
|
||||
port_clear_buffer(cb_args->p_bclk_count);
|
||||
port_in(cb_args->p_bclk_count); // Block until BCLK transition to synchronise
|
||||
uint16_t mclk_pt = port_get_trigger_time(cb_args->p_mclk_count); // Immediately sample mclk_count
|
||||
uint16_t bclk_pt = port_get_trigger_time(cb_args->p_bclk_count); // Now grab bclk_count (which won't have changed)
|
||||
|
||||
old_mclk_pt = mclk_pt;
|
||||
old_bclk_pt = bclk_pt;
|
||||
|
||||
sw_pll_lut_do_control(cb_args->sw_pll, mclk_pt, bclk_pt);
|
||||
|
||||
if(cb_args->sw_pll->lock_status != cb_args->curr_lock_status){
|
||||
cb_args->curr_lock_status = cb_args->sw_pll->lock_status;
|
||||
const char msg[3][16] = {"UNLOCKED LOW", "LOCKED", "UNLOCKED HIGH"};
|
||||
printf("%s\n", msg[cb_args->curr_lock_status+1]);
|
||||
}
|
||||
|
||||
// Debug only. Print raw error term to check jitter on mclk sampling
|
||||
// static int cnt=0; if(++cnt == CONTROL_LOOP_COUNT) {cnt=0;printintln(cb_args->sw_pll->mclk_diff);}
|
||||
|
||||
return I2S_NO_RESTART;
|
||||
}
|
||||
|
||||
|
||||
I2S_CALLBACK_ATTR
|
||||
static void i2s_send(void *app_data, size_t num_out, int32_t *i2s_sample_buf){
|
||||
i2s_callback_args_t *cb_args = app_data;
|
||||
|
||||
for(int i = 0; i < NUM_I2S_CHANNELS; i++){
|
||||
i2s_sample_buf[i] = cb_args->loopback_samples[i];
|
||||
}
|
||||
}
|
||||
|
||||
I2S_CALLBACK_ATTR
|
||||
static void i2s_receive(void *app_data, size_t num_in, const int32_t *i2s_sample_buf){
|
||||
i2s_callback_args_t *cb_args = app_data;
|
||||
|
||||
|
||||
for(int i = 0; i < NUM_I2S_CHANNELS; i++){
|
||||
cb_args->loopback_samples[i] = i2s_sample_buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sw_pll_test(void){
|
||||
|
||||
// I2S resources
|
||||
port_t p_i2s_dout[NUM_I2S_LINES] = {PORT_I2S_DATA1};
|
||||
port_t p_i2s_din[NUM_I2S_LINES] = {PORT_I2S_DATA0};
|
||||
port_t p_bclk = PORT_I2S_BCLK;
|
||||
port_t p_lrclk = PORT_I2S_LRCLK;
|
||||
xclock_t i2s_ck_bclk = XS1_CLKBLK_1;
|
||||
|
||||
port_enable(p_bclk);
|
||||
// NOTE: p_lrclk does not need to be enabled by the caller
|
||||
|
||||
|
||||
// sw-pll resources
|
||||
port_t p_mclk = PORT_MCLK;
|
||||
port_t p_mclk_count = PORT_MCLK_COUNT;
|
||||
xclock_t clk_mclk = XS1_CLKBLK_2;
|
||||
port_t p_bclk_count = XS1_PORT_16A; // Any unused port
|
||||
|
||||
// Create clock from mclk port and use it to clock clk_mclk.
|
||||
port_enable(p_mclk);
|
||||
clock_enable(clk_mclk);
|
||||
clock_set_source_port(clk_mclk, p_mclk);
|
||||
|
||||
// Clock p_mclk_count from clk_mclk
|
||||
port_enable(p_mclk_count);
|
||||
port_set_clock(p_mclk_count, clk_mclk);
|
||||
|
||||
clock_start(clk_mclk);
|
||||
|
||||
|
||||
// Enable p_bclk_count to count bclks cycles
|
||||
port_enable(p_bclk_count);
|
||||
port_set_clock(p_bclk_count, i2s_ck_bclk);
|
||||
|
||||
printf("Initialising SW PLL\n");
|
||||
|
||||
sw_pll_state_t sw_pll;
|
||||
sw_pll_lut_init(&sw_pll,
|
||||
SW_PLL_15Q16(0.0),
|
||||
SW_PLL_15Q16(1.0),
|
||||
SW_PLL_15Q16(0.0),
|
||||
CONTROL_LOOP_COUNT,
|
||||
PLL_RATIO,
|
||||
BCLKS_PER_LRCLK,
|
||||
frac_values_80,
|
||||
SW_PLL_NUM_LUT_ENTRIES(frac_values_80),
|
||||
APP_PLL_CTL_REG,
|
||||
APP_PLL_DIV_REG,
|
||||
SW_PLL_NUM_LUT_ENTRIES(frac_values_80) / 2,
|
||||
PPM_RANGE);
|
||||
|
||||
|
||||
printf("i_windup_limit: %ld\n", sw_pll.pi_state.i_windup_limit);
|
||||
|
||||
|
||||
// Initialise app_data
|
||||
i2s_callback_args_t app_data = {
|
||||
.did_restart = false,
|
||||
.curr_lock_status = SW_PLL_UNLOCKED_LOW,
|
||||
.loopback_samples = {0},
|
||||
.p_mclk_count = p_mclk_count,
|
||||
.p_bclk_count = p_bclk_count,
|
||||
.i2s_ck_bclk = i2s_ck_bclk,
|
||||
.sw_pll = &sw_pll
|
||||
};
|
||||
|
||||
// Initialise callback function pointers
|
||||
i2s_callback_group_t i2s_cb_group;
|
||||
i2s_cb_group.init = i2s_init;
|
||||
i2s_cb_group.restart_check = i2s_restart_check;
|
||||
i2s_cb_group.receive = i2s_receive;
|
||||
i2s_cb_group.send = i2s_send;
|
||||
i2s_cb_group.app_data = &app_data;
|
||||
|
||||
printf("Starting I2S slave\n");
|
||||
|
||||
// Make a test output to observe the recovered mclk divided down to the refclk frequency
|
||||
xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3;
|
||||
port_t p_recovered_ref_clk = PORT_I2S_DATA2;
|
||||
setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO, p_lrclk);
|
||||
|
||||
i2s_slave(
|
||||
&i2s_cb_group,
|
||||
p_i2s_dout,
|
||||
NUM_I2S_LINES,
|
||||
p_i2s_din,
|
||||
NUM_I2S_LINES,
|
||||
p_bclk,
|
||||
p_lrclk,
|
||||
i2s_ck_bclk);
|
||||
}
|
||||
20
lib_sw_pll/examples/i2s_slave_lut/src/main.xc
Normal file
20
lib_sw_pll/examples/i2s_slave_lut/src/main.xc
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2022-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <platform.h>
|
||||
#include <xs1.h>
|
||||
|
||||
extern void sw_pll_test(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
par
|
||||
{
|
||||
on tile[0]: par {
|
||||
}
|
||||
on tile[1]: par {
|
||||
sw_pll_test();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
16
lib_sw_pll/examples/i2s_slave_lut/src/register_setup.h
Normal file
16
lib_sw_pll/examples/i2s_slave_lut/src/register_setup.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/* Autogenerated by app_pll_model.py using command:
|
||||
/Users/ed/sandboxes/lib_sw_pll/python/sw_pll/pll_calc.py -i 24.0 -a -m 80 -t 12.288 -p 6.0 -e 5 -r --fracmin 0.695 --fracmax 0.905 --header
|
||||
Picked output solution #62
|
||||
Input freq: 24000000
|
||||
F: 203
|
||||
R: 1
|
||||
f: 3
|
||||
p: 4
|
||||
OD: 1
|
||||
ACD: 24
|
||||
Output freq: 12288000.0
|
||||
VCO freq: 2457600000.0 */
|
||||
|
||||
#define APP_PLL_CTL_REG 0x0880CB01
|
||||
#define APP_PLL_DIV_REG 0x80000018
|
||||
#define APP_PLL_FRAC_REG 0x80000304
|
||||
98
lib_sw_pll/examples/i2s_slave_lut/src/xvf3800_qf60.xn
Normal file
98
lib_sw_pll/examples/i2s_slave_lut/src/xvf3800_qf60.xn
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Network xmlns="http://www.xmos.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xmos.com http://www.xmos.com" ManuallySpecifiedRouting="true">
|
||||
<Type>Board</Type>
|
||||
<Name>XS3 MC Audio</Name>
|
||||
<Declarations>
|
||||
<Declaration>tileref tile[2]</Declaration>
|
||||
<Declaration>tileref usb_tile</Declaration>
|
||||
</Declarations>
|
||||
<Packages>
|
||||
<Package id="0" Type="XS3-UnA-1024-QF60A">
|
||||
<Nodes>
|
||||
<Node Id="0" InPackageId="0" Type="XS3-L16A-1024" Oscillator="24MHz" SystemFrequency="800MHz" referencefrequency="100MHz">
|
||||
<Boot>
|
||||
<Source Location="SPI:bootFlash"/>
|
||||
</Boot>
|
||||
<Tile Number="0" Reference="tile[0]">
|
||||
|
||||
<!-- USB Ports -->
|
||||
<Port Location="XS1_PORT_1H" Name="PORT_USB_TX_READYIN"/>
|
||||
<Port Location="XS1_PORT_1J" Name="PORT_USB_CLK"/>
|
||||
<Port Location="XS1_PORT_1K" Name="PORT_USB_TX_READYOUT"/>
|
||||
<Port Location="XS1_PORT_1I" Name="PORT_USB_RX_READY"/>
|
||||
<Port Location="XS1_PORT_1E" Name="PORT_USB_FLAG0"/>
|
||||
<Port Location="XS1_PORT_1F" Name="PORT_USB_FLAG1"/>
|
||||
<Port Location="XS1_PORT_8A" Name="PORT_USB_TXD"/>
|
||||
<Port Location="XS1_PORT_8B" Name="PORT_USB_RXD"/>
|
||||
|
||||
<!-- QSPI Ports -->
|
||||
<Port Location="XS1_PORT_1B" Name="PORT_SQI_CS"/>
|
||||
<Port Location="XS1_PORT_1C" Name="PORT_SQI_SCLK"/>
|
||||
<Port Location="XS1_PORT_4B" Name="PORT_SQI_SIO"/>
|
||||
|
||||
<!-- Mic related ports -->
|
||||
<Port Location="XS1_PORT_1M" Name="PORT_PDM_CLK"/>
|
||||
<Port Location="XS1_PORT_8D" Name="PORT_PDM_DATA"/> <!-- only bits 4,5,6 & 7 are used due to overlap with 1b ports -->
|
||||
<Port Location="XS1_PORT_1L" Name="PORT_PDM_MCLK"/>
|
||||
|
||||
<!-- I2C Master Ports -->
|
||||
<Port Location="XS1_PORT_1N" Name="PORT_I2C_SCL"/>
|
||||
<Port Location="XS1_PORT_1O" Name="PORT_I2C_SDA"/>
|
||||
|
||||
<!-- SPI Slave Ports -->
|
||||
<Port Location="XS1_PORT_1A" Name="PORT_SPI_SLAVE_CS"/>
|
||||
<Port Location="XS1_PORT_1C" Name="PORT_SPI_SLAVE_SCLK"/>
|
||||
<Port Location="XS1_PORT_1D" Name="PORT_SPI_SLAVE_MOSI"/>
|
||||
<Port Location="XS1_PORT_1P" Name="PORT_SPI_SLAVE_MISO"/>
|
||||
|
||||
<!-- GPIO Ports -->
|
||||
<Port Location="XS1_PORT_8C" Name="GPO_TILE_0_PORT_8C"/> <!-- only bits 3,4,5,6 & 7 brought out on pckg -->
|
||||
|
||||
</Tile>
|
||||
|
||||
<Tile Number="1" Reference="tile[1]">
|
||||
<!-- Audio Ports -->
|
||||
<Port Location="XS1_PORT_1D" Name="PORT_MCLK"/>
|
||||
<Port Location="XS1_PORT_1B" Name="PORT_I2S_LRCLK"/>
|
||||
<Port Location="XS1_PORT_1C" Name="PORT_I2S_BCLK"/>
|
||||
<Port Location="XS1_PORT_1A" Name="PORT_I2S_DATA0"/>
|
||||
<Port Location="XS1_PORT_1G" Name="PORT_I2S_DATA1"/>
|
||||
<Port Location="XS1_PORT_1K" Name="PORT_I2S_DATA2"/> <!-- Optional in some configs. NOTE shared with GPIO_TILE_1_PORT_1K -->
|
||||
|
||||
<!-- MCLK recovery port. This is used internally to count MCLKs and isn't pinned out -->
|
||||
<Port Location="XS1_PORT_32A" Name="PORT_MCLK_COUNT"/>
|
||||
|
||||
<!-- GPIO Ports -->
|
||||
<Port Location="XS1_PORT_4A" Name="GPI_TILE_1_PORT_4A"/> <!-- only bit 3 is pinned out on package -->
|
||||
<Port Location="XS1_PORT_1F" Name="GPIO_TILE_1_PORT_1F"/>
|
||||
<Port Location="XS1_PORT_1K" Name="GPIO_TILE_1_PORT_1K"/> <!-- NOTE shared with PORT_I2S_DATA2 -->
|
||||
|
||||
</Tile>
|
||||
</Node>
|
||||
</Nodes>
|
||||
</Package>
|
||||
</Packages>
|
||||
<Nodes>
|
||||
<Node Id="2" Type="device:" RoutingId="0x8000">
|
||||
<Service Id="0" Proto="xscope_host_data(chanend c);">
|
||||
<Chanend Identifier="c" end="3"/>
|
||||
</Service>
|
||||
</Node>
|
||||
</Nodes>
|
||||
<Links>
|
||||
<Link Encoding="2wire" Delays="4,4" Flags="XSCOPE">
|
||||
<LinkEndpoint NodeId="0" Link="XL0"/>
|
||||
<LinkEndpoint NodeId="2" Chanend="1"/>
|
||||
</Link>
|
||||
</Links>
|
||||
<ExternalDevices>
|
||||
<Device NodeId="0" Tile="0" Class="SQIFlash" Name="bootFlash">
|
||||
<Attribute Name="PORT_SQI_CS" Value="PORT_SQI_CS"/>
|
||||
<Attribute Name="PORT_SQI_SCLK" Value="PORT_SQI_SCLK"/>
|
||||
<Attribute Name="PORT_SQI_SIO" Value="PORT_SQI_SIO"/>
|
||||
</Device>
|
||||
</ExternalDevices>
|
||||
<JTAGChain>
|
||||
<JTAGDevice NodeId="0"/>
|
||||
</JTAGChain>
|
||||
</Network>
|
||||
60
lib_sw_pll/examples/shared/src/clock_gen.c
Normal file
60
lib_sw_pll/examples/shared/src/clock_gen.c
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <xs1.h>
|
||||
#include <stdio.h>
|
||||
#include <xcore/hwtimer.h>
|
||||
#include <xcore/port.h>
|
||||
|
||||
#include "sw_pll_common.h"
|
||||
|
||||
#define DO_CLOCKS \
|
||||
printf("Ref Hz: %d\n", clock_rate >> 1); \
|
||||
\
|
||||
unsigned cycle_ticks_int = XS1_TIMER_HZ / clock_rate; \
|
||||
unsigned cycle_ticks_remaidner = XS1_TIMER_HZ % clock_rate; \
|
||||
unsigned carry = 0; \
|
||||
\
|
||||
period_trig += XS1_TIMER_HZ * 1; \
|
||||
unsigned time_now = hwtimer_get_time(period_tmr); \
|
||||
while(TIMER_TIMEAFTER(period_trig, time_now)) \
|
||||
{ \
|
||||
port_out(p_clock_gen, port_val); \
|
||||
hwtimer_wait_until(clock_tmr, time_trig); \
|
||||
time_trig += cycle_ticks_int; \
|
||||
carry += cycle_ticks_remaidner; \
|
||||
if(carry >= clock_rate){ \
|
||||
time_trig++; \
|
||||
carry -= clock_rate; \
|
||||
} \
|
||||
port_val ^= 1; \
|
||||
time_now = hwtimer_get_time(period_tmr); \
|
||||
}
|
||||
|
||||
void clock_gen(unsigned ref_frequency, unsigned ppm_range) // Step from - to + this
|
||||
{
|
||||
unsigned clock_rate = ref_frequency * 2; // Note double because we generate edges at this rate
|
||||
|
||||
unsigned clock_rate_low = (unsigned)(clock_rate * (1.0 - (float)ppm_range / 1000000.0));
|
||||
unsigned clock_rate_high = (unsigned)(clock_rate * (1.0 + (float)ppm_range / 1000000.0));
|
||||
unsigned step_size = (clock_rate_high - clock_rate_low) / 20;
|
||||
|
||||
printf("Sweep range: %d %d %d, step size: %d\n", clock_rate_low / 2, clock_rate / 2, clock_rate_high / 2, step_size);
|
||||
|
||||
hwtimer_t period_tmr = hwtimer_alloc();
|
||||
unsigned period_trig = hwtimer_get_time(period_tmr);
|
||||
|
||||
hwtimer_t clock_tmr = hwtimer_alloc();
|
||||
unsigned time_trig = hwtimer_get_time(clock_tmr);
|
||||
|
||||
port_t p_clock_gen = PORT_I2S_BCLK;
|
||||
port_enable(p_clock_gen);
|
||||
unsigned port_val = 1;
|
||||
|
||||
for(unsigned clock_rate = clock_rate_low; clock_rate <= clock_rate_high; clock_rate += 2 * step_size){
|
||||
DO_CLOCKS
|
||||
}
|
||||
for(unsigned clock_rate = clock_rate_high; clock_rate > clock_rate_low; clock_rate -= 2 * step_size){
|
||||
DO_CLOCKS
|
||||
}
|
||||
}
|
||||
9
lib_sw_pll/examples/shared/src/clock_gen.h
Normal file
9
lib_sw_pll/examples/shared/src/clock_gen.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
// Runs a task in a thread that produces a clock that sweeps a reference clock
|
||||
// between + and - the ppm value specified.
|
||||
//
|
||||
// param ref_frequency Nominal frequency in Hz
|
||||
// param ppm_range The range to sweep
|
||||
void clock_gen(unsigned ref_frequency, unsigned ppm_range);
|
||||
43
lib_sw_pll/examples/shared/src/resource_setup.c
Normal file
43
lib_sw_pll/examples/shared/src/resource_setup.c
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <stdio.h>
|
||||
#include "resource_setup.h"
|
||||
|
||||
void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_clock_counter, xclock_t clk_ref_clk, port_t p_ref_clk_timing)
|
||||
{
|
||||
// Create clock from mclk port and use it to clock the p_clock_counter port.
|
||||
clock_enable(clk_mclk);
|
||||
port_enable(p_mclk);
|
||||
clock_set_source_port(clk_mclk, p_mclk);
|
||||
|
||||
// Clock p_clock_counter from MCLK
|
||||
port_enable(p_clock_counter);
|
||||
port_set_clock(p_clock_counter, clk_mclk);
|
||||
|
||||
clock_start(clk_mclk);
|
||||
|
||||
// Create clock from ref_clock_port and use it to clock the p_clock_counter port.
|
||||
clock_enable(clk_ref_clk);
|
||||
clock_set_source_port(clk_ref_clk, p_clock_counter);
|
||||
port_enable(p_ref_clk_timing);
|
||||
port_set_clock(p_ref_clk_timing, clk_ref_clk);
|
||||
|
||||
clock_start(clk_ref_clk);
|
||||
}
|
||||
|
||||
|
||||
void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider)
|
||||
{
|
||||
// Connect clock block with divide to mclk
|
||||
clock_enable(clk_recovered_ref_clk);
|
||||
clock_set_source_port(clk_recovered_ref_clk, p_mclk);
|
||||
clock_set_divide(clk_recovered_ref_clk, divider / 2);
|
||||
printf("Divider: %u\n", divider);
|
||||
|
||||
// Output the divided mclk on a port
|
||||
port_enable(p_recovered_ref_clk);
|
||||
port_set_clock(p_recovered_ref_clk, clk_recovered_ref_clk);
|
||||
port_set_out_clock(p_recovered_ref_clk);
|
||||
clock_start(clk_recovered_ref_clk);
|
||||
}
|
||||
28
lib_sw_pll/examples/shared/src/resource_setup.h
Normal file
28
lib_sw_pll/examples/shared/src/resource_setup.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <xcore/port.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
// Sets up the provided resources so that we can count PLL output clocks.
|
||||
// We do this by clocking the input reference clock port with the output from the PLL
|
||||
// and its internal counter is used to count the PLL clock cycles(normal timers cannot count custom clocks)
|
||||
// It also sets up a dummy port clocked by the input reference to act as a timing barrier so that
|
||||
// the output clock count can be precisely sampled.
|
||||
//
|
||||
// param p_mclk The mclk output port (Always P1D on tile[1])
|
||||
// param clk_mclk The clockblock for mclk out
|
||||
// param p_clock_counter The port used for counting mclks - in this case the ref input clock
|
||||
// param clk_ref_clk The clockblock for the timing barrier
|
||||
// param p_ref_clk_timing The port used for t he timing barrier
|
||||
void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_clock_counter, xclock_t clk_ref_clk, port_t p_ref_clk_timing);
|
||||
|
||||
// Sets up a divided version of the PLL output so it can visually be compared (eg. on a DSO)
|
||||
// with the input reference clock to the PLL
|
||||
//
|
||||
// param p_recovered_ref_clk The port used to drive the recovered and divided clock
|
||||
// param clk_recovered_ref_clk The clockblock used to drive the recovered and divided clock
|
||||
// param p_mclk The mclk output port (Always P1D on tile[1])
|
||||
// param divider The divide value from mclk to the divided output
|
||||
void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider);
|
||||
50
lib_sw_pll/examples/simple_lut/simple_lut.cmake
Normal file
50
lib_sw_pll/examples/simple_lut/simple_lut.cmake
Normal file
@@ -0,0 +1,50 @@
|
||||
#**********************
|
||||
# Gather Sources
|
||||
#**********************
|
||||
file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/*.xc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../shared/src/*.c )
|
||||
set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src
|
||||
${CMAKE_CURRENT_LIST_DIR}/../shared/src
|
||||
)
|
||||
|
||||
#**********************
|
||||
# Flags
|
||||
#**********************
|
||||
set(APP_COMPILER_FLAGS
|
||||
-Os
|
||||
-g
|
||||
-report
|
||||
-fxscope
|
||||
-mcmodel=large
|
||||
-Wno-xcore-fptrgroup
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/config.xscope
|
||||
-target=XCORE-AI-EXPLORER
|
||||
)
|
||||
|
||||
set(APP_COMPILE_DEFINITIONS
|
||||
DEBUG_PRINT_ENABLE=1
|
||||
PLATFORM_SUPPORTS_TILE_0=1
|
||||
PLATFORM_SUPPORTS_TILE_1=1
|
||||
PLATFORM_SUPPORTS_TILE_2=0
|
||||
PLATFORM_SUPPORTS_TILE_3=0
|
||||
PLATFORM_USES_TILE_0=1
|
||||
PLATFORM_USES_TILE_1=1
|
||||
)
|
||||
|
||||
set(APP_LINK_OPTIONS
|
||||
-report
|
||||
-target=XCORE-AI-EXPLORER
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/config.xscope
|
||||
)
|
||||
|
||||
#**********************
|
||||
# Tile Targets
|
||||
#**********************
|
||||
add_executable(simple_lut)
|
||||
target_sources(simple_lut PUBLIC ${APP_SOURCES})
|
||||
target_include_directories(simple_lut PUBLIC ${APP_INCLUDES})
|
||||
target_compile_definitions(simple_lut PRIVATE ${APP_COMPILE_DEFINITIONS})
|
||||
target_compile_options(simple_lut PRIVATE ${APP_COMPILER_FLAGS})
|
||||
target_link_options(simple_lut PRIVATE ${APP_LINK_OPTIONS})
|
||||
target_link_libraries(simple_lut PUBLIC lib_sw_pll)
|
||||
24
lib_sw_pll/examples/simple_lut/src/config.xscope
Normal file
24
lib_sw_pll/examples/simple_lut/src/config.xscope
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ======================================================= -->
|
||||
<!-- The 'ioMode' attribute on the xSCOPEconfig -->
|
||||
<!-- element can take the following values: -->
|
||||
<!-- "none", "basic", "timed" -->
|
||||
<!-- -->
|
||||
<!-- The 'type' attribute on Probe -->
|
||||
<!-- elements can take the following values: -->
|
||||
<!-- "STARTSTOP", "CONTINUOUS", "DISCRETE", "STATEMACHINE" -->
|
||||
<!-- -->
|
||||
<!-- The 'datatype' attribute on Probe -->
|
||||
<!-- elements can take the following values: -->
|
||||
<!-- "NONE", "UINT", "INT", "FLOAT" -->
|
||||
<!-- ======================================================= -->
|
||||
|
||||
<xSCOPEconfig ioMode="basic" enabled="true">
|
||||
|
||||
<!-- For example: -->
|
||||
<!-- <Probe name="I2S_RX_0" type="CONTINUOUS" datatype="INT" units="Value" enabled="true"/> -->
|
||||
|
||||
|
||||
<!-- From the target code, call: xscope_int(PROBE_NAME, value); -->
|
||||
</xSCOPEconfig>
|
||||
417
lib_sw_pll/examples/simple_lut/src/fractions.h
Normal file
417
lib_sw_pll/examples/simple_lut/src/fractions.h
Normal file
@@ -0,0 +1,417 @@
|
||||
// Header file listing fraction options searched
|
||||
// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register.
|
||||
short frac_values_80[413] = {
|
||||
0x0F16, // Index: 0 Fraction: 16/23 = 0.6957
|
||||
0x364E, // Index: 1 Fraction: 55/79 = 0.6962
|
||||
0x2637, // Index: 2 Fraction: 39/56 = 0.6964
|
||||
0x1620, // Index: 3 Fraction: 23/33 = 0.6970
|
||||
0x344B, // Index: 4 Fraction: 53/76 = 0.6974
|
||||
0x1D2A, // Index: 5 Fraction: 30/43 = 0.6977
|
||||
0x2434, // Index: 6 Fraction: 37/53 = 0.6981
|
||||
0x2B3E, // Index: 7 Fraction: 44/63 = 0.6984
|
||||
0x3248, // Index: 8 Fraction: 51/73 = 0.6986
|
||||
0x0609, // Index: 9 Fraction: 7/10 = 0.7000
|
||||
0x354C, // Index: 10 Fraction: 54/77 = 0.7013
|
||||
0x2E42, // Index: 11 Fraction: 47/67 = 0.7015
|
||||
0x2738, // Index: 12 Fraction: 40/57 = 0.7018
|
||||
0x202E, // Index: 13 Fraction: 33/47 = 0.7021
|
||||
0x1924, // Index: 14 Fraction: 26/37 = 0.7027
|
||||
0x2C3F, // Index: 15 Fraction: 45/64 = 0.7031
|
||||
0x121A, // Index: 16 Fraction: 19/27 = 0.7037
|
||||
0x3146, // Index: 17 Fraction: 50/71 = 0.7042
|
||||
0x1E2B, // Index: 18 Fraction: 31/44 = 0.7045
|
||||
0x2A3C, // Index: 19 Fraction: 43/61 = 0.7049
|
||||
0x364D, // Index: 20 Fraction: 55/78 = 0.7051
|
||||
0x0B10, // Index: 21 Fraction: 12/17 = 0.7059
|
||||
0x344A, // Index: 22 Fraction: 53/75 = 0.7067
|
||||
0x2839, // Index: 23 Fraction: 41/58 = 0.7069
|
||||
0x1C28, // Index: 24 Fraction: 29/41 = 0.7073
|
||||
0x2D40, // Index: 25 Fraction: 46/65 = 0.7077
|
||||
0x1017, // Index: 26 Fraction: 17/24 = 0.7083
|
||||
0x374E, // Index: 27 Fraction: 56/79 = 0.7089
|
||||
0x2636, // Index: 28 Fraction: 39/55 = 0.7091
|
||||
0x151E, // Index: 29 Fraction: 22/31 = 0.7097
|
||||
0x3044, // Index: 30 Fraction: 49/69 = 0.7101
|
||||
0x1A25, // Index: 31 Fraction: 27/38 = 0.7105
|
||||
0x1F2C, // Index: 32 Fraction: 32/45 = 0.7111
|
||||
0x2433, // Index: 33 Fraction: 37/52 = 0.7115
|
||||
0x293A, // Index: 34 Fraction: 42/59 = 0.7119
|
||||
0x2E41, // Index: 35 Fraction: 47/66 = 0.7121
|
||||
0x3348, // Index: 36 Fraction: 52/73 = 0.7123
|
||||
0x384F, // Index: 37 Fraction: 57/80 = 0.7125
|
||||
0x0406, // Index: 38 Fraction: 5/7 = 0.7143
|
||||
0x3449, // Index: 39 Fraction: 53/74 = 0.7162
|
||||
0x2F42, // Index: 40 Fraction: 48/67 = 0.7164
|
||||
0x2A3B, // Index: 41 Fraction: 43/60 = 0.7167
|
||||
0x2534, // Index: 42 Fraction: 38/53 = 0.7170
|
||||
0x202D, // Index: 43 Fraction: 33/46 = 0.7174
|
||||
0x1B26, // Index: 44 Fraction: 28/39 = 0.7179
|
||||
0x3246, // Index: 45 Fraction: 51/71 = 0.7183
|
||||
0x161F, // Index: 46 Fraction: 23/32 = 0.7188
|
||||
0x2838, // Index: 47 Fraction: 41/57 = 0.7193
|
||||
0x1118, // Index: 48 Fraction: 18/25 = 0.7200
|
||||
0x3043, // Index: 49 Fraction: 49/68 = 0.7206
|
||||
0x1E2A, // Index: 50 Fraction: 31/43 = 0.7209
|
||||
0x2B3C, // Index: 51 Fraction: 44/61 = 0.7213
|
||||
0x384E, // Index: 52 Fraction: 57/79 = 0.7215
|
||||
0x0C11, // Index: 53 Fraction: 13/18 = 0.7222
|
||||
0x2E40, // Index: 54 Fraction: 47/65 = 0.7231
|
||||
0x212E, // Index: 55 Fraction: 34/47 = 0.7234
|
||||
0x364B, // Index: 56 Fraction: 55/76 = 0.7237
|
||||
0x141C, // Index: 57 Fraction: 21/29 = 0.7241
|
||||
0x3144, // Index: 58 Fraction: 50/69 = 0.7246
|
||||
0x1C27, // Index: 59 Fraction: 29/40 = 0.7250
|
||||
0x2432, // Index: 60 Fraction: 37/51 = 0.7255
|
||||
0x2C3D, // Index: 61 Fraction: 45/62 = 0.7258
|
||||
0x3448, // Index: 62 Fraction: 53/73 = 0.7260
|
||||
0x070A, // Index: 63 Fraction: 8/11 = 0.7273
|
||||
0x3245, // Index: 64 Fraction: 51/70 = 0.7286
|
||||
0x2A3A, // Index: 65 Fraction: 43/59 = 0.7288
|
||||
0x222F, // Index: 66 Fraction: 35/48 = 0.7292
|
||||
0x1A24, // Index: 67 Fraction: 27/37 = 0.7297
|
||||
0x2D3E, // Index: 68 Fraction: 46/63 = 0.7302
|
||||
0x1219, // Index: 69 Fraction: 19/26 = 0.7308
|
||||
0x3042, // Index: 70 Fraction: 49/67 = 0.7313
|
||||
0x1D28, // Index: 71 Fraction: 30/41 = 0.7317
|
||||
0x2837, // Index: 72 Fraction: 41/56 = 0.7321
|
||||
0x3346, // Index: 73 Fraction: 52/71 = 0.7324
|
||||
0x0A0E, // Index: 74 Fraction: 11/15 = 0.7333
|
||||
0x394E, // Index: 75 Fraction: 58/79 = 0.7342
|
||||
0x2E3F, // Index: 76 Fraction: 47/64 = 0.7344
|
||||
0x2330, // Index: 77 Fraction: 36/49 = 0.7347
|
||||
0x1821, // Index: 78 Fraction: 25/34 = 0.7353
|
||||
0x2634, // Index: 79 Fraction: 39/53 = 0.7358
|
||||
0x3447, // Index: 80 Fraction: 53/72 = 0.7361
|
||||
0x0D12, // Index: 81 Fraction: 14/19 = 0.7368
|
||||
0x3A4F, // Index: 82 Fraction: 59/80 = 0.7375
|
||||
0x2C3C, // Index: 83 Fraction: 45/61 = 0.7377
|
||||
0x1E29, // Index: 84 Fraction: 31/42 = 0.7381
|
||||
0x2F40, // Index: 85 Fraction: 48/65 = 0.7385
|
||||
0x1016, // Index: 86 Fraction: 17/23 = 0.7391
|
||||
0x3548, // Index: 87 Fraction: 54/73 = 0.7397
|
||||
0x2431, // Index: 88 Fraction: 37/50 = 0.7400
|
||||
0x384C, // Index: 89 Fraction: 57/77 = 0.7403
|
||||
0x131A, // Index: 90 Fraction: 20/27 = 0.7407
|
||||
0x2A39, // Index: 91 Fraction: 43/58 = 0.7414
|
||||
0x161E, // Index: 92 Fraction: 23/31 = 0.7419
|
||||
0x3041, // Index: 93 Fraction: 49/66 = 0.7424
|
||||
0x1922, // Index: 94 Fraction: 26/35 = 0.7429
|
||||
0x3649, // Index: 95 Fraction: 55/74 = 0.7432
|
||||
0x1C26, // Index: 96 Fraction: 29/39 = 0.7436
|
||||
0x1F2A, // Index: 97 Fraction: 32/43 = 0.7442
|
||||
0x222E, // Index: 98 Fraction: 35/47 = 0.7447
|
||||
0x2532, // Index: 99 Fraction: 38/51 = 0.7451
|
||||
0x2836, // Index: 100 Fraction: 41/55 = 0.7455
|
||||
0x2B3A, // Index: 101 Fraction: 44/59 = 0.7458
|
||||
0x2E3E, // Index: 102 Fraction: 47/63 = 0.7460
|
||||
0x3142, // Index: 103 Fraction: 50/67 = 0.7463
|
||||
0x3446, // Index: 104 Fraction: 53/71 = 0.7465
|
||||
0x374A, // Index: 105 Fraction: 56/75 = 0.7467
|
||||
0x3A4E, // Index: 106 Fraction: 59/79 = 0.7468
|
||||
0x0203, // Index: 107 Fraction: 3/4 = 0.7500
|
||||
0x394C, // Index: 108 Fraction: 58/77 = 0.7532
|
||||
0x3648, // Index: 109 Fraction: 55/73 = 0.7534
|
||||
0x3344, // Index: 110 Fraction: 52/69 = 0.7536
|
||||
0x3040, // Index: 111 Fraction: 49/65 = 0.7538
|
||||
0x2D3C, // Index: 112 Fraction: 46/61 = 0.7541
|
||||
0x2A38, // Index: 113 Fraction: 43/57 = 0.7544
|
||||
0x2734, // Index: 114 Fraction: 40/53 = 0.7547
|
||||
0x2430, // Index: 115 Fraction: 37/49 = 0.7551
|
||||
0x212C, // Index: 116 Fraction: 34/45 = 0.7556
|
||||
0x1E28, // Index: 117 Fraction: 31/41 = 0.7561
|
||||
0x3A4D, // Index: 118 Fraction: 59/78 = 0.7564
|
||||
0x1B24, // Index: 119 Fraction: 28/37 = 0.7568
|
||||
0x3445, // Index: 120 Fraction: 53/70 = 0.7571
|
||||
0x1820, // Index: 121 Fraction: 25/33 = 0.7576
|
||||
0x2E3D, // Index: 122 Fraction: 47/62 = 0.7581
|
||||
0x151C, // Index: 123 Fraction: 22/29 = 0.7586
|
||||
0x2835, // Index: 124 Fraction: 41/54 = 0.7593
|
||||
0x3B4E, // Index: 125 Fraction: 60/79 = 0.7595
|
||||
0x1218, // Index: 126 Fraction: 19/25 = 0.7600
|
||||
0x3546, // Index: 127 Fraction: 54/71 = 0.7606
|
||||
0x222D, // Index: 128 Fraction: 35/46 = 0.7609
|
||||
0x3242, // Index: 129 Fraction: 51/67 = 0.7612
|
||||
0x0F14, // Index: 130 Fraction: 16/21 = 0.7619
|
||||
0x3C4F, // Index: 131 Fraction: 61/80 = 0.7625
|
||||
0x2C3A, // Index: 132 Fraction: 45/59 = 0.7627
|
||||
0x1C25, // Index: 133 Fraction: 29/38 = 0.7632
|
||||
0x2936, // Index: 134 Fraction: 42/55 = 0.7636
|
||||
0x3647, // Index: 135 Fraction: 55/72 = 0.7639
|
||||
0x0C10, // Index: 136 Fraction: 13/17 = 0.7647
|
||||
0x303F, // Index: 137 Fraction: 49/64 = 0.7656
|
||||
0x232E, // Index: 138 Fraction: 36/47 = 0.7660
|
||||
0x3A4C, // Index: 139 Fraction: 59/77 = 0.7662
|
||||
0x161D, // Index: 140 Fraction: 23/30 = 0.7667
|
||||
0x3748, // Index: 141 Fraction: 56/73 = 0.7671
|
||||
0x202A, // Index: 142 Fraction: 33/43 = 0.7674
|
||||
0x2A37, // Index: 143 Fraction: 43/56 = 0.7679
|
||||
0x3444, // Index: 144 Fraction: 53/69 = 0.7681
|
||||
0x090C, // Index: 145 Fraction: 10/13 = 0.7692
|
||||
0x3849, // Index: 146 Fraction: 57/74 = 0.7703
|
||||
0x2E3C, // Index: 147 Fraction: 47/61 = 0.7705
|
||||
0x242F, // Index: 148 Fraction: 37/48 = 0.7708
|
||||
0x1A22, // Index: 149 Fraction: 27/35 = 0.7714
|
||||
0x2B38, // Index: 150 Fraction: 44/57 = 0.7719
|
||||
0x3C4E, // Index: 151 Fraction: 61/79 = 0.7722
|
||||
0x1015, // Index: 152 Fraction: 17/22 = 0.7727
|
||||
0x394A, // Index: 153 Fraction: 58/75 = 0.7733
|
||||
0x2834, // Index: 154 Fraction: 41/53 = 0.7736
|
||||
0x171E, // Index: 155 Fraction: 24/31 = 0.7742
|
||||
0x3646, // Index: 156 Fraction: 55/71 = 0.7746
|
||||
0x1E27, // Index: 157 Fraction: 31/40 = 0.7750
|
||||
0x2530, // Index: 158 Fraction: 38/49 = 0.7755
|
||||
0x2C39, // Index: 159 Fraction: 45/58 = 0.7759
|
||||
0x3342, // Index: 160 Fraction: 52/67 = 0.7761
|
||||
0x3A4B, // Index: 161 Fraction: 59/76 = 0.7763
|
||||
0x0608, // Index: 162 Fraction: 7/9 = 0.7778
|
||||
0x3B4C, // Index: 163 Fraction: 60/77 = 0.7792
|
||||
0x3443, // Index: 164 Fraction: 53/68 = 0.7794
|
||||
0x2D3A, // Index: 165 Fraction: 46/59 = 0.7797
|
||||
0x2631, // Index: 166 Fraction: 39/50 = 0.7800
|
||||
0x1F28, // Index: 167 Fraction: 32/41 = 0.7805
|
||||
0x3848, // Index: 168 Fraction: 57/73 = 0.7808
|
||||
0x181F, // Index: 169 Fraction: 25/32 = 0.7812
|
||||
0x2A36, // Index: 170 Fraction: 43/55 = 0.7818
|
||||
0x3C4D, // Index: 171 Fraction: 61/78 = 0.7821
|
||||
0x1116, // Index: 172 Fraction: 18/23 = 0.7826
|
||||
0x2E3B, // Index: 173 Fraction: 47/60 = 0.7833
|
||||
0x1C24, // Index: 174 Fraction: 29/37 = 0.7838
|
||||
0x2732, // Index: 175 Fraction: 40/51 = 0.7843
|
||||
0x3240, // Index: 176 Fraction: 51/65 = 0.7846
|
||||
0x3D4E, // Index: 177 Fraction: 62/79 = 0.7848
|
||||
0x0A0D, // Index: 178 Fraction: 11/14 = 0.7857
|
||||
0x3A4A, // Index: 179 Fraction: 59/75 = 0.7867
|
||||
0x2F3C, // Index: 180 Fraction: 48/61 = 0.7869
|
||||
0x242E, // Index: 181 Fraction: 37/47 = 0.7872
|
||||
0x3E4F, // Index: 182 Fraction: 63/80 = 0.7875
|
||||
0x1920, // Index: 183 Fraction: 26/33 = 0.7879
|
||||
0x2833, // Index: 184 Fraction: 41/52 = 0.7885
|
||||
0x3746, // Index: 185 Fraction: 56/71 = 0.7887
|
||||
0x0E12, // Index: 186 Fraction: 15/19 = 0.7895
|
||||
0x303D, // Index: 187 Fraction: 49/62 = 0.7903
|
||||
0x212A, // Index: 188 Fraction: 34/43 = 0.7907
|
||||
0x3442, // Index: 189 Fraction: 53/67 = 0.7910
|
||||
0x1217, // Index: 190 Fraction: 19/24 = 0.7917
|
||||
0x3C4C, // Index: 191 Fraction: 61/77 = 0.7922
|
||||
0x2934, // Index: 192 Fraction: 42/53 = 0.7925
|
||||
0x161C, // Index: 193 Fraction: 23/29 = 0.7931
|
||||
0x313E, // Index: 194 Fraction: 50/63 = 0.7937
|
||||
0x1A21, // Index: 195 Fraction: 27/34 = 0.7941
|
||||
0x3948, // Index: 196 Fraction: 58/73 = 0.7945
|
||||
0x1E26, // Index: 197 Fraction: 31/39 = 0.7949
|
||||
0x222B, // Index: 198 Fraction: 35/44 = 0.7955
|
||||
0x2630, // Index: 199 Fraction: 39/49 = 0.7959
|
||||
0x2A35, // Index: 200 Fraction: 43/54 = 0.7963
|
||||
0x2E3A, // Index: 201 Fraction: 47/59 = 0.7966
|
||||
0x323F, // Index: 202 Fraction: 51/64 = 0.7969
|
||||
0x3644, // Index: 203 Fraction: 55/69 = 0.7971
|
||||
0x3A49, // Index: 204 Fraction: 59/74 = 0.7973
|
||||
0x3E4E, // Index: 205 Fraction: 63/79 = 0.7975
|
||||
0x0304, // Index: 206 Fraction: 4/5 = 0.8000
|
||||
0x3C4B, // Index: 207 Fraction: 61/76 = 0.8026
|
||||
0x3846, // Index: 208 Fraction: 57/71 = 0.8028
|
||||
0x3441, // Index: 209 Fraction: 53/66 = 0.8030
|
||||
0x303C, // Index: 210 Fraction: 49/61 = 0.8033
|
||||
0x2C37, // Index: 211 Fraction: 45/56 = 0.8036
|
||||
0x2832, // Index: 212 Fraction: 41/51 = 0.8039
|
||||
0x242D, // Index: 213 Fraction: 37/46 = 0.8043
|
||||
0x2028, // Index: 214 Fraction: 33/41 = 0.8049
|
||||
0x3D4C, // Index: 215 Fraction: 62/77 = 0.8052
|
||||
0x1C23, // Index: 216 Fraction: 29/36 = 0.8056
|
||||
0x3542, // Index: 217 Fraction: 54/67 = 0.8060
|
||||
0x181E, // Index: 218 Fraction: 25/31 = 0.8065
|
||||
0x2D38, // Index: 219 Fraction: 46/57 = 0.8070
|
||||
0x1419, // Index: 220 Fraction: 21/26 = 0.8077
|
||||
0x3A48, // Index: 221 Fraction: 59/73 = 0.8082
|
||||
0x252E, // Index: 222 Fraction: 38/47 = 0.8085
|
||||
0x3643, // Index: 223 Fraction: 55/68 = 0.8088
|
||||
0x1014, // Index: 224 Fraction: 17/21 = 0.8095
|
||||
0x3F4E, // Index: 225 Fraction: 64/79 = 0.8101
|
||||
0x2E39, // Index: 226 Fraction: 47/58 = 0.8103
|
||||
0x1D24, // Index: 227 Fraction: 30/37 = 0.8108
|
||||
0x2A34, // Index: 228 Fraction: 43/53 = 0.8113
|
||||
0x3744, // Index: 229 Fraction: 56/69 = 0.8116
|
||||
0x0C0F, // Index: 230 Fraction: 13/16 = 0.8125
|
||||
0x3C4A, // Index: 231 Fraction: 61/75 = 0.8133
|
||||
0x2F3A, // Index: 232 Fraction: 48/59 = 0.8136
|
||||
0x222A, // Index: 233 Fraction: 35/43 = 0.8140
|
||||
0x3845, // Index: 234 Fraction: 57/70 = 0.8143
|
||||
0x151A, // Index: 235 Fraction: 22/27 = 0.8148
|
||||
0x3440, // Index: 236 Fraction: 53/65 = 0.8154
|
||||
0x1E25, // Index: 237 Fraction: 31/38 = 0.8158
|
||||
0x2730, // Index: 238 Fraction: 40/49 = 0.8163
|
||||
0x303B, // Index: 239 Fraction: 49/60 = 0.8167
|
||||
0x3946, // Index: 240 Fraction: 58/71 = 0.8169
|
||||
0x080A, // Index: 241 Fraction: 9/11 = 0.8182
|
||||
0x3A47, // Index: 242 Fraction: 59/72 = 0.8194
|
||||
0x313C, // Index: 243 Fraction: 50/61 = 0.8197
|
||||
0x2831, // Index: 244 Fraction: 41/50 = 0.8200
|
||||
0x1F26, // Index: 245 Fraction: 32/39 = 0.8205
|
||||
0x3642, // Index: 246 Fraction: 55/67 = 0.8209
|
||||
0x161B, // Index: 247 Fraction: 23/28 = 0.8214
|
||||
0x3B48, // Index: 248 Fraction: 60/73 = 0.8219
|
||||
0x242C, // Index: 249 Fraction: 37/45 = 0.8222
|
||||
0x323D, // Index: 250 Fraction: 51/62 = 0.8226
|
||||
0x404E, // Index: 251 Fraction: 65/79 = 0.8228
|
||||
0x0D10, // Index: 252 Fraction: 14/17 = 0.8235
|
||||
0x3C49, // Index: 253 Fraction: 61/74 = 0.8243
|
||||
0x2E38, // Index: 254 Fraction: 47/57 = 0.8246
|
||||
0x2027, // Index: 255 Fraction: 33/40 = 0.8250
|
||||
0x333E, // Index: 256 Fraction: 52/63 = 0.8254
|
||||
0x1216, // Index: 257 Fraction: 19/23 = 0.8261
|
||||
0x3D4A, // Index: 258 Fraction: 62/75 = 0.8267
|
||||
0x2A33, // Index: 259 Fraction: 43/52 = 0.8269
|
||||
0x171C, // Index: 260 Fraction: 24/29 = 0.8276
|
||||
0x343F, // Index: 261 Fraction: 53/64 = 0.8281
|
||||
0x1C22, // Index: 262 Fraction: 29/35 = 0.8286
|
||||
0x3E4B, // Index: 263 Fraction: 63/76 = 0.8289
|
||||
0x2128, // Index: 264 Fraction: 34/41 = 0.8293
|
||||
0x262E, // Index: 265 Fraction: 39/47 = 0.8298
|
||||
0x2B34, // Index: 266 Fraction: 44/53 = 0.8302
|
||||
0x303A, // Index: 267 Fraction: 49/59 = 0.8305
|
||||
0x3540, // Index: 268 Fraction: 54/65 = 0.8308
|
||||
0x3A46, // Index: 269 Fraction: 59/71 = 0.8310
|
||||
0x3F4C, // Index: 270 Fraction: 64/77 = 0.8312
|
||||
0x0405, // Index: 271 Fraction: 5/6 = 0.8333
|
||||
0x414E, // Index: 272 Fraction: 66/79 = 0.8354
|
||||
0x3C48, // Index: 273 Fraction: 61/73 = 0.8356
|
||||
0x3742, // Index: 274 Fraction: 56/67 = 0.8358
|
||||
0x323C, // Index: 275 Fraction: 51/61 = 0.8361
|
||||
0x2D36, // Index: 276 Fraction: 46/55 = 0.8364
|
||||
0x2830, // Index: 277 Fraction: 41/49 = 0.8367
|
||||
0x232A, // Index: 278 Fraction: 36/43 = 0.8372
|
||||
0x424F, // Index: 279 Fraction: 67/80 = 0.8375
|
||||
0x1E24, // Index: 280 Fraction: 31/37 = 0.8378
|
||||
0x3843, // Index: 281 Fraction: 57/68 = 0.8382
|
||||
0x191E, // Index: 282 Fraction: 26/31 = 0.8387
|
||||
0x2E37, // Index: 283 Fraction: 47/56 = 0.8393
|
||||
0x1418, // Index: 284 Fraction: 21/25 = 0.8400
|
||||
0x3944, // Index: 285 Fraction: 58/69 = 0.8406
|
||||
0x242B, // Index: 286 Fraction: 37/44 = 0.8409
|
||||
0x343E, // Index: 287 Fraction: 53/63 = 0.8413
|
||||
0x0F12, // Index: 288 Fraction: 16/19 = 0.8421
|
||||
0x3A45, // Index: 289 Fraction: 59/70 = 0.8429
|
||||
0x2A32, // Index: 290 Fraction: 43/51 = 0.8431
|
||||
0x1A1F, // Index: 291 Fraction: 27/32 = 0.8438
|
||||
0x404C, // Index: 292 Fraction: 65/77 = 0.8442
|
||||
0x252C, // Index: 293 Fraction: 38/45 = 0.8444
|
||||
0x3039, // Index: 294 Fraction: 49/58 = 0.8448
|
||||
0x3B46, // Index: 295 Fraction: 60/71 = 0.8451
|
||||
0x0A0C, // Index: 296 Fraction: 11/13 = 0.8462
|
||||
0x3C47, // Index: 297 Fraction: 61/72 = 0.8472
|
||||
0x313A, // Index: 298 Fraction: 50/59 = 0.8475
|
||||
0x262D, // Index: 299 Fraction: 39/46 = 0.8478
|
||||
0x424E, // Index: 300 Fraction: 67/79 = 0.8481
|
||||
0x1B20, // Index: 301 Fraction: 28/33 = 0.8485
|
||||
0x2C34, // Index: 302 Fraction: 45/53 = 0.8491
|
||||
0x3D48, // Index: 303 Fraction: 62/73 = 0.8493
|
||||
0x1013, // Index: 304 Fraction: 17/20 = 0.8500
|
||||
0x3842, // Index: 305 Fraction: 57/67 = 0.8507
|
||||
0x272E, // Index: 306 Fraction: 40/47 = 0.8511
|
||||
0x3E49, // Index: 307 Fraction: 63/74 = 0.8514
|
||||
0x161A, // Index: 308 Fraction: 23/27 = 0.8519
|
||||
0x333C, // Index: 309 Fraction: 52/61 = 0.8525
|
||||
0x1C21, // Index: 310 Fraction: 29/34 = 0.8529
|
||||
0x3F4A, // Index: 311 Fraction: 64/75 = 0.8533
|
||||
0x2228, // Index: 312 Fraction: 35/41 = 0.8537
|
||||
0x282F, // Index: 313 Fraction: 41/48 = 0.8542
|
||||
0x2E36, // Index: 314 Fraction: 47/55 = 0.8545
|
||||
0x343D, // Index: 315 Fraction: 53/62 = 0.8548
|
||||
0x3A44, // Index: 316 Fraction: 59/69 = 0.8551
|
||||
0x404B, // Index: 317 Fraction: 65/76 = 0.8553
|
||||
0x0506, // Index: 318 Fraction: 6/7 = 0.8571
|
||||
0x424D, // Index: 319 Fraction: 67/78 = 0.8590
|
||||
0x3C46, // Index: 320 Fraction: 61/71 = 0.8592
|
||||
0x363F, // Index: 321 Fraction: 55/64 = 0.8594
|
||||
0x3038, // Index: 322 Fraction: 49/57 = 0.8596
|
||||
0x2A31, // Index: 323 Fraction: 43/50 = 0.8600
|
||||
0x242A, // Index: 324 Fraction: 37/43 = 0.8605
|
||||
0x434E, // Index: 325 Fraction: 68/79 = 0.8608
|
||||
0x1E23, // Index: 326 Fraction: 31/36 = 0.8611
|
||||
0x3740, // Index: 327 Fraction: 56/65 = 0.8615
|
||||
0x181C, // Index: 328 Fraction: 25/29 = 0.8621
|
||||
0x444F, // Index: 329 Fraction: 69/80 = 0.8625
|
||||
0x2B32, // Index: 330 Fraction: 44/51 = 0.8627
|
||||
0x3E48, // Index: 331 Fraction: 63/73 = 0.8630
|
||||
0x1215, // Index: 332 Fraction: 19/22 = 0.8636
|
||||
0x323A, // Index: 333 Fraction: 51/59 = 0.8644
|
||||
0x1F24, // Index: 334 Fraction: 32/37 = 0.8649
|
||||
0x2C33, // Index: 335 Fraction: 45/52 = 0.8654
|
||||
0x3942, // Index: 336 Fraction: 58/67 = 0.8657
|
||||
0x0C0E, // Index: 337 Fraction: 13/15 = 0.8667
|
||||
0x3A43, // Index: 338 Fraction: 59/68 = 0.8676
|
||||
0x2D34, // Index: 339 Fraction: 46/53 = 0.8679
|
||||
0x2025, // Index: 340 Fraction: 33/38 = 0.8684
|
||||
0x343C, // Index: 341 Fraction: 53/61 = 0.8689
|
||||
0x1316, // Index: 342 Fraction: 20/23 = 0.8696
|
||||
0x424C, // Index: 343 Fraction: 67/77 = 0.8701
|
||||
0x2E35, // Index: 344 Fraction: 47/54 = 0.8704
|
||||
0x1A1E, // Index: 345 Fraction: 27/31 = 0.8710
|
||||
0x3C45, // Index: 346 Fraction: 61/70 = 0.8714
|
||||
0x2126, // Index: 347 Fraction: 34/39 = 0.8718
|
||||
0x282E, // Index: 348 Fraction: 41/47 = 0.8723
|
||||
0x2F36, // Index: 349 Fraction: 48/55 = 0.8727
|
||||
0x363E, // Index: 350 Fraction: 55/63 = 0.8730
|
||||
0x3D46, // Index: 351 Fraction: 62/71 = 0.8732
|
||||
0x444E, // Index: 352 Fraction: 69/79 = 0.8734
|
||||
0x0607, // Index: 353 Fraction: 7/8 = 0.8750
|
||||
0x3F48, // Index: 354 Fraction: 64/73 = 0.8767
|
||||
0x3840, // Index: 355 Fraction: 57/65 = 0.8769
|
||||
0x3138, // Index: 356 Fraction: 50/57 = 0.8772
|
||||
0x2A30, // Index: 357 Fraction: 43/49 = 0.8776
|
||||
0x2328, // Index: 358 Fraction: 36/41 = 0.8780
|
||||
0x4049, // Index: 359 Fraction: 65/74 = 0.8784
|
||||
0x1C20, // Index: 360 Fraction: 29/33 = 0.8788
|
||||
0x3239, // Index: 361 Fraction: 51/58 = 0.8793
|
||||
0x1518, // Index: 362 Fraction: 22/25 = 0.8800
|
||||
0x3A42, // Index: 363 Fraction: 59/67 = 0.8806
|
||||
0x2429, // Index: 364 Fraction: 37/42 = 0.8810
|
||||
0x333A, // Index: 365 Fraction: 52/59 = 0.8814
|
||||
0x424B, // Index: 366 Fraction: 67/76 = 0.8816
|
||||
0x0E10, // Index: 367 Fraction: 15/17 = 0.8824
|
||||
0x434C, // Index: 368 Fraction: 68/77 = 0.8831
|
||||
0x343B, // Index: 369 Fraction: 53/60 = 0.8833
|
||||
0x252A, // Index: 370 Fraction: 38/43 = 0.8837
|
||||
0x3C44, // Index: 371 Fraction: 61/69 = 0.8841
|
||||
0x1619, // Index: 372 Fraction: 23/26 = 0.8846
|
||||
0x353C, // Index: 373 Fraction: 54/61 = 0.8852
|
||||
0x1E22, // Index: 374 Fraction: 31/35 = 0.8857
|
||||
0x454E, // Index: 375 Fraction: 70/79 = 0.8861
|
||||
0x262B, // Index: 376 Fraction: 39/44 = 0.8864
|
||||
0x2E34, // Index: 377 Fraction: 47/53 = 0.8868
|
||||
0x363D, // Index: 378 Fraction: 55/62 = 0.8871
|
||||
0x3E46, // Index: 379 Fraction: 63/71 = 0.8873
|
||||
0x464F, // Index: 380 Fraction: 71/80 = 0.8875
|
||||
0x0708, // Index: 381 Fraction: 8/9 = 0.8889
|
||||
0x4048, // Index: 382 Fraction: 65/73 = 0.8904
|
||||
0x383F, // Index: 383 Fraction: 57/64 = 0.8906
|
||||
0x3036, // Index: 384 Fraction: 49/55 = 0.8909
|
||||
0x282D, // Index: 385 Fraction: 41/46 = 0.8913
|
||||
0x2024, // Index: 386 Fraction: 33/37 = 0.8919
|
||||
0x3940, // Index: 387 Fraction: 58/65 = 0.8923
|
||||
0x181B, // Index: 388 Fraction: 25/28 = 0.8929
|
||||
0x424A, // Index: 389 Fraction: 67/75 = 0.8933
|
||||
0x292E, // Index: 390 Fraction: 42/47 = 0.8936
|
||||
0x3A41, // Index: 391 Fraction: 59/66 = 0.8939
|
||||
0x1012, // Index: 392 Fraction: 17/19 = 0.8947
|
||||
0x3B42, // Index: 393 Fraction: 60/67 = 0.8955
|
||||
0x2A2F, // Index: 394 Fraction: 43/48 = 0.8958
|
||||
0x444C, // Index: 395 Fraction: 69/77 = 0.8961
|
||||
0x191C, // Index: 396 Fraction: 26/29 = 0.8966
|
||||
0x3C43, // Index: 397 Fraction: 61/68 = 0.8971
|
||||
0x2226, // Index: 398 Fraction: 35/39 = 0.8974
|
||||
0x2B30, // Index: 399 Fraction: 44/49 = 0.8980
|
||||
0x343A, // Index: 400 Fraction: 53/59 = 0.8983
|
||||
0x3D44, // Index: 401 Fraction: 62/69 = 0.8986
|
||||
0x464E, // Index: 402 Fraction: 71/79 = 0.8987
|
||||
0x0809, // Index: 403 Fraction: 9/10 = 0.9000
|
||||
0x3F46, // Index: 404 Fraction: 64/71 = 0.9014
|
||||
0x363C, // Index: 405 Fraction: 55/61 = 0.9016
|
||||
0x2D32, // Index: 406 Fraction: 46/51 = 0.9020
|
||||
0x2428, // Index: 407 Fraction: 37/41 = 0.9024
|
||||
0x4047, // Index: 408 Fraction: 65/72 = 0.9028
|
||||
0x1B1E, // Index: 409 Fraction: 28/31 = 0.9032
|
||||
0x2E33, // Index: 410 Fraction: 47/52 = 0.9038
|
||||
0x4148, // Index: 411 Fraction: 66/73 = 0.9041
|
||||
0x1214, // Index: 412 Fraction: 19/21 = 0.9048
|
||||
};
|
||||
29
lib_sw_pll/examples/simple_lut/src/main.xc
Normal file
29
lib_sw_pll/examples/simple_lut/src/main.xc
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <platform.h>
|
||||
#include <xs1.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern void sw_pll_test(void);
|
||||
extern "C" {
|
||||
#include "clock_gen.h"
|
||||
}
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
par
|
||||
{
|
||||
on tile[0]: par {
|
||||
}
|
||||
on tile[1]: par {
|
||||
sw_pll_test();
|
||||
{
|
||||
clock_gen(48000, 500);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
16
lib_sw_pll/examples/simple_lut/src/register_setup.h
Normal file
16
lib_sw_pll/examples/simple_lut/src/register_setup.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/* Autogenerated by app_pll_model.py using command:
|
||||
/Users/ed/sandboxes/lib_sw_pll/python/sw_pll/pll_calc.py -i 24.0 -a -m 80 -t 12.288 -p 6.0 -e 5 -r --fracmin 0.695 --fracmax 0.905 --header
|
||||
Picked output solution #62
|
||||
Input freq: 24000000
|
||||
F: 203
|
||||
R: 1
|
||||
f: 3
|
||||
p: 4
|
||||
OD: 1
|
||||
ACD: 24
|
||||
Output freq: 12288000.0
|
||||
VCO freq: 2457600000.0 */
|
||||
|
||||
#define APP_PLL_CTL_REG 0x0880CB01
|
||||
#define APP_PLL_DIV_REG 0x80000018
|
||||
#define APP_PLL_FRAC_REG 0x80000304
|
||||
73
lib_sw_pll/examples/simple_lut/src/simple_sw_pll.c
Normal file
73
lib_sw_pll/examples/simple_lut/src/simple_sw_pll.c
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2022-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <xscope.h>
|
||||
#include <xs1.h>
|
||||
|
||||
#include "sw_pll.h"
|
||||
#include "resource_setup.h"
|
||||
|
||||
#define MCLK_FREQUENCY 12288000
|
||||
#define REF_FREQUENCY 48000
|
||||
#define PLL_RATIO (MCLK_FREQUENCY / REF_FREQUENCY)
|
||||
#define CONTROL_LOOP_COUNT 512
|
||||
#define PPM_RANGE 500
|
||||
|
||||
// These are generated from sw_pll_sim.py
|
||||
#include "fractions.h"
|
||||
#include "register_setup.h"
|
||||
|
||||
void sw_pll_test(void){
|
||||
|
||||
// Declare mclk and refclk resources and connect up
|
||||
port_t p_mclk = PORT_MCLK_IN;
|
||||
xclock_t clk_mclk = XS1_CLKBLK_1;
|
||||
port_t p_clock_counter = PORT_I2S_LRCLK;
|
||||
xclock_t clk_ref_clk = XS1_CLKBLK_2;
|
||||
port_t p_ref_clk_timing = XS1_PORT_32A;
|
||||
setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_clock_counter, clk_ref_clk, p_ref_clk_timing);
|
||||
|
||||
// Make a test output to observe the recovered mclk divided down to the refclk frequency
|
||||
xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3;
|
||||
port_t p_recovered_ref_clk = PORT_I2S_DAC_DATA;
|
||||
setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO);
|
||||
|
||||
sw_pll_state_t sw_pll;
|
||||
sw_pll_lut_init(&sw_pll,
|
||||
SW_PLL_15Q16(0.0),
|
||||
SW_PLL_15Q16(1.0),
|
||||
SW_PLL_15Q16(0.0),
|
||||
CONTROL_LOOP_COUNT,
|
||||
PLL_RATIO,
|
||||
0, /* No jitter compensation needed */
|
||||
frac_values_80,
|
||||
SW_PLL_NUM_LUT_ENTRIES(frac_values_80),
|
||||
APP_PLL_CTL_REG,
|
||||
APP_PLL_DIV_REG,
|
||||
SW_PLL_NUM_LUT_ENTRIES(frac_values_80) / 2,
|
||||
PPM_RANGE);
|
||||
|
||||
sw_pll_lock_status_t lock_status = SW_PLL_LOCKED;
|
||||
|
||||
uint32_t max_time = 0;
|
||||
while(1)
|
||||
{
|
||||
port_in(p_ref_clk_timing); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time.
|
||||
uint16_t mclk_pt = port_get_trigger_time(p_clock_counter);// Get the port timer val from p_clock_counter (which is clocked running from the PLL output).
|
||||
uint32_t t0 = get_reference_time();
|
||||
sw_pll_lut_do_control(&sw_pll, mclk_pt, 0);
|
||||
uint32_t t1 = get_reference_time();
|
||||
if(t1 - t0 > max_time){
|
||||
max_time = t1 - t0;
|
||||
printf("Max ticks taken: %lu\n", max_time);
|
||||
}
|
||||
|
||||
if(sw_pll.lock_status != lock_status){
|
||||
lock_status = sw_pll.lock_status;
|
||||
const char msg[3][16] = {"UNLOCKED LOW\0", "LOCKED\0", "UNLOCKED HIGH\0"};
|
||||
printf("%s\n", msg[lock_status+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
lib_sw_pll/examples/simple_sdm/simple_sdm.cmake
Normal file
50
lib_sw_pll/examples/simple_sdm/simple_sdm.cmake
Normal file
@@ -0,0 +1,50 @@
|
||||
#**********************
|
||||
# Gather Sources
|
||||
#**********************
|
||||
file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/*.xc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../shared/src/*.c )
|
||||
set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src
|
||||
${CMAKE_CURRENT_LIST_DIR}/../shared/src
|
||||
)
|
||||
|
||||
#**********************
|
||||
# Flags
|
||||
#**********************
|
||||
set(APP_COMPILER_FLAGS
|
||||
-Os
|
||||
-g
|
||||
-report
|
||||
-fxscope
|
||||
-mcmodel=large
|
||||
-Wno-xcore-fptrgroup
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/config.xscope
|
||||
-target=XCORE-AI-EXPLORER
|
||||
)
|
||||
|
||||
set(APP_COMPILE_DEFINITIONS
|
||||
DEBUG_PRINT_ENABLE=1
|
||||
PLATFORM_SUPPORTS_TILE_0=1
|
||||
PLATFORM_SUPPORTS_TILE_1=1
|
||||
PLATFORM_SUPPORTS_TILE_2=0
|
||||
PLATFORM_SUPPORTS_TILE_3=0
|
||||
PLATFORM_USES_TILE_0=1
|
||||
PLATFORM_USES_TILE_1=1
|
||||
)
|
||||
|
||||
set(APP_LINK_OPTIONS
|
||||
-report
|
||||
-target=XCORE-AI-EXPLORER
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/config.xscope
|
||||
)
|
||||
|
||||
#**********************
|
||||
# Tile Targets
|
||||
#**********************
|
||||
add_executable(simple_sdm)
|
||||
target_sources(simple_sdm PUBLIC ${APP_SOURCES})
|
||||
target_include_directories(simple_sdm PUBLIC ${APP_INCLUDES})
|
||||
target_compile_definitions(simple_sdm PRIVATE ${APP_COMPILE_DEFINITIONS})
|
||||
target_compile_options(simple_sdm PRIVATE ${APP_COMPILER_FLAGS})
|
||||
target_link_options(simple_sdm PRIVATE ${APP_LINK_OPTIONS})
|
||||
target_link_libraries(simple_sdm PUBLIC lib_sw_pll)
|
||||
24
lib_sw_pll/examples/simple_sdm/src/config.xscope
Normal file
24
lib_sw_pll/examples/simple_sdm/src/config.xscope
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ======================================================= -->
|
||||
<!-- The 'ioMode' attribute on the xSCOPEconfig -->
|
||||
<!-- element can take the following values: -->
|
||||
<!-- "none", "basic", "timed" -->
|
||||
<!-- -->
|
||||
<!-- The 'type' attribute on Probe -->
|
||||
<!-- elements can take the following values: -->
|
||||
<!-- "STARTSTOP", "CONTINUOUS", "DISCRETE", "STATEMACHINE" -->
|
||||
<!-- -->
|
||||
<!-- The 'datatype' attribute on Probe -->
|
||||
<!-- elements can take the following values: -->
|
||||
<!-- "NONE", "UINT", "INT", "FLOAT" -->
|
||||
<!-- ======================================================= -->
|
||||
|
||||
<xSCOPEconfig ioMode="basic" enabled="true">
|
||||
|
||||
<!-- For example: -->
|
||||
<!-- <Probe name="I2S_RX_0" type="CONTINUOUS" datatype="INT" units="Value" enabled="true"/> -->
|
||||
|
||||
|
||||
<!-- From the target code, call: xscope_int(PROBE_NAME, value); -->
|
||||
</xSCOPEconfig>
|
||||
33
lib_sw_pll/examples/simple_sdm/src/main.xc
Normal file
33
lib_sw_pll/examples/simple_sdm/src/main.xc
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <platform.h>
|
||||
#include <xs1.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern void sw_pll_sdm_test(chanend c_sdm_control);
|
||||
extern void sdm_task(chanend c_sdm_control);
|
||||
extern "C" {
|
||||
#include "clock_gen.h"
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
chan c_sdm_control;
|
||||
|
||||
par
|
||||
{
|
||||
on tile[0]: par {
|
||||
}
|
||||
|
||||
on tile[1]: par {
|
||||
sw_pll_sdm_test(c_sdm_control);
|
||||
sdm_task(c_sdm_control);
|
||||
{
|
||||
clock_gen(96000, 300);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
15
lib_sw_pll/examples/simple_sdm/src/register_setup.h
Normal file
15
lib_sw_pll/examples/simple_sdm/src/register_setup.h
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Autogenerated SDM App PLL setup by dco_model.py using 24.576_1M profile */
|
||||
/* Input freq: 24000000
|
||||
F: 146
|
||||
R: 0
|
||||
f: 4
|
||||
p: 10
|
||||
OD: 5
|
||||
ACD: 5
|
||||
*/
|
||||
|
||||
#define APP_PLL_CTL_REG 0x0A809200
|
||||
#define APP_PLL_DIV_REG 0x80000005
|
||||
#define APP_PLL_FRAC_REG 0x8000040A
|
||||
#define SW_PLL_SDM_CTRL_MID 478151
|
||||
#define SW_PLL_SDM_RATE 1000000
|
||||
137
lib_sw_pll/examples/simple_sdm/src/simple_sw_pll_sdm.c
Normal file
137
lib_sw_pll/examples/simple_sdm/src/simple_sw_pll_sdm.c
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2022-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <xscope.h>
|
||||
#include <xs1.h>
|
||||
#include <platform.h>
|
||||
|
||||
#include <xcore/select.h>
|
||||
|
||||
#include "sw_pll.h"
|
||||
#include "resource_setup.h"
|
||||
|
||||
#define MCLK_FREQUENCY 24576000
|
||||
#define REF_FREQUENCY 96000
|
||||
#define PLL_RATIO (MCLK_FREQUENCY / REF_FREQUENCY)
|
||||
#define CONTROL_LOOP_COUNT 512
|
||||
|
||||
#include "register_setup.h"
|
||||
|
||||
void sdm_task(chanend_t c_sdm_control){
|
||||
printf("sdm_task\n");
|
||||
|
||||
const uint32_t sdm_interval = XS1_TIMER_HZ / SW_PLL_SDM_RATE; // in 10ns ticks = 1MHz
|
||||
|
||||
sw_pll_sdm_state_t sdm_state;
|
||||
sw_pll_init_sigma_delta(&sdm_state);
|
||||
|
||||
tileref_t this_tile = get_local_tile_id();
|
||||
|
||||
hwtimer_t tmr = hwtimer_alloc();
|
||||
int32_t trigger_time = hwtimer_get_time(tmr) + sdm_interval;
|
||||
bool running = true;
|
||||
int32_t sdm_in = 0; // Zero is an invalid number and the SDM will not write the frac reg until
|
||||
// the first control value has been received. This avoids issues with
|
||||
// channel lockup if two tasks (eg. init and SDM) try to write at the same
|
||||
// time.
|
||||
|
||||
while(running){
|
||||
// Poll for new SDM control value
|
||||
SELECT_RES(
|
||||
CASE_THEN(c_sdm_control, ctrl_update),
|
||||
DEFAULT_THEN(default_handler)
|
||||
)
|
||||
{
|
||||
ctrl_update:
|
||||
{
|
||||
sdm_in = chan_in_word(c_sdm_control);
|
||||
}
|
||||
break;
|
||||
|
||||
default_handler:
|
||||
{
|
||||
// Do nothing & fall-through
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait until the timer value has been reached
|
||||
// This implements a timing barrier and keeps
|
||||
// the loop rate constant.
|
||||
hwtimer_wait_until(tmr, trigger_time);
|
||||
trigger_time += sdm_interval;
|
||||
|
||||
// Do not write to the frac reg until we get out first
|
||||
// control value. This will avoid the writing of the
|
||||
// frac reg from two different threads which may cause
|
||||
// a channel deadlock.
|
||||
if(sdm_in){
|
||||
sw_pll_do_sigma_delta(&sdm_state, this_tile, sdm_in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){
|
||||
chan_out_word(c_sdm_control, dco_ctl);
|
||||
}
|
||||
|
||||
void sw_pll_sdm_test(chanend_t c_sdm_control){
|
||||
|
||||
// Declare mclk and refclk resources and connect up
|
||||
port_t p_mclk = PORT_MCLK_IN;
|
||||
xclock_t clk_mclk = XS1_CLKBLK_1;
|
||||
port_t p_clock_counter = PORT_I2S_LRCLK;
|
||||
xclock_t clk_ref_clk = XS1_CLKBLK_2;
|
||||
port_t p_ref_clk_timing = XS1_PORT_32A;
|
||||
setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_clock_counter, clk_ref_clk, p_ref_clk_timing);
|
||||
|
||||
// Make a test output to observe the recovered mclk divided down to the refclk frequency
|
||||
xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3;
|
||||
port_t p_recovered_ref_clk = PORT_I2S_DAC_DATA;
|
||||
setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO);
|
||||
|
||||
sw_pll_state_t sw_pll;
|
||||
sw_pll_sdm_init(&sw_pll,
|
||||
SW_PLL_15Q16(0.0),
|
||||
SW_PLL_15Q16(32.0),
|
||||
SW_PLL_15Q16(0.25),
|
||||
CONTROL_LOOP_COUNT,
|
||||
PLL_RATIO,
|
||||
0, /* No jitter compensation needed */
|
||||
APP_PLL_CTL_REG,
|
||||
APP_PLL_DIV_REG,
|
||||
APP_PLL_FRAC_REG,
|
||||
SW_PLL_SDM_CTRL_MID,
|
||||
3000 /*PPM_RANGE FOR PFD*/);
|
||||
|
||||
sw_pll_lock_status_t lock_status = SW_PLL_LOCKED;
|
||||
|
||||
uint32_t max_time = 0;
|
||||
while(1)
|
||||
{
|
||||
port_in(p_ref_clk_timing); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time.
|
||||
uint16_t mclk_pt = port_get_trigger_time(p_clock_counter);// Get the port timer val from p_clock_counter (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK.
|
||||
|
||||
uint32_t t0 = get_reference_time();
|
||||
bool ctrl_done = sw_pll_sdm_do_control(&sw_pll, mclk_pt, 0);
|
||||
uint32_t t1 = get_reference_time();
|
||||
|
||||
if(ctrl_done){
|
||||
sw_pll_send_ctrl_to_sdm_task(c_sdm_control, sw_pll.sdm_state.current_ctrl_val);
|
||||
}
|
||||
|
||||
if(t1 - t0 > max_time){
|
||||
max_time = t1 - t0;
|
||||
printf("Max ticks taken: %lu\n", max_time);
|
||||
}
|
||||
|
||||
if(sw_pll.lock_status != lock_status){
|
||||
lock_status = sw_pll.lock_status;
|
||||
const char msg[3][16] = {"UNLOCKED LOW\0", "LOCKED\0", "UNLOCKED HIGH\0"};
|
||||
printf("%s\n", msg[lock_status+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
lib_sw_pll/lib_sw_pll/CMakeLists.txt
Normal file
44
lib_sw_pll/lib_sw_pll/CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
## Source files
|
||||
file(GLOB_RECURSE LIB_C_SOURCES src/*.c )
|
||||
file(GLOB_RECURSE LIB_CXX_SOURCES src/*.cc)
|
||||
file(GLOB_RECURSE LIB_XC_SOURCES src/*.xc)
|
||||
file(GLOB_RECURSE LIB_ASM_SOURCES src/*.S )
|
||||
|
||||
## cmake doesn't recognize .S files as assembly by default
|
||||
set_source_files_properties(LIB_ASM_SOURCES PROPERTIES LANGUAGE ASM)
|
||||
|
||||
## Assume all asm is XS3A for now
|
||||
set(XCORE_XS3A_SOURCES ${LIB_ASM_SOURCES})
|
||||
|
||||
## Set any local library compile options
|
||||
set(LIB_COMPILE_FLAGS "-Os" "-g")
|
||||
|
||||
## Includes files
|
||||
set(LIB_PUBLIC_INCLUDES api src)
|
||||
set(LIB_PRIVATE_INCLUDES src)
|
||||
|
||||
## Gather library sources
|
||||
set(LIB_PUBLIC_SOURCES "")
|
||||
set(LIB_PRIVATE_SOURCES ${LIB_C_SOURCES} ${LIB_CXX_SOURCES} ${LIB_XC_SOURCES})
|
||||
|
||||
## Append platform specific sources
|
||||
list(APPEND LIB_PRIVATE_SOURCES ${${CMAKE_SYSTEM_NAME}_SOURCES})
|
||||
|
||||
## Create library target
|
||||
add_library(lib_sw_pll STATIC)
|
||||
target_sources(lib_sw_pll
|
||||
PUBLIC
|
||||
${LIB_PUBLIC_SOURCES}
|
||||
PRIVATE
|
||||
${LIB_PRIVATE_SOURCES}
|
||||
)
|
||||
target_include_directories(lib_sw_pll
|
||||
PUBLIC
|
||||
${LIB_PUBLIC_INCLUDES}
|
||||
PRIVATE
|
||||
${LIB_PRIVATE_INCLUDES}
|
||||
)
|
||||
target_compile_options(lib_sw_pll
|
||||
PRIVATE
|
||||
${LIB_COMPILE_FLAGS}
|
||||
)
|
||||
317
lib_sw_pll/lib_sw_pll/api/sw_pll.h
Normal file
317
lib_sw_pll/lib_sw_pll/api/sw_pll.h
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2022-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <xccompat.h>
|
||||
|
||||
#ifdef __XC__
|
||||
#define _Bool uint8_t
|
||||
#else
|
||||
#include <xcore/hwtimer.h>
|
||||
#include <xcore/port.h>
|
||||
#include <xcore/clock.h>
|
||||
#include <xcore/channel.h>
|
||||
#include <xcore/assert.h>
|
||||
#endif
|
||||
|
||||
// SW_PLL Component includes
|
||||
#include "sw_pll_common.h"
|
||||
#include "sw_pll_pfd.h"
|
||||
#include "sw_pll_sdm.h"
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup sw_pll_lut sw_pll_lut
|
||||
*
|
||||
* The public API for using the Software PLL.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* sw_lut_pll initialisation function.
|
||||
*
|
||||
* This must be called before use of sw_pll_lut_do_control.
|
||||
* Call this passing a pointer to the sw_pll_state_t stuct declared locally.
|
||||
*
|
||||
* \param sw_pll Pointer to the struct to be initialised.
|
||||
* \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param loop_rate_count How many counts of the call to sw_pll_lut_do_control before control is done.
|
||||
* Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error
|
||||
* calls the control loop every time so this is ignored.
|
||||
* \param pll_ratio Integer ratio between input reference clock and the PLL output.
|
||||
* Only used by sw_pll_lut_do_control for the PFD. Don't care otherwise.
|
||||
* Used to calculate the expected port timer increment when control is called.
|
||||
* \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_lut_do_control is called.
|
||||
* Pass in zero if you are sure the mclk sampling timing is precise. This
|
||||
* will disable the scaling of the mclk count inside sw_pll_lut_do_control.
|
||||
* Only used by sw_pll_lut_do_control. Don't care otherwise.
|
||||
* \param lut_table_base Pointer to the base of the fractional PLL LUT used
|
||||
* \param num_lut_entries Number of entries in the LUT (half sizeof since entries are 16b)
|
||||
* \param app_pll_ctl_reg_val The setting of the app pll control register.
|
||||
* \param app_pll_div_reg_val The setting of the app pll divider register.
|
||||
* \param nominal_lut_idx The index into the LUT which gives the nominal output. Normally
|
||||
* close to halfway to allow symmetrical range.
|
||||
* \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation
|
||||
* of counted mclk before the PLL resets its state.
|
||||
* Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error
|
||||
* calls the control loop every time so this is ignored.
|
||||
*
|
||||
*/
|
||||
void sw_pll_lut_init( sw_pll_state_t * const sw_pll,
|
||||
const sw_pll_15q16_t Kp,
|
||||
const sw_pll_15q16_t Ki,
|
||||
const sw_pll_15q16_t Kii,
|
||||
const size_t loop_rate_count,
|
||||
const size_t pll_ratio,
|
||||
const uint32_t ref_clk_expected_inc,
|
||||
const int16_t * const lut_table_base,
|
||||
const size_t num_lut_entries,
|
||||
const uint32_t app_pll_ctl_reg_val,
|
||||
const uint32_t app_pll_div_reg_val,
|
||||
const unsigned nominal_lut_idx,
|
||||
const unsigned ppm_range);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sw_pll LUT version control function.
|
||||
*
|
||||
* It implements the PFD, controller and DCO output.
|
||||
*
|
||||
* This must be called periodically for every reference clock transition.
|
||||
* Typically, in an audio system, this would be at the I2S or reference clock input rate.
|
||||
* Eg. 16kHz, 48kHz ...
|
||||
*
|
||||
* When this is called, the control loop will be executed every n times (set by init) and the
|
||||
* application PLL will be adjusted to minimise the error seen on the mclk count value.
|
||||
*
|
||||
* If the precise sampling point of mclk is not easily controlled (for example in an I2S callback)
|
||||
* then an additional timer count may be passed in which will scale the mclk count. See i2s_slave
|
||||
* example to show how this is done. This will help reduce input jitter which, in turn, relates
|
||||
* to reduced output jitter.
|
||||
*
|
||||
* \param sw_pll Pointer to the sw_pll state struct.
|
||||
* \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_lut_do_control.
|
||||
* \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_lut_do_control. This value
|
||||
* is ignored when the pll is initialised with a zero ref_clk_expected_inc and the
|
||||
* control loop will assume that mclk_pt sample timing is precise.
|
||||
*
|
||||
* \returns The lock status of the PLL. Locked or unlocked high/low. Note that
|
||||
* this value is only updated when the control loop has run.
|
||||
* The type is sw_pll_lock_status_t.
|
||||
*/
|
||||
sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt);
|
||||
|
||||
/**
|
||||
* low level sw_pll control function for use as pure PLL control loop.
|
||||
*
|
||||
* This must be called periodically.
|
||||
*
|
||||
* When this is called, the control loop will be executed every time and the
|
||||
* application PLL will be adjusted to minimise the error seen on the input error value.
|
||||
*
|
||||
* \param sw_pll Pointer to the sw_pll state struct.
|
||||
* \param error 16b signed input error value
|
||||
* \returns The lock status of the PLL. Locked or unlocked high/low. Note that
|
||||
* this value is only updated when the control loop is running.
|
||||
* The type is sw_pll_lock_status_t.
|
||||
*/
|
||||
sw_pll_lock_status_t sw_pll_lut_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error);
|
||||
|
||||
|
||||
/**
|
||||
* Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings.
|
||||
*
|
||||
* Sets Kp, Ki and the windup limits. Note this resets the PFD accumulators too and so PI controller state is reset.
|
||||
*
|
||||
* \param sw_pll Pointer to the state struct to be reset.
|
||||
* \param Kp New Kp in sw_pll_15q16_t format.
|
||||
* \param Ki New Ki in sw_pll_15q16_t format.
|
||||
* \param Kii New Kii in sw_pll_15q16_t format.
|
||||
* \param num_lut_entries The number of elements in the sw_pll LUT.
|
||||
*/
|
||||
static inline void sw_pll_lut_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries)
|
||||
{
|
||||
sw_pll->pi_state.Kp = Kp;
|
||||
sw_pll->pi_state.Ki = Ki;
|
||||
sw_pll->pi_state.Kii = Kii;
|
||||
|
||||
sw_pll->pi_state.error_accum = 0;
|
||||
sw_pll->pi_state.error_accum_accum = 0;
|
||||
if(Ki){
|
||||
sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT
|
||||
}else{
|
||||
sw_pll->pi_state.i_windup_limit = 0;
|
||||
}
|
||||
if(Kii){
|
||||
sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT
|
||||
}else{
|
||||
sw_pll->pi_state.ii_windup_limit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**@}*/ // END: addtogroup sw_pll_lut
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup sw_pll_sdm sw_pll_sdm
|
||||
*
|
||||
* The public API for using the Software PLL.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* sw_pll_sdm initialisation function.
|
||||
*
|
||||
* This must be called before use of sw_pll_sdm_do_control or sw_pll_sdm_do_control_from_error.
|
||||
* Call this passing a pointer to the sw_pll_state_t stuct declared locally.
|
||||
*
|
||||
* \param sw_pll Pointer to the struct to be initialised.
|
||||
* \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param loop_rate_count How many counts of the call to sw_pll_sdm_do_control before control is done.
|
||||
* Note this is only used by sw_pll_sdm_do_control. sw_pll_sdm_do_control_from_error
|
||||
* calls the control loop every time so this is ignored.
|
||||
* \param pll_ratio Integer ratio between input reference clock and the PLL output.
|
||||
* Only used by sw_pll_sdm_do_control in the PFD. Don't care otherwise.
|
||||
* Used to calculate the expected port timer increment when control is called.
|
||||
* \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_sdm_do_control is called.
|
||||
* Pass in zero if you are sure the mclk sampling timing is precise. This
|
||||
* will disable the scaling of the mclk count inside sw_pll_sdm_do_control.
|
||||
* Only used by sw_pll_sdm_do_control. Don't care otherwise.
|
||||
* \param app_pll_ctl_reg_val The setting of the app pll control register.
|
||||
* \param app_pll_div_reg_val The setting of the app pll divider register.
|
||||
* \param app_pll_frac_reg_val The initial setting of the app pll fractional register.
|
||||
* \param ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally
|
||||
* close to halfway to allow symmetrical range.
|
||||
* \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation
|
||||
* of counted mclk before the PLL resets its state. Note this is only used
|
||||
* by sw_pll_sdm_do_control. sw_pll_sdm_do_control_from_error
|
||||
* calls the control loop every time so this is ignored.
|
||||
*
|
||||
*/
|
||||
void sw_pll_sdm_init(sw_pll_state_t * const sw_pll,
|
||||
const sw_pll_15q16_t Kp,
|
||||
const sw_pll_15q16_t Ki,
|
||||
const sw_pll_15q16_t Kii,
|
||||
const size_t loop_rate_count,
|
||||
const size_t pll_ratio,
|
||||
const uint32_t ref_clk_expected_inc,
|
||||
const uint32_t app_pll_ctl_reg_val,
|
||||
const uint32_t app_pll_div_reg_val,
|
||||
const uint32_t app_pll_frac_reg_val,
|
||||
const int32_t ctrl_mid_point,
|
||||
const unsigned ppm_range);
|
||||
|
||||
/**
|
||||
* sw_pll_sdm_do_control control function.
|
||||
*
|
||||
* It implements the PFD and controller and generates a DCO control value for the SDM.
|
||||
*
|
||||
* This must be called periodically for every reference clock transition.
|
||||
* Typically, in an audio system, this would be at the I2S or reference clock input rate.
|
||||
* Eg. 16kHz, 48kHz ...
|
||||
*
|
||||
* When this is called, the control loop will be executed every n times (set by init) and the
|
||||
* Sigma Delta Modulator control value will be set according the error seen on the mclk count value.
|
||||
*
|
||||
* If control is executed, TRUE is returned from the function and the value can be sent to the SDM.
|
||||
* The most recent calculated control output value can be found written to sw_pll->sdm_state.current_ctrl_val.
|
||||
*
|
||||
* If the precise sampling point of mclk is not easily controlled (for example in an I2S callback)
|
||||
* then an additional timer count may be passed in which will scale the mclk count. See i2s_slave
|
||||
* example to show how this is done. This will help reduce input jitter which, in turn, relates
|
||||
* to reduced output jitter.
|
||||
*
|
||||
* \param sw_pll Pointer to the sw_pll state struct.
|
||||
* \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_sdm_do_control.
|
||||
* \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_sdm_do_control. This value
|
||||
* is ignored when the pll is initialised with a zero ref_clk_expected_inc and the
|
||||
* control loop will assume that mclk_pt sample timing is precise.
|
||||
*
|
||||
* \returns Whether or not control was executed (controoled by loop_rate_count)
|
||||
*/
|
||||
bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt);
|
||||
|
||||
/**
|
||||
* low level sw_pll_sdm control function for use as pure PLL control loop.
|
||||
*
|
||||
* This must be called periodically.
|
||||
*
|
||||
* Takes the raw error input and applies the PI controller algorithm.
|
||||
* The most recent calculated control output value can be found written to sw_pll->sdm_state.current_ctrl_val.
|
||||
*
|
||||
* \param sw_pll Pointer to the sw_pll state struct.
|
||||
* \param error 16b signed input error value
|
||||
* \returns The controller lock status
|
||||
*/
|
||||
sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error);
|
||||
|
||||
|
||||
/**
|
||||
* Use to initialise the core sigma delta modulator. Broken out as seperate API as the SDM
|
||||
* is usually run in a dedicated thread which could be on a remote tile.
|
||||
*
|
||||
* \param sw_pll Pointer to the SDM state struct.
|
||||
*/
|
||||
void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state);
|
||||
|
||||
|
||||
#ifdef __DOXYGEN__
|
||||
/**
|
||||
* Performs the Sigma Delta Modulation from a control input.
|
||||
* It performs the SDM algorithm, converts the output to a fractional register setting
|
||||
* and then writes the value to the PLL fractional register.
|
||||
* Is typically called in a constant period fast loop and run from a dedicated thread which could be on a remote tile.
|
||||
*
|
||||
* NOTE: Attempting to write the PLL fractional register from more than
|
||||
* one logical core at the same time may result in channel lock-up.
|
||||
* Please ensure the that PLL initiaisation has completed before
|
||||
* the SDM task writes to the register. The provided `simple_sdm` example
|
||||
* implements a method for doing this.
|
||||
*
|
||||
* \param sw_pll Pointer to the SDM state struct.
|
||||
* \param this_tile The ID of the xcore tile that is doing the write.
|
||||
* Use get_local_tile_id() to obtain this.
|
||||
* \param sdm_control_in Current control value.
|
||||
*/
|
||||
static inline void sw_pll_do_sigma_delta(sw_pll_sdm_state_t *sdm_state, tileref_t this_tile, int32_t sdm_control_in);
|
||||
#endif
|
||||
|
||||
/**@}*/ // END: addtogroup sw_pll_sdm
|
||||
|
||||
/**
|
||||
* \addtogroup sw_pll_common sw_pll_common
|
||||
*
|
||||
* The public API for using the Software PLL.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resets PI controller state
|
||||
*
|
||||
* \param sw_pll Pointer to the Software PLL state.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
inline void sw_pll_reset_pi_state(sw_pll_state_t * const sw_pll)
|
||||
{
|
||||
sw_pll->pi_state.error_accum = 0;
|
||||
sw_pll->pi_state.error_accum_accum = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a fixed (not phase locked) clock between 11.2896 MHz and 49.152 MHz.
|
||||
* Assumes a 24 MHz XTAL.
|
||||
*
|
||||
* \param frequency Frequency in Hz. An incorrect value will assert.
|
||||
*/
|
||||
void sw_pll_fixed_clock(const unsigned frequency);
|
||||
|
||||
/**@}*/ // END: addtogroup sw_pll_common
|
||||
7
lib_sw_pll/lib_sw_pll/lib_build_info.cmake
Normal file
7
lib_sw_pll/lib_sw_pll/lib_build_info.cmake
Normal file
@@ -0,0 +1,7 @@
|
||||
set(LIB_NAME lib_sw_pll)
|
||||
set(LIB_VERSION 2.2.0)
|
||||
set(LIB_INCLUDES api src)
|
||||
set(LIB_COMPILER_FLAGS -Os -g)
|
||||
set(LIB_DEPENDENT_MODULES "")
|
||||
|
||||
XMOS_REGISTER_MODULE()
|
||||
14
lib_sw_pll/lib_sw_pll/module_build_info
Normal file
14
lib_sw_pll/lib_sw_pll/module_build_info
Normal file
@@ -0,0 +1,14 @@
|
||||
VERSION = 2.2.0
|
||||
|
||||
DEPENDENT_MODULES =
|
||||
|
||||
MODULE_XCC_FLAGS = $(XCC_FLAGS)
|
||||
|
||||
OPTIONAL_HEADERS +=
|
||||
|
||||
EXPORT_INCLUDE_DIRS = api \
|
||||
src
|
||||
|
||||
INCLUDE_DIRS = $(EXPORT_INCLUDE_DIRS)
|
||||
|
||||
SOURCE_DIRS = src
|
||||
138
lib_sw_pll/lib_sw_pll/src/sw_pll_common.c
Normal file
138
lib_sw_pll/lib_sw_pll/src/sw_pll_common.c
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#ifdef __XS3A__
|
||||
|
||||
#include "sw_pll.h"
|
||||
|
||||
// Implement a delay in 100MHz timer ticks without using a timer resource
|
||||
static void blocking_delay(const uint32_t delay_ticks)
|
||||
{
|
||||
uint32_t time_delay = get_reference_time() + delay_ticks;
|
||||
while(TIMER_TIMEAFTER(time_delay, get_reference_time()));
|
||||
}
|
||||
|
||||
|
||||
// Set secondary (App) PLL control register safely to work around chip bug.
|
||||
void sw_pll_app_pll_init(const unsigned tileid,
|
||||
const uint32_t app_pll_ctl_reg_val,
|
||||
const uint32_t app_pll_div_reg_val,
|
||||
const uint16_t frac_val_nominal)
|
||||
{
|
||||
// Disable the PLL
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF));
|
||||
// Enable the PLL to invoke a reset on the appPLL.
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val);
|
||||
// Must write the CTL register twice so that the F and R divider values are captured using a running clock.
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val);
|
||||
// Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values.
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF));
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val);
|
||||
|
||||
// Wait for PLL to settle.
|
||||
blocking_delay(500 * XS1_TIMER_MHZ);
|
||||
|
||||
// Write the fractional-n register and set to nominal
|
||||
// We set the top bit to enable the frac-n block.
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | frac_val_nominal));
|
||||
// And then write the clock divider register to enable the output
|
||||
write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, app_pll_div_reg_val);
|
||||
}
|
||||
|
||||
|
||||
//Found solution: IN 24.000MHz, OUT 49.151786MHz, VCO 3145.71MHz, RD 1, FD 131.071 (m = 1, n = 14), OD 8, FOD 2, ERR -4.36ppm
|
||||
// Measure: 100Hz-40kHz: ~7ps
|
||||
// 100Hz-1MHz: 70ps.
|
||||
// 100Hz high pass: 118ps.
|
||||
#define APP_PLL_CTL_49M 0x0B808200
|
||||
#define APP_PLL_DIV_49M 0x80000001
|
||||
#define APP_PLL_FRAC_49M 0x8000000D
|
||||
|
||||
//Found solution: IN 24.000MHz, OUT 45.157895MHz, VCO 2709.47MHz, RD 1, FD 112.895 (m = 17, n = 19), OD 5, FOD 3, ERR -11.19ppm
|
||||
// Measure: 100Hz-40kHz: 6.5ps
|
||||
// 100Hz-1MHz: 67ps.
|
||||
// 100Hz high pass: 215ps.
|
||||
#define APP_PLL_CTL_45M 0x0A006F00
|
||||
#define APP_PLL_DIV_45M 0x80000002
|
||||
#define APP_PLL_FRAC_45M 0x80001012
|
||||
|
||||
// Found solution: IN 24.000MHz, OUT 24.576000MHz, VCO 2457.60MHz, RD 1, FD 102.400 (m = 2, n = 5), OD 5, FOD 5, ERR 0.0ppm
|
||||
// Measure: 100Hz-40kHz: ~8ps
|
||||
// 100Hz-1MHz: 63ps.
|
||||
// 100Hz high pass: 127ps.
|
||||
#define APP_PLL_CTL_24M 0x0A006500
|
||||
#define APP_PLL_DIV_24M 0x80000004
|
||||
#define APP_PLL_FRAC_24M 0x80000104
|
||||
|
||||
// Found solution: IN 24.000MHz, OUT 22.579186MHz, VCO 3522.35MHz, RD 1, FD 146.765 (m = 13, n = 17), OD 3, FOD 13, ERR -0.641ppm
|
||||
// Measure: 100Hz-40kHz: 7ps
|
||||
// 100Hz-1MHz: 67ps.
|
||||
// 100Hz high pass: 260ps.
|
||||
#define APP_PLL_CTL_22M 0x09009100
|
||||
#define APP_PLL_DIV_22M 0x8000000C
|
||||
#define APP_PLL_FRAC_22M 0x80000C10
|
||||
|
||||
#define APP_PLL_CTL_12M 0x0A006500
|
||||
#define APP_PLL_DIV_12M 0x80000009
|
||||
#define APP_PLL_FRAC_12M 0x80000104
|
||||
|
||||
#define APP_PLL_CTL_11M 0x09009100
|
||||
#define APP_PLL_DIV_11M 0x80000019
|
||||
#define APP_PLL_FRAC_11M 0x80000C10
|
||||
|
||||
// Setup a fixed clock (not phase locked)
|
||||
void sw_pll_fixed_clock(const unsigned frequency)
|
||||
{
|
||||
unsigned ctrl = 0;
|
||||
unsigned div = 0;
|
||||
unsigned frac = 0;
|
||||
|
||||
|
||||
switch(frequency)
|
||||
{
|
||||
case 44100*256:
|
||||
ctrl = APP_PLL_CTL_11M;
|
||||
div = APP_PLL_DIV_11M;
|
||||
frac = APP_PLL_FRAC_11M;
|
||||
break;
|
||||
|
||||
case 48000*256:
|
||||
ctrl = APP_PLL_CTL_12M;
|
||||
div = APP_PLL_DIV_12M;
|
||||
frac = APP_PLL_FRAC_12M;
|
||||
break;
|
||||
|
||||
case 44100*512:
|
||||
ctrl = APP_PLL_CTL_22M;
|
||||
div = APP_PLL_DIV_22M;
|
||||
frac = APP_PLL_FRAC_22M;
|
||||
break;
|
||||
|
||||
case 48000*512:
|
||||
ctrl = APP_PLL_CTL_24M;
|
||||
div = APP_PLL_DIV_24M;
|
||||
frac = APP_PLL_FRAC_24M;
|
||||
break;
|
||||
|
||||
case 44100*1024:
|
||||
ctrl = APP_PLL_CTL_45M;
|
||||
div = APP_PLL_DIV_45M;
|
||||
frac = APP_PLL_FRAC_45M;
|
||||
break;
|
||||
|
||||
case 48000*1024:
|
||||
ctrl = APP_PLL_CTL_49M;
|
||||
div = APP_PLL_DIV_49M;
|
||||
frac = APP_PLL_FRAC_49M;
|
||||
break;
|
||||
|
||||
default:
|
||||
xassert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
sw_pll_app_pll_init(get_local_tile_id(), ctrl, div, frac);
|
||||
}
|
||||
|
||||
|
||||
#endif // __XS3A__
|
||||
123
lib_sw_pll/lib_sw_pll/src/sw_pll_common.h
Normal file
123
lib_sw_pll/lib_sw_pll/src/sw_pll_common.h
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2023-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#pragma once
|
||||
|
||||
// The number of consecutive lock positive reports of the control loop before declaring we are finally locked
|
||||
#define SW_PLL_LOCK_COUNT 10
|
||||
|
||||
// Helpers used in this module
|
||||
#define TIMER_TIMEAFTER(A, B) ((int)((B) - (A)) < 0) // Returns non-zero if A is after B, accounting for wrap
|
||||
#define PORT_TIMEAFTER(NOW, EVENT_TIME) ((int16_t)((EVENT_TIME) - (NOW)) < 0) // Returns non-zero if A is after B, accounting for wrap
|
||||
#define MAGNITUDE(A) (A < 0 ? -A : A) // Removes the sign of a value
|
||||
|
||||
typedef int32_t sw_pll_15q16_t; // Type for 15.16 signed fixed point
|
||||
|
||||
#define SW_PLL_NUM_FRAC_BITS 16
|
||||
#define SW_PLL_15Q16(val) ((sw_pll_15q16_t)((float)val * (1 << SW_PLL_NUM_FRAC_BITS)))
|
||||
#define SW_PLL_NUM_LUT_ENTRIES(lut_array) (sizeof(lut_array) / sizeof(lut_array[0]))
|
||||
|
||||
// This is just here to catch an error and provide useful info if you happen to forget to include from XC properly
|
||||
typedef struct xc_check{
|
||||
int *xc_check; // If you see this error, then you need to extern "C"{} the sw_pll include in your XC file.
|
||||
} xc_check;
|
||||
|
||||
typedef enum sw_pll_lock_status_t{
|
||||
SW_PLL_UNLOCKED_LOW = -1,
|
||||
SW_PLL_LOCKED = 0,
|
||||
SW_PLL_UNLOCKED_HIGH = 1
|
||||
} sw_pll_lock_status_t;
|
||||
|
||||
typedef struct sw_pll_pfd_state_t{
|
||||
int16_t mclk_diff; // Raw difference between mclk count and expected mclk count
|
||||
uint16_t ref_clk_pt_last; // Last ref clock value
|
||||
uint32_t ref_clk_expected_inc; // Expected ref clock increment
|
||||
uint64_t ref_clk_scaling_numerator; // Used for a cheap pre-computed divide rather than runtime divide
|
||||
uint16_t mclk_pt_last; // The last mclk port timer count
|
||||
uint32_t mclk_expected_pt_inc; // Expected increment of port timer count
|
||||
uint16_t mclk_max_diff; // Maximum mclk_diff before control loop decides to skip that iteration
|
||||
} sw_pll_pfd_state_t;
|
||||
|
||||
typedef struct sw_pll_pi_state_t{
|
||||
sw_pll_15q16_t Kp; // Proportional constant
|
||||
sw_pll_15q16_t Ki; // Integral constant
|
||||
sw_pll_15q16_t Kii; // Double integral constant
|
||||
int32_t i_windup_limit; // Integral term windup limit
|
||||
int32_t ii_windup_limit; // Double integral term windup limit
|
||||
int32_t error_accum; // Accumulation of the raw mclk_diff term (for I)
|
||||
int32_t error_accum_accum; // Accumulation of the raw mclk_diff term (for II)
|
||||
int32_t iir_y; // Optional IIR low pass filter state
|
||||
} sw_pll_pi_state_t;
|
||||
|
||||
typedef struct sw_pll_lut_state_t{
|
||||
const int16_t * lut_table_base; // Pointer to the base of the fractional look up table
|
||||
size_t num_lut_entries; // Number of LUT entries
|
||||
unsigned nominal_lut_idx; // Initial (mid point normally) LUT index
|
||||
uint16_t current_reg_val; // Last value sent to the register, used by tests
|
||||
} sw_pll_lut_state_t;
|
||||
|
||||
|
||||
typedef struct sw_pll_sdm_state_t{
|
||||
int32_t current_ctrl_val; // The last control value calculated
|
||||
int32_t ctrl_mid_point; // The mid point for the DCO input
|
||||
int32_t ds_x1; // Sigma delta modulator state
|
||||
int32_t ds_x2; // Sigma delta modulator state
|
||||
int32_t ds_x3; // Sigma delta modulator state
|
||||
} sw_pll_sdm_state_t;
|
||||
|
||||
|
||||
typedef struct sw_pll_state_t{
|
||||
|
||||
sw_pll_lock_status_t lock_status; // State showing whether the PLL has locked or is under/over
|
||||
uint8_t lock_counter; // Counter used to determine lock status
|
||||
uint8_t first_loop; // Flag which indicates if the sw_pll is initialising or not
|
||||
unsigned loop_rate_count; // How often the control loop logic runs compared to control call rate
|
||||
unsigned loop_counter; // Intenal loop counter to determine when to do control
|
||||
|
||||
sw_pll_pfd_state_t pfd_state; // Phase Frequency Detector
|
||||
sw_pll_pi_state_t pi_state; // PI(II) controller
|
||||
sw_pll_lut_state_t lut_state; // Look Up Table based DCO
|
||||
sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO
|
||||
|
||||
}sw_pll_state_t;
|
||||
|
||||
/**
|
||||
* This is the core PI controller code used by both SDM and LUT SW PLLs.
|
||||
*
|
||||
* \param sw_pll Pointer to the Software PLL state.
|
||||
* \param error The error input to the PI controller.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
inline int32_t sw_pll_do_pi_ctrl(sw_pll_state_t * const sw_pll, int16_t error)
|
||||
{
|
||||
sw_pll->pi_state.error_accum += error; // Integral error.
|
||||
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;
|
||||
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;
|
||||
|
||||
sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error.
|
||||
sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum;
|
||||
sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum;
|
||||
|
||||
// Use long long maths to avoid overflow if ever we had a large error accum term
|
||||
int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error);
|
||||
int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum);
|
||||
int64_t error_ii = ((int64_t)sw_pll->pi_state.Kii * (int64_t)sw_pll->pi_state.error_accum_accum);
|
||||
|
||||
// Convert back to 32b since we are handling LUTs of around a hundred entries
|
||||
int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS);
|
||||
|
||||
return total_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the application (secondary) PLL.
|
||||
*
|
||||
* \param tileid The resource ID of the tile that calls this function.
|
||||
* \param app_pll_ctl_reg_val The App PLL control register setting.
|
||||
* \param app_pll_div_reg_val The App PLL divider register setting.
|
||||
* \param frac_val_nominal The App PLL initial fractional register setting.
|
||||
*/ void sw_pll_app_pll_init( const unsigned tileid,
|
||||
const uint32_t app_pll_ctl_reg_val,
|
||||
const uint32_t app_pll_div_reg_val,
|
||||
const uint16_t frac_val_nominal);
|
||||
|
||||
129
lib_sw_pll/lib_sw_pll/src/sw_pll_lut.c
Normal file
129
lib_sw_pll/lib_sw_pll/src/sw_pll_lut.c
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2022-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#ifdef __XS3A__
|
||||
|
||||
#include "sw_pll.h"
|
||||
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int32_t total_error)
|
||||
{
|
||||
const int set = (sw_pll->lut_state.nominal_lut_idx - total_error); //Notice negative term for error
|
||||
unsigned int frac_index = 0;
|
||||
|
||||
if (set < 0)
|
||||
{
|
||||
frac_index = 0;
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
|
||||
}
|
||||
else if (set >= sw_pll->lut_state.num_lut_entries)
|
||||
{
|
||||
frac_index = sw_pll->lut_state.num_lut_entries - 1;
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_HIGH;
|
||||
}
|
||||
else
|
||||
{
|
||||
frac_index = set;
|
||||
if(sw_pll->lock_counter){
|
||||
sw_pll->lock_counter--;
|
||||
// Keep last unlocked status
|
||||
}
|
||||
else
|
||||
{
|
||||
sw_pll->lock_status = SW_PLL_LOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
return sw_pll->lut_state.lut_table_base[frac_index];
|
||||
}
|
||||
|
||||
void sw_pll_lut_init( sw_pll_state_t * const sw_pll,
|
||||
const sw_pll_15q16_t Kp,
|
||||
const sw_pll_15q16_t Ki,
|
||||
const sw_pll_15q16_t Kii,
|
||||
const size_t loop_rate_count,
|
||||
const size_t pll_ratio,
|
||||
const uint32_t ref_clk_expected_inc,
|
||||
const int16_t * const lut_table_base,
|
||||
const size_t num_lut_entries,
|
||||
const uint32_t app_pll_ctl_reg_val,
|
||||
const uint32_t app_pll_div_reg_val,
|
||||
const unsigned nominal_lut_idx,
|
||||
const unsigned ppm_range)
|
||||
{
|
||||
// Get PLL started and running at nominal
|
||||
sw_pll_app_pll_init(get_local_tile_id(),
|
||||
app_pll_ctl_reg_val,
|
||||
app_pll_div_reg_val,
|
||||
lut_table_base[nominal_lut_idx]);
|
||||
|
||||
// Setup sw_pll with supplied user paramaters
|
||||
sw_pll_lut_reset(sw_pll, Kp, Ki, Kii, num_lut_entries);
|
||||
|
||||
// Setup general controller state
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
|
||||
sw_pll->loop_rate_count = loop_rate_count;
|
||||
sw_pll->loop_counter = 0;
|
||||
sw_pll->first_loop = 1;
|
||||
|
||||
sw_pll_reset_pi_state(sw_pll);
|
||||
|
||||
// Setup LUT params
|
||||
sw_pll->lut_state.current_reg_val = app_pll_div_reg_val;
|
||||
sw_pll->lut_state.lut_table_base = lut_table_base;
|
||||
sw_pll->lut_state.num_lut_entries = num_lut_entries;
|
||||
sw_pll->lut_state.nominal_lut_idx = nominal_lut_idx;
|
||||
|
||||
// Setup PFD state
|
||||
sw_pll_pfd_init(&(sw_pll->pfd_state), loop_rate_count, pll_ratio, ref_clk_expected_inc, ppm_range);
|
||||
}
|
||||
|
||||
|
||||
__attribute__((always_inline))
|
||||
inline sw_pll_lock_status_t sw_pll_lut_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error)
|
||||
{
|
||||
int32_t total_error = sw_pll_do_pi_ctrl(sw_pll, error);
|
||||
sw_pll->lut_state.current_reg_val = lookup_pll_frac(sw_pll, total_error);
|
||||
|
||||
write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->lut_state.current_reg_val));
|
||||
|
||||
return sw_pll->lock_status;
|
||||
}
|
||||
|
||||
sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt)
|
||||
{
|
||||
if (++sw_pll->loop_counter == sw_pll->loop_rate_count)
|
||||
{
|
||||
sw_pll->loop_counter = 0;
|
||||
|
||||
if (sw_pll->first_loop) // First loop around so ensure state is clear
|
||||
{
|
||||
sw_pll->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data
|
||||
sw_pll_reset_pi_state(sw_pll);
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
|
||||
|
||||
sw_pll->first_loop = 0;
|
||||
|
||||
// Do not set PLL frac as last setting probably the best. At power on we set to nominal (midway in table)
|
||||
}
|
||||
else
|
||||
{
|
||||
sw_pll_calc_error_from_port_timers(&sw_pll->pfd_state, &sw_pll->first_loop, mclk_pt, ref_clk_pt);
|
||||
sw_pll_lut_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff);
|
||||
|
||||
// Save for next iteration to calc diff
|
||||
sw_pll->pfd_state.mclk_pt_last = mclk_pt;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return sw_pll->lock_status;
|
||||
}
|
||||
|
||||
#endif // __XS3A__
|
||||
35
lib_sw_pll/lib_sw_pll/src/sw_pll_pfd.c
Normal file
35
lib_sw_pll/lib_sw_pll/src/sw_pll_pfd.c
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#ifdef __XS3A__
|
||||
|
||||
#include <xcore/assert.h>
|
||||
#include "sw_pll_pfd.h"
|
||||
|
||||
void sw_pll_pfd_init(sw_pll_pfd_state_t *pfd_state,
|
||||
const size_t loop_rate_count,
|
||||
const size_t pll_ratio,
|
||||
const uint32_t ref_clk_expected_inc,
|
||||
const unsigned ppm_range)
|
||||
{
|
||||
pfd_state->mclk_diff = 0;
|
||||
pfd_state->ref_clk_pt_last = 0;
|
||||
pfd_state->ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count;
|
||||
if(pfd_state->ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used
|
||||
{
|
||||
pfd_state->ref_clk_scaling_numerator = (1ULL << SW_PLL_PFD_PRE_DIV_BITS) / pfd_state->ref_clk_expected_inc + 1; //+1 helps with rounding accuracy
|
||||
}
|
||||
pfd_state->mclk_pt_last = 0;
|
||||
pfd_state->mclk_expected_pt_inc = loop_rate_count * pll_ratio;
|
||||
// Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range.
|
||||
pfd_state->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000);
|
||||
// Check we can actually support the numbers used in the maths we use
|
||||
const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX
|
||||
const float max = (float)pfd_state->ref_clk_expected_inc
|
||||
* (float)pfd_state->ref_clk_scaling_numerator
|
||||
* (float)pfd_state->mclk_expected_pt_inc;
|
||||
// If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency
|
||||
xassert(max < calc_max);
|
||||
}
|
||||
|
||||
#endif // __XS3A__
|
||||
85
lib_sw_pll/lib_sw_pll/src/sw_pll_pfd.h
Normal file
85
lib_sw_pll/lib_sw_pll/src/sw_pll_pfd.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2023-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sw_pll_common.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
#define SW_PLL_PFD_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max
|
||||
|
||||
/**
|
||||
* Initialises the Phase Frequency Detector.
|
||||
*
|
||||
* Sets how often the PFD and control loop runs and error limits for resetting
|
||||
*
|
||||
* \param pfd_state Pointer to the struct to be initialised.
|
||||
* \param loop_rate_count How many counts of the call to sw_pll_lut_do_control before control is done.
|
||||
* Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error
|
||||
* calls the control loop every time so this is ignored.
|
||||
* \param pll_ratio Integer ratio between input reference clock and the PLL output.
|
||||
* Only used by sw_pll_lut_do_control for the PFD. Don't care otherwise.
|
||||
* Used to calculate the expected port timer increment when control is called.
|
||||
* \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_lut_do_control is called.
|
||||
* Pass in zero if you are sure the mclk sampling timing is precise. This
|
||||
* will disable the scaling of the mclk count inside sw_pll_lut_do_control.
|
||||
* Only used by sw_pll_lut_do_control. Don't care otherwise.
|
||||
* \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation
|
||||
* of counted mclk before the PLL resets its state.
|
||||
* Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error
|
||||
* calls the control loop every time so this is ignored.
|
||||
*/
|
||||
void sw_pll_pfd_init(sw_pll_pfd_state_t *pfd_state,
|
||||
const size_t loop_rate_count,
|
||||
const size_t pll_ratio,
|
||||
const uint32_t ref_clk_expected_inc,
|
||||
const unsigned ppm_range);
|
||||
|
||||
/**
|
||||
* Helper to perform the maths needed on the port count, accounting for wrapping, to determine the frequency difference.
|
||||
*
|
||||
* \param pfd_state Pointer to the PFD state.
|
||||
* \param first_loop Pointer to the first_loop var used for resetting when out of range.
|
||||
* \param mclk_pt The clock count from the port counter.
|
||||
* \param ref_clk_pt The clock count from the ref clock counter (optional).
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void sw_pll_calc_error_from_port_timers( sw_pll_pfd_state_t * const pfd,
|
||||
uint8_t *first_loop,
|
||||
const uint16_t mclk_pt,
|
||||
const uint16_t ref_clk_pt)
|
||||
{
|
||||
uint16_t mclk_expected_pt = 0;
|
||||
// See if we are using variable loop period sampling, if so, compensate for it by scaling the expected mclk count
|
||||
if(pfd->ref_clk_expected_inc)
|
||||
{
|
||||
uint16_t ref_clk_expected_pt = pfd->ref_clk_pt_last + pfd->ref_clk_expected_inc;
|
||||
// This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536
|
||||
int16_t ref_clk_diff = PORT_TIMEAFTER(ref_clk_pt, ref_clk_expected_pt) ? -(int16_t)(ref_clk_expected_pt - ref_clk_pt) : (int16_t)(ref_clk_pt - ref_clk_expected_pt);
|
||||
pfd->ref_clk_pt_last = ref_clk_pt;
|
||||
|
||||
// This allows for wrapping of the timer when CONTROL_LOOP_COUNT is high
|
||||
// Note we use a pre-computed divide followed by a shift to replace a constant divide with a constant multiply + shift
|
||||
uint32_t mclk_expected_pt_inc = ((uint64_t)pfd->mclk_expected_pt_inc
|
||||
* ((uint64_t)pfd->ref_clk_expected_inc + ref_clk_diff)
|
||||
* pfd->ref_clk_scaling_numerator) >> SW_PLL_PFD_PRE_DIV_BITS;
|
||||
// Below is the line we would use if we do not pre-compute the divide. This can take a long time if we spill over 32b
|
||||
// uint32_t mclk_expected_pt_inc = pfd->mclk_expected_pt_inc * (pfd->ref_clk_expected_inc + ref_clk_diff) / pfd->ref_clk_expected_inc;
|
||||
mclk_expected_pt = pfd->mclk_pt_last + mclk_expected_pt_inc;
|
||||
}
|
||||
else // we are assuming mclk_pt is sampled precisely and needs no compoensation
|
||||
{
|
||||
mclk_expected_pt = pfd->mclk_pt_last + pfd->mclk_expected_pt_inc;
|
||||
}
|
||||
|
||||
// This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536
|
||||
pfd->mclk_diff = PORT_TIMEAFTER(mclk_pt, mclk_expected_pt) ? -(int16_t)(mclk_expected_pt - mclk_pt) : (int16_t)(mclk_pt - mclk_expected_pt);
|
||||
|
||||
// Check to see if something has gone very wrong, for example ref clock stop/start. If so, reset state and keep trying
|
||||
if(MAGNITUDE(pfd->mclk_diff) > pfd->mclk_max_diff)
|
||||
{
|
||||
*first_loop = 1;
|
||||
}
|
||||
}
|
||||
150
lib_sw_pll/lib_sw_pll/src/sw_pll_sdm.c
Normal file
150
lib_sw_pll/lib_sw_pll/src/sw_pll_sdm.c
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2022-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#ifdef __XS3A__
|
||||
|
||||
#include "sw_pll.h"
|
||||
|
||||
void sw_pll_sdm_controller_init(sw_pll_state_t * const sw_pll,
|
||||
const sw_pll_15q16_t Kp,
|
||||
const sw_pll_15q16_t Ki,
|
||||
const sw_pll_15q16_t Kii,
|
||||
const size_t loop_rate_count,
|
||||
const int32_t ctrl_mid_point)
|
||||
{
|
||||
// Setup sw_pll with supplied user paramaters
|
||||
sw_pll_lut_reset(sw_pll, Kp, Ki, Kii, 0);
|
||||
// override windup limits
|
||||
sw_pll->pi_state.i_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT;
|
||||
sw_pll->pi_state.ii_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT;
|
||||
sw_pll->sdm_state.ctrl_mid_point = ctrl_mid_point;
|
||||
sw_pll->pi_state.iir_y = 0;
|
||||
sw_pll->sdm_state.current_ctrl_val = ctrl_mid_point;
|
||||
|
||||
sw_pll_reset_pi_state(sw_pll);
|
||||
|
||||
// Setup general controller state
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
|
||||
sw_pll->loop_rate_count = loop_rate_count;
|
||||
sw_pll->loop_counter = 0;
|
||||
sw_pll->first_loop = 1;
|
||||
}
|
||||
|
||||
void sw_pll_sdm_init( sw_pll_state_t * const sw_pll,
|
||||
const sw_pll_15q16_t Kp,
|
||||
const sw_pll_15q16_t Ki,
|
||||
const sw_pll_15q16_t Kii,
|
||||
const size_t loop_rate_count,
|
||||
const size_t pll_ratio,
|
||||
const uint32_t ref_clk_expected_inc,
|
||||
const uint32_t app_pll_ctl_reg_val,
|
||||
const uint32_t app_pll_div_reg_val,
|
||||
const uint32_t app_pll_frac_reg_val,
|
||||
const int32_t ctrl_mid_point,
|
||||
const unsigned ppm_range)
|
||||
{
|
||||
// Get PLL started and running at nominal
|
||||
sw_pll_app_pll_init(get_local_tile_id(),
|
||||
app_pll_ctl_reg_val,
|
||||
app_pll_div_reg_val,
|
||||
(uint16_t)(app_pll_frac_reg_val & 0xffff));
|
||||
|
||||
// Setup SDM controller state
|
||||
sw_pll_sdm_controller_init( sw_pll,
|
||||
Kp,
|
||||
Ki,
|
||||
Kii,
|
||||
loop_rate_count,
|
||||
ctrl_mid_point);
|
||||
|
||||
// Setup PFD state
|
||||
sw_pll_pfd_init(&(sw_pll->pfd_state), loop_rate_count, pll_ratio, ref_clk_expected_inc, ppm_range);
|
||||
}
|
||||
|
||||
|
||||
void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state){
|
||||
sdm_state->ds_x1 = 0;
|
||||
sdm_state->ds_x2 = 0;
|
||||
sdm_state->ds_x3 = 0;
|
||||
}
|
||||
|
||||
|
||||
__attribute__((always_inline))
|
||||
int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error)
|
||||
{
|
||||
// Filter some noise into DCO to reduce jitter
|
||||
// First order IIR, make A=0.125
|
||||
// y = y + A(x-y)
|
||||
sw_pll->pi_state.iir_y += ((error - sw_pll->pi_state.iir_y)>>3);
|
||||
|
||||
int32_t dco_ctl = sw_pll->sdm_state.ctrl_mid_point + sw_pll->pi_state.iir_y;
|
||||
|
||||
if(dco_ctl > SW_PLL_SDM_UPPER_LIMIT){
|
||||
dco_ctl = SW_PLL_SDM_UPPER_LIMIT;
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_HIGH;
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
} else if (dco_ctl < SW_PLL_SDM_LOWER_LIMIT){
|
||||
dco_ctl = SW_PLL_SDM_LOWER_LIMIT;
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
} else {
|
||||
if(sw_pll->lock_counter){
|
||||
sw_pll->lock_counter--;
|
||||
} else {
|
||||
sw_pll->lock_status = SW_PLL_LOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
return dco_ctl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
__attribute__((always_inline))
|
||||
inline sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error)
|
||||
{
|
||||
int32_t ctrl_error = sw_pll_do_pi_ctrl(sw_pll, error);
|
||||
sw_pll->sdm_state.current_ctrl_val = sw_pll_sdm_post_control_proc(sw_pll, ctrl_error);
|
||||
|
||||
return sw_pll->lock_status;
|
||||
}
|
||||
|
||||
|
||||
bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt)
|
||||
{
|
||||
bool control_done = true;
|
||||
|
||||
if (++sw_pll->loop_counter == sw_pll->loop_rate_count)
|
||||
{
|
||||
sw_pll->loop_counter = 0;
|
||||
|
||||
if (sw_pll->first_loop) // First loop around so ensure state is clear
|
||||
{
|
||||
sw_pll->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data
|
||||
sw_pll->pi_state.iir_y = 0;
|
||||
sw_pll_reset_pi_state(sw_pll);
|
||||
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
|
||||
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
|
||||
|
||||
sw_pll->first_loop = 0;
|
||||
// Do not set current_ctrl_val as last setting probably the best. At power on we set to nominal (midway in settings)
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt);
|
||||
sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff);
|
||||
|
||||
// Save for next iteration to calc diff
|
||||
sw_pll->pfd_state.mclk_pt_last = mclk_pt;
|
||||
}
|
||||
} else {
|
||||
control_done = false;
|
||||
}
|
||||
|
||||
return control_done;
|
||||
}
|
||||
|
||||
#endif // __XS3A__
|
||||
133
lib_sw_pll/lib_sw_pll/src/sw_pll_sdm.h
Normal file
133
lib_sw_pll/lib_sw_pll/src/sw_pll_sdm.h
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2023-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include "sw_pll.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
#define SW_PLL_SDM_UPPER_LIMIT 980000
|
||||
#define SW_PLL_SDM_LOWER_LIMIT 60000
|
||||
|
||||
typedef int tileref_t;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sw_pll_sdm_controller_init initialisation function.
|
||||
*
|
||||
* This sets up the PI controller and post processing for the SDM sw_pll. It is provided to allow
|
||||
* cases where the PI controller may be separated from the SDM on a different tile and so we want
|
||||
* to init the SDM without the sigma delta modulator code and physical writes to the app PLL.
|
||||
*
|
||||
* \param sw_pll Pointer to the struct to be initialised.
|
||||
* \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float.
|
||||
* \param loop_rate_count How many counts of the call to sw_pll_sdm_do_control before control is done.
|
||||
* \param ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally
|
||||
* close to halfway to allow symmetrical range.
|
||||
*
|
||||
*/
|
||||
void sw_pll_sdm_controller_init(sw_pll_state_t * const sw_pll,
|
||||
const sw_pll_15q16_t Kp,
|
||||
const sw_pll_15q16_t Ki,
|
||||
const sw_pll_15q16_t Kii,
|
||||
const size_t loop_rate_count,
|
||||
const int32_t ctrl_mid_point);
|
||||
|
||||
|
||||
/**
|
||||
* low level sw_pll_calc_sigma_delta function that turns a control signal
|
||||
* into a Sigma Delta Modulated output signal.
|
||||
*
|
||||
*
|
||||
* \param sdm_state Pointer to the SDM state.
|
||||
* \param sdm_in 32b signed input error value. Note limited range.
|
||||
* See SW_PLL_SDM_UPPER_LIMIT and SW_PLL_SDM_LOWER_LIMIT.
|
||||
* \returns Sigma Delta modulated signal.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline int32_t sw_pll_calc_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t sdm_in){
|
||||
// Third order, 9 level output delta sigma. 20 bit unsigned input.
|
||||
int32_t sdm_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13;
|
||||
if (sdm_out > 8){
|
||||
sdm_out = 8;
|
||||
}
|
||||
if (sdm_out < 0){
|
||||
sdm_out = 0;
|
||||
}
|
||||
sdm_state->ds_x3 += (sdm_state->ds_x2>>5) - (sdm_out<<9) - (sdm_out<<8);
|
||||
sdm_state->ds_x2 += (sdm_state->ds_x1>>5) - (sdm_out<<14);
|
||||
sdm_state->ds_x1 += sdm_in - (sdm_out<<17);
|
||||
|
||||
return sdm_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* low level sw_pll_sdm sw_pll_sdm_out_to_frac_reg function that turns
|
||||
* a sigma delta output signal into a PLL fractional register setting.
|
||||
*
|
||||
* \param sdm_out 32b signed input value.
|
||||
* \returns Fractional register value.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t sw_pll_sdm_out_to_frac_reg(int32_t sdm_out){
|
||||
// bit 31 is frac enable
|
||||
// bits 15..8 are the f value
|
||||
// bits 7..0 are the p value
|
||||
// Freq - F + (f + 1)/(p + 1)
|
||||
uint32_t frac_val = 0;
|
||||
|
||||
if (sdm_out == 0){
|
||||
frac_val = 0x00000007; // step 0/8
|
||||
}
|
||||
else{
|
||||
frac_val = ((sdm_out - 1) << 8) | 0x80000007; // steps 1/8 to 8/8
|
||||
}
|
||||
|
||||
return frac_val;
|
||||
}
|
||||
|
||||
/**
|
||||
* low level sw_pll_write_frac_reg function that writes the PLL fractional
|
||||
* register.
|
||||
*
|
||||
* NOTE: Attempting to write the PLL fractional register from more than
|
||||
* one logical core at the same time may result in channel lock-up.
|
||||
* Please ensure the that PLL initiaisation has completed before
|
||||
* the SDM task writes to the register. The provided example
|
||||
* implements a method for doing this.
|
||||
*
|
||||
* \param this_tile The ID of the xcore tile that is doing the write.
|
||||
* \param frac_val 32b register value
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void sw_pll_write_frac_reg(tileref_t this_tile, uint32_t frac_val){
|
||||
write_sswitch_reg_no_ack(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs the Sigma Delta Modulation from a control input.
|
||||
* It performs the SDM algorithm, converts the output to a fractional register setting
|
||||
* and then writes the value to the PLL fractional register.
|
||||
* Is typically called in a constant period fast loop and run from a dedicated thread which could be on a remote tile.
|
||||
*
|
||||
* NOTE: Attempting to write the PLL fractional register from more than
|
||||
* one logical core at the same time may result in channel lock-up.
|
||||
* Please ensure the that PLL initiaisation has completed before
|
||||
* the SDM task writes to the register. The provided `simple_sdm` example
|
||||
* implements a method for doing this.
|
||||
*
|
||||
* \param sw_pll Pointer to the SDM state struct.
|
||||
* \param this_tile The ID of the xcore tile that is doing the write.
|
||||
* Use get_local_tile_id() to obtain this.
|
||||
* \param sdm_control_in Current control value.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void sw_pll_do_sigma_delta(sw_pll_sdm_state_t *sdm_state, tileref_t this_tile, int32_t sdm_control_in){
|
||||
|
||||
int32_t sdm_out = sw_pll_calc_sigma_delta(sdm_state, sdm_control_in);
|
||||
uint32_t frac_val = sw_pll_sdm_out_to_frac_reg(sdm_out);
|
||||
sw_pll_write_frac_reg(this_tile, frac_val);
|
||||
}
|
||||
96
lib_sw_pll/python/sw_pll/analysis_tools.py
Normal file
96
lib_sw_pll/python/sw_pll/analysis_tools.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import soundfile
|
||||
from scipy.io import wavfile # soundfile has some issues writing high Fs files
|
||||
|
||||
class audio_modulator:
|
||||
"""
|
||||
This test helper generates a wav file with a fixed sample rate and tone frequency
|
||||
of a certain length.
|
||||
A method then allows sections of it to be frequency modulated by a value in Hz.
|
||||
The modulated signal (which uses cumultaive phase to avoid discontinuites)
|
||||
may then be plotted as an FFT to understand the SNR/THD and may also be saved
|
||||
as a wav file.
|
||||
"""
|
||||
|
||||
def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000):
|
||||
self.sample_rate = sample_rate
|
||||
self.test_tone_hz = test_tone_hz
|
||||
|
||||
self.modulator = np.full(int(duration_s * sample_rate), test_tone_hz, dtype=np.float64)
|
||||
|
||||
def apply_frequency_deviation(self, start_s, end_s, delta_freq):
|
||||
start_idx = int(start_s * self.sample_rate)
|
||||
end_idx = int(end_s * self.sample_rate)
|
||||
self.modulator[start_idx:end_idx] += delta_freq
|
||||
|
||||
def modulate_waveform(self):
|
||||
# Now create the frequency modulated waveform
|
||||
# this is designed to accumulate the phase so doesn't see discontinuities
|
||||
# https://dsp.stackexchange.com/questions/80768/fsk-modulation-with-python
|
||||
delta_phi = self.modulator * np.pi / (self.sample_rate / 2.0)
|
||||
phi = np.cumsum(delta_phi)
|
||||
self.waveform = np.sin(phi)
|
||||
|
||||
def save_modulated_wav(self, filename):
|
||||
integer_output = np.int16(self.waveform * 32767)
|
||||
# soundfile.write(filename, integer_output, int(self.sample_rate)) # This struggles with >768ksps
|
||||
wavfile.write(filename, int(self.sample_rate), integer_output)
|
||||
|
||||
def plot_modulated_fft(self, filename, skip_s=None):
|
||||
start_x = 0 if skip_s is None else int(skip_s * self.sample_rate) // 2 * 2
|
||||
waveform = self.waveform[start_x:]
|
||||
|
||||
xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), waveform.size // 2)
|
||||
N = xf.size
|
||||
window = np.kaiser(N*2, 14)
|
||||
waveform = waveform * window
|
||||
yf = np.fft.fft(waveform)
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
# Plot a zoom in on the test
|
||||
tone_idx = int(self.test_tone_hz / (self.sample_rate / 2) * N)
|
||||
num_side_bins = 50
|
||||
yf = 20 * np.log10(np.abs(yf) / N)
|
||||
# ax.plot(xf[tone_idx - num_side_bins:tone_idx + num_side_bins], yf[tone_idx - num_side_bins:tone_idx + num_side_bins], marker='.')
|
||||
|
||||
# Plot the whole frequncy range from DC to nyquist
|
||||
ax.plot(xf[:N], yf[:N], marker='.')
|
||||
ax.set_xscale("log")
|
||||
plt.xlim((10**1, 10**5))
|
||||
plt.ylim((-200, 0))
|
||||
plt.savefig(filename, dpi=150)
|
||||
|
||||
def load_wav(self, filename):
|
||||
"""
|
||||
Used for testing only - load a wav into self.waveform
|
||||
"""
|
||||
self.waveform, self.sample_rate = soundfile.read(filename)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
This module is not intended to be run directly. This is here for internal testing only.
|
||||
"""
|
||||
if 0:
|
||||
test_len = 10
|
||||
audio = audio_modulator(test_len)
|
||||
for time_s in range(test_len):
|
||||
modulation_hz = 10 * (time_s - (test_len) / 2)
|
||||
audio.apply_frequency_deviation(time_s, time_s + 1, modulation_hz)
|
||||
|
||||
audio.modulate_waveform()
|
||||
audio.save_modulated_wav("modulated.wav")
|
||||
audio.plot_modulated_fft("modulated_fft.png")
|
||||
|
||||
else:
|
||||
audio = audio_modulator(1)
|
||||
audio.load_wav("modulated_tone_1000Hz_sd_ds.wav")
|
||||
# audio = audio_modulator(1, sample_rate=3072000)
|
||||
# audio.modulate_waveform()
|
||||
audio.plot_modulated_fft("modulated_tone_1000Hz_sd_ds.png")
|
||||
# audio.save_modulated_wav("modulated.wav")
|
||||
|
||||
287
lib_sw_pll/python/sw_pll/app_pll_model.py
Normal file
287
lib_sw_pll/python/sw_pll/app_pll_model.py
Normal file
@@ -0,0 +1,287 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
from pathlib import Path
|
||||
from sw_pll.pll_calc import print_regs
|
||||
from contextlib import redirect_stdout
|
||||
import io
|
||||
|
||||
register_file = "register_setup.h" # can be changed as needed. This contains the register setup params and is accessible via C in the firmware
|
||||
|
||||
|
||||
class app_pll_frac_calc:
|
||||
"""
|
||||
This class uses the formulae in the XU316 datasheet to calculate the output frequency of the
|
||||
application PLL (sometimes called secondary PLL) from the register settings provided.
|
||||
It uses the checks specified in the datasheet to ensure the settings are valid, and will assert if not.
|
||||
To keep the inherent jitter of the PLL output down to a minimum, it is recommended that R be kept small,
|
||||
ideally = 0 (which equiates to 1) but reduces lock range.
|
||||
"""
|
||||
|
||||
frac_enable_mask = 0x80000000
|
||||
|
||||
def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD_init, verbose=False):
|
||||
"""
|
||||
Constructor initialising a PLL instance
|
||||
"""
|
||||
self.input_frequency = input_frequency
|
||||
self.F = F_init
|
||||
self.R = R_init
|
||||
self.OD = OD_init
|
||||
self.ACD = ACD_init
|
||||
self.f = f_init # fractional multiplier (+1.0)
|
||||
self.p = p_init # fractional divider (+1.0)
|
||||
self.output_frequency = None
|
||||
self.fractional_enable = True
|
||||
self.verbose = verbose
|
||||
|
||||
self.calc_frequency()
|
||||
|
||||
def calc_frequency(self):
|
||||
"""
|
||||
Calculate the output frequency based on current object settings
|
||||
"""
|
||||
if self.verbose:
|
||||
print(f"F: {self.F} R: {self.R} OD: {self.OD} ACD: {self.ACD} f: {self.f} p: {self.p}")
|
||||
print(f"input_frequency: {self.input_frequency}")
|
||||
assert self.F >= 1 and self.F <= 8191, f"Invalid F setting {self.F}"
|
||||
assert type(self.F) is int, f"Error: F must be an INT"
|
||||
assert self.R >= 0 and self.R <= 63, f"Invalid R setting {self.R}"
|
||||
assert type(self.R) is int, f"Error: R must be an INT"
|
||||
assert self.OD >= 0 and self.OD <= 7, f"Invalid OD setting {self.OD}"
|
||||
assert type(self.OD) is int, f"Error: OD must be an INT"
|
||||
|
||||
intermediate_freq = self.input_frequency * (self.F + 1.0) / 2.0 / (self.R + 1.0)
|
||||
assert intermediate_freq >= 360000000.0 and intermediate_freq <= 1800000000.0, f"Invalid VCO freq: {intermediate_freq}"
|
||||
# print(f"intermediate_freq: {intermediate_freq}")
|
||||
|
||||
assert type(self.p) is int, f"Error: r must be an INT"
|
||||
assert type(self.f) is int, f"Error: f must be an INT"
|
||||
|
||||
# From XU316-1024-QF60A-xcore.ai-Datasheet_22.pdf
|
||||
if self.fractional_enable:
|
||||
# assert self.p > self.f, "Error f is not < p: {self.f} {self.p}" # This check has been removed as Joe found it to be OK in RTL/practice
|
||||
pll_ratio = (self.F + 1.0 + ((self.f + 1) / (self.p + 1)) ) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1))
|
||||
else:
|
||||
pll_ratio = (self.F + 1.0) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1))
|
||||
|
||||
self.output_frequency = self.input_frequency * pll_ratio
|
||||
|
||||
return self.output_frequency
|
||||
|
||||
def get_output_frequency(self):
|
||||
"""
|
||||
Get last calculated frequency
|
||||
"""
|
||||
return self.output_frequency
|
||||
|
||||
def update_all(self, F, R, OD, ACD, f, p):
|
||||
"""
|
||||
Reset all App PLL vars
|
||||
"""
|
||||
self.F = F
|
||||
self.R = R
|
||||
self.OD = OD
|
||||
self.ACD = ACD
|
||||
self.f = f
|
||||
self.p = p
|
||||
return self.calc_frequency()
|
||||
|
||||
def update_frac(self, f, p, fractional=None):
|
||||
"""
|
||||
Update only the fractional parts of the App PLL
|
||||
"""
|
||||
self.f = f
|
||||
self.p = p
|
||||
# print(f"update_frac f:{self.f} p:{self.p}")
|
||||
if fractional is not None:
|
||||
self.fractional_enable = fractional
|
||||
|
||||
return self.calc_frequency()
|
||||
|
||||
def update_frac_reg(self, reg):
|
||||
"""
|
||||
Determine f and p from the register number and recalculate frequency
|
||||
Assumes fractional is set to true
|
||||
"""
|
||||
f = int((reg >> 8) & ((2**8)-1))
|
||||
p = int(reg & ((2**8)-1))
|
||||
|
||||
self.fractional_enable = True if (reg & self.frac_enable_mask) else False
|
||||
|
||||
return self.update_frac(f, p)
|
||||
|
||||
|
||||
def get_frac_reg(self):
|
||||
"""
|
||||
Returns the fractional reg value from current setting
|
||||
"""
|
||||
# print(f"get_frac_reg f:{self.f} p:{self.p}")
|
||||
reg = self.p | (self.f << 8)
|
||||
if self.fractional_enable:
|
||||
reg |= self.frac_enable_mask
|
||||
|
||||
return reg
|
||||
|
||||
def gen_register_file_text(self):
|
||||
"""
|
||||
Helper used to generate text for the register setup h file
|
||||
"""
|
||||
text = f"/* Input freq: {self.input_frequency}\n"
|
||||
text += f" F: {self.F}\n"
|
||||
text += f" R: {self.R}\n"
|
||||
text += f" f: {self.f}\n"
|
||||
text += f" p: {self.p}\n"
|
||||
text += f" OD: {self.OD}\n"
|
||||
text += f" ACD: {self.ACD}\n"
|
||||
text += "*/\n\n"
|
||||
|
||||
# This is a way of calling a printing function from another module and capturing the STDOUT
|
||||
class args:
|
||||
app = True
|
||||
f = io.StringIO()
|
||||
with redirect_stdout(f):
|
||||
# in pll_calc, op_div = OD, fb_div = F, f, p, ref_div = R, fin_op_div = ACD
|
||||
print_regs(args, self.OD + 1, [self.F + 1, self.f + 1, self.p + 1] , self.R + 1, self.ACD + 1)
|
||||
text += f.getvalue().replace(" ", "_").replace("REG_0x", "REG 0x").replace("APP_PLL", "#define APP_PLL")
|
||||
|
||||
return text
|
||||
|
||||
# see /doc/sw_pll.rst for guidance on these settings
|
||||
def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95):
|
||||
"""
|
||||
This is a wrapper function for pll_calc.py and allows it to be called programatically.
|
||||
It contains sensible defaults for the arguments and abstracts some of the complexity away from
|
||||
the underlying script. Configuring the PLL is not an exact science and there are many tradeoffs involved.
|
||||
See sw_pll.rst for some of the tradeoffs involved and some example paramater sets.
|
||||
|
||||
Once run, this function saves two output files:
|
||||
- fractions.h which contains the fractional term lookup table, which is guarranteed monotonic (important for PI stability)
|
||||
- register_setup.h which contains the PLL settings in comments as well as register settings for init in the application
|
||||
|
||||
This function and the underlying call to pll_calc may take several seconds to complete since it searches a range
|
||||
of possible solutions numerically.
|
||||
|
||||
input_frequency - The xcore clock frequency, normally the XTAL frequency
|
||||
nominal_ref_frequency - The nominal input reference frequency
|
||||
target_output_frequency - The nominal target output frequency
|
||||
max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance
|
||||
min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance
|
||||
ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance
|
||||
fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance
|
||||
fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
input_frequency_MHz = input_frequency / 1000000.0
|
||||
target_output_frequency_MHz = target_output_frequency / 1000000.0
|
||||
|
||||
calc_script = Path(__file__).parent/"pll_calc.py"
|
||||
|
||||
# input freq, app pll, max denom, output freq, min phase comp freq, max ppm error, raw, fractional range, make header
|
||||
cmd = f"{calc_script} -i {input_frequency_MHz} -a -m {max_denom} -t {target_output_frequency_MHz} -p 6.0 -e {int(ppm_max)} -r --fracmin {fracmin} --fracmax {fracmax} --header"
|
||||
print(f"Running: {cmd}")
|
||||
output = subprocess.check_output(cmd.split(), text=True)
|
||||
|
||||
# Get each solution
|
||||
solutions = []
|
||||
Fs = []
|
||||
regex = r"Found solution.+\nAPP.+\nAPP.+\nAPP.+"
|
||||
matches = re.findall(regex, output)
|
||||
|
||||
for solution in matches:
|
||||
F = int(float(re.search(r".+FD\s+(\d+.\d+).+", solution).groups()[0]))
|
||||
solutions.append(solution)
|
||||
Fs.append(F)
|
||||
|
||||
possible_Fs = sorted(set(Fs))
|
||||
print(f"Available F values: {possible_Fs}")
|
||||
|
||||
# Find first solution with F greater than F
|
||||
idx = next(x for x, val in enumerate(Fs) if val > min_F)
|
||||
solution = matches[idx]
|
||||
|
||||
# Get actual PLL register bitfield settings and info
|
||||
regex = r".+OUT (\d+\.\d+)MHz, VCO (\d+\.\d+)MHz, RD\s+(\d+), FD\s+(\d+.\d*)\s+\(m =\s+(\d+), n =\s+(\d+)\), OD\s+(\d+), FOD\s+(\d+), ERR (-*\d+.\d+)ppm.*"
|
||||
match = re.search(regex, solution)
|
||||
|
||||
if match:
|
||||
vals = match.groups()
|
||||
|
||||
output_frequency = (1000000.0 * float(vals[0]))
|
||||
vco_freq = 1000000.0 * float(vals[1])
|
||||
|
||||
# Now convert to actual settings in register bitfields
|
||||
F = int(float(vals[3]) - 1) # PLL integer multiplier
|
||||
R = int(vals[2]) - 1 # PLL integer divisor
|
||||
f = int(vals[4]) - 1 # PLL fractional multiplier
|
||||
p = int(vals[5]) - 1 # PLL fractional divisor
|
||||
OD = int(vals[6]) - 1 # PLL output divider
|
||||
ACD = int(vals[7]) - 1 # PLL application clock divider
|
||||
ppm = float(vals[8]) # PLL PPM error for requrested set frequency
|
||||
|
||||
assert match, f"Could not parse output of: {cmd} output: {solution}"
|
||||
|
||||
# Now get reg values and save to file
|
||||
with open(register_file, "w") as reg_vals:
|
||||
reg_vals.write(f"/* Autogenerated by {Path(__file__).name} using command:\n")
|
||||
reg_vals.write(f" {cmd}\n")
|
||||
reg_vals.write(f" Picked output solution #{idx}\n")
|
||||
# reg_vals.write(f"\n{solution}\n\n") # This is verbose and contains the same info as below
|
||||
reg_vals.write(f" Input freq: {input_frequency}\n")
|
||||
reg_vals.write(f" F: {F}\n")
|
||||
reg_vals.write(f" R: {R}\n")
|
||||
reg_vals.write(f" f: {f}\n")
|
||||
reg_vals.write(f" p: {p}\n")
|
||||
reg_vals.write(f" OD: {OD}\n")
|
||||
reg_vals.write(f" ACD: {ACD}\n")
|
||||
reg_vals.write(f" Output freq: {output_frequency}\n")
|
||||
reg_vals.write(f" VCO freq: {vco_freq} */\n")
|
||||
reg_vals.write("\n")
|
||||
|
||||
|
||||
for reg in ["APP PLL CTL REG", "APP PLL DIV REG", "APP PLL FRAC REG"]:
|
||||
regex = rf"({reg})\s+(0[xX][A-Fa-f0-9]+)"
|
||||
match = re.search(regex, solution)
|
||||
if match:
|
||||
val = match.groups()[1]
|
||||
reg_name = reg.replace(" ", "_")
|
||||
line = f"#define {reg_name} \t{val}\n"
|
||||
reg_vals.write(line)
|
||||
|
||||
|
||||
return output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm
|
||||
|
||||
class pll_solution:
|
||||
"""
|
||||
Access to all the info from get_pll_solution, cleaning up temp files.
|
||||
intended for programatic access from the tests. Creates a PLL setup and LUT and reads back the generated LUT
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.output_frequency, self.vco_freq, self.F, self.R, self.f, self.p, self.OD, self.ACD, self.ppm = get_pll_solution(*args, **kwargs)
|
||||
from .dco_model import lut_dco
|
||||
dco = lut_dco("fractions.h")
|
||||
self.lut, min_frac, max_frac = dco._read_lut_header("fractions.h")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
This module is not intended to be run directly. This is here for internal testing only.
|
||||
"""
|
||||
input_frequency = 24000000
|
||||
output_frequency = 12288000
|
||||
print(f"get_pll_solution input_frequency: {input_frequency} output_frequency: {output_frequency}...")
|
||||
output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(input_frequency, output_frequency)
|
||||
print(f"got solution: \noutput_frequency: {output_frequency}\nvco_freq: {vco_freq}\nF: {F}\nR: {R}\nf: {f}\np: {p}\nOD: {OD}\nACD: {ACD}\nppm: {ppm}")
|
||||
|
||||
app_pll = app_pll_frac_calc(input_frequency, F, R, f, p, OD, ACD)
|
||||
print(f"Got output frequency: {app_pll.calc_frequency()}")
|
||||
p = 10
|
||||
for f in range(p):
|
||||
for frac_enable in [True, False]:
|
||||
print(f"For f: {f} frac_enable: {frac_enable} got frequency: {app_pll.update_frac(f, p, frac_enable)}")
|
||||
|
||||
204
lib_sw_pll/python/sw_pll/controller_model.py
Normal file
204
lib_sw_pll/python/sw_pll/controller_model.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
from sw_pll.dco_model import lut_dco, sigma_delta_dco, lock_count_threshold
|
||||
import numpy as np
|
||||
|
||||
|
||||
class pi_ctrl():
|
||||
"""
|
||||
Parent PI(I) controller class
|
||||
"""
|
||||
def __init__(self, Kp, Ki, Kii=None, i_windup_limit=None, ii_windup_limit=None, verbose=False):
|
||||
self.Kp = Kp
|
||||
self.Ki = Ki
|
||||
self.Kii = 0.0 if Kii is None else Kii
|
||||
self.i_windup_limit = i_windup_limit
|
||||
self.ii_windup_limit = ii_windup_limit
|
||||
|
||||
self.error_accum = 0.0 # Integral of error
|
||||
self.error_accum_accum = 0.0 # Double integral of error (optional)
|
||||
self.total_error = 0.0 # Calculated total error
|
||||
|
||||
self.verbose = verbose
|
||||
|
||||
if verbose:
|
||||
print(f"Init sw_pll_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}")
|
||||
|
||||
def _reset_controller(self):
|
||||
"""
|
||||
Reset any accumulated state
|
||||
"""
|
||||
self.error_accum = 0.0
|
||||
self.error_accum_accum = 0.0
|
||||
self.total_error = 0.0
|
||||
|
||||
def do_control_from_error(self, error):
|
||||
"""
|
||||
Calculate the LUT setting from the input error
|
||||
"""
|
||||
|
||||
# clamp integral terms to stop them irrecoverably drifting off.
|
||||
if self.i_windup_limit is None:
|
||||
self.error_accum = self.error_accum + error
|
||||
else:
|
||||
self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit)
|
||||
|
||||
if self.ii_windup_limit is None:
|
||||
self.error_accum_accum = self.error_accum_accum + self.error_accum
|
||||
else:
|
||||
self.error_accum_accum = np.clip(self.error_accum_accum + self.error_accum, -self.ii_windup_limit, self.ii_windup_limit)
|
||||
|
||||
error_p = self.Kp * error;
|
||||
error_i = self.Ki * self.error_accum
|
||||
error_ii = self.Kii * self.error_accum_accum
|
||||
|
||||
self.total_error = error_p + error_i + error_ii
|
||||
|
||||
if self.verbose:
|
||||
print(f"error: {error} error_p: {error_p} error_i: {error_i} error_ii: {error_ii} total error: {self.total_error}")
|
||||
|
||||
return self.total_error
|
||||
|
||||
|
||||
|
||||
##############################
|
||||
# LOOK UP TABLE IMPLEMENTATION
|
||||
##############################
|
||||
|
||||
class lut_pi_ctrl(pi_ctrl):
|
||||
"""
|
||||
This class instantiates a control loop instance. It takes a lookup table function which can be generated
|
||||
from the error_from_h class which allows it use the actual pre-calculated transfer function.
|
||||
Once instantiated, the do_control method runs the control loop.
|
||||
|
||||
This class forms the core of the simulator and allows the constants (K..) to be tuned to acheive the
|
||||
desired response. The function run_sim allows for a plot of a step resopnse input which allows this
|
||||
to be done visually.
|
||||
"""
|
||||
def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False):
|
||||
"""
|
||||
Create instance absed on specific control constants
|
||||
"""
|
||||
self.dco = lut_dco()
|
||||
self.lut_lookup_function = self.dco.get_lut()
|
||||
lut_size = self.dco.get_lut_size()
|
||||
self.diff = 0.0 # Most recent diff between expected and actual. Used by tests
|
||||
|
||||
|
||||
# By default set the nominal LUT index to half way
|
||||
if base_lut_index is None:
|
||||
base_lut_index = lut_size // 2
|
||||
self.base_lut_index = base_lut_index
|
||||
|
||||
# Set windup limit to the lut_size, which by default is double of the deflection from nominal
|
||||
i_windup_limit = lut_size / Ki if Ki != 0.0 else 0.0
|
||||
ii_windup_limit = 0.0 if Kii is None else lut_size / Kii if Kii != 0.0 else 0.0
|
||||
|
||||
pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, i_windup_limit=i_windup_limit, ii_windup_limit=ii_windup_limit, verbose=verbose)
|
||||
|
||||
self.verbose = verbose
|
||||
|
||||
if verbose:
|
||||
print(f"Init lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}")
|
||||
|
||||
def get_dco_control_from_error(self, error, first_loop=False):
|
||||
"""
|
||||
Calculate the LUT setting from the input error
|
||||
"""
|
||||
self.diff = error # Used by tests
|
||||
|
||||
if first_loop:
|
||||
pi_ctrl._reset_controller(self)
|
||||
error = 0.0
|
||||
|
||||
dco_ctrl = self.base_lut_index - pi_ctrl.do_control_from_error(self, error)
|
||||
|
||||
return None if first_loop else dco_ctrl
|
||||
|
||||
|
||||
######################################
|
||||
# SIGMA DELTA MODULATOR IMPLEMENTATION
|
||||
######################################
|
||||
|
||||
class sdm_pi_ctrl(pi_ctrl):
|
||||
def __init__(self, mod_init, sdm_in_max, sdm_in_min, Kp, Ki, Kii=None, verbose=False):
|
||||
"""
|
||||
Create instance absed on specific control constants
|
||||
"""
|
||||
pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=verbose)
|
||||
|
||||
# Low pass filter state
|
||||
self.alpha = 0.125
|
||||
self.iir_y = 0
|
||||
|
||||
# Nominal setting for SDM
|
||||
self.initial_setting = mod_init
|
||||
|
||||
# Limits for SDM output
|
||||
self.sdm_in_max = sdm_in_max
|
||||
self.sdm_in_min = sdm_in_min
|
||||
|
||||
# Lock status state
|
||||
self.lock_status = -1
|
||||
self.lock_count = lock_count_threshold
|
||||
|
||||
def do_control_from_error(self, error):
|
||||
"""
|
||||
Run the control loop. Also contains an additional
|
||||
low passs filtering stage.
|
||||
"""
|
||||
x = pi_ctrl.do_control_from_error(self, -error)
|
||||
x = int(x)
|
||||
|
||||
# Filter noise into DCO to reduce jitter
|
||||
# First order IIR, make A=0.125
|
||||
# y = y + A(x-y)
|
||||
|
||||
# self.iir_y = int(self.iir_y + (x - self.iir_y) * self.alpha)
|
||||
self.iir_y += (x - self.iir_y) >> 3 # This matches the firmware
|
||||
|
||||
sdm_in = self.initial_setting + self.iir_y
|
||||
|
||||
|
||||
if sdm_in > self.sdm_in_max:
|
||||
print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}")
|
||||
sdm_in = self. sdm_in_max
|
||||
self.lock_status = 1
|
||||
self.lock_count = lock_count_threshold
|
||||
|
||||
elif sdm_in < self.sdm_in_min:
|
||||
print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}")
|
||||
sdm_in = self.sdm_in_min
|
||||
self.lock_status = -1
|
||||
self.lock_count = lock_count_threshold
|
||||
|
||||
else:
|
||||
if self.lock_count > 0:
|
||||
self.lock_count -= 1
|
||||
else:
|
||||
self.lock_status = 0
|
||||
|
||||
return sdm_in, self.lock_status
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
This module is not intended to be run directly. This is here for internal testing only.
|
||||
"""
|
||||
Kp = 1.0
|
||||
Ki = 0.1
|
||||
Kii = 0.0
|
||||
|
||||
sw_pll = lut_pi_ctrl(Kp, Ki, Kii=Kii, verbose=True)
|
||||
for error_input in range(-10, 20):
|
||||
dco_ctrl = sw_pll.do_control_from_error(error_input)
|
||||
|
||||
mod_init = (sigma_delta_dco.sdm_in_max + sigma_delta_dco.sdm_in_min) / 2
|
||||
Kp = 0.0
|
||||
Ki = 0.1
|
||||
Kii = 0.1
|
||||
|
||||
sw_pll = sdm_pi_ctrl(mod_init, sigma_delta_dco.sdm_in_max, sigma_delta_dco.sdm_in_min, Kp, Ki, Kii=Kii, verbose=True)
|
||||
for error_input in range(-10, 20):
|
||||
dco_ctrl, lock_status = sw_pll.do_control_from_error(error_input)
|
||||
399
lib_sw_pll/python/sw_pll/dco_model.py
Normal file
399
lib_sw_pll/python/sw_pll/dco_model.py
Normal file
@@ -0,0 +1,399 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
from sw_pll.app_pll_model import register_file, app_pll_frac_calc
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
"""
|
||||
This file contains implementations of digitally controlled oscillators.
|
||||
It uses the app_pll_model underneath to turn a control signal into a
|
||||
calculated output frequency.
|
||||
|
||||
It currently contains two implementations of DCO:
|
||||
|
||||
- A lookup table version which is efficient in computation and offers
|
||||
a range of frequencies based on a pre-calculated look up table (LUT)
|
||||
- A Sigma Delta Modulator which typically uses a dedicated thread to
|
||||
run the modulator but results in lower noise in the audio spectrum
|
||||
"""
|
||||
|
||||
|
||||
lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"}
|
||||
lock_count_threshold = 10
|
||||
|
||||
##############################
|
||||
# LOOK UP TABLE IMPLEMENTATION
|
||||
##############################
|
||||
|
||||
class lut_dco:
|
||||
"""
|
||||
This class parses a pre-generated fractions.h file and builds a lookup table so that the values can be
|
||||
used by the sw_pll simulation. It may be used directly but is generally used a sub class of error_to_pll_output_frequency.
|
||||
"""
|
||||
|
||||
def __init__(self, header_file = "fractions.h", verbose=False): # fixed header_file name by pll_calc.py
|
||||
"""
|
||||
Constructor for the LUT DCO. Reads the pre-calculated header filed and produces the LUT which contains
|
||||
the pll fractional register settings (16b) for each of the entries. Also a
|
||||
"""
|
||||
|
||||
self.lut, self.min_frac, self.max_frac = self._read_lut_header(header_file)
|
||||
input_freq, F, R, f, p, OD, ACD = self._parse_register_file(register_file)
|
||||
self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD)
|
||||
|
||||
self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2] | app_pll_frac_calc.frac_enable_mask)
|
||||
self.lock_status = -1
|
||||
self.lock_count = lock_count_threshold
|
||||
|
||||
def _read_lut_header(self, header_file):
|
||||
"""
|
||||
read and parse the pre-written LUT
|
||||
"""
|
||||
if not os.path.exists(header_file):
|
||||
assert False, f"Please initialize a lut_dco to produce a parsable header file {header_file}"
|
||||
|
||||
with open(header_file) as hdr:
|
||||
header = hdr.readlines()
|
||||
min_frac = 1.0
|
||||
max_frac = 0.0
|
||||
for line in header:
|
||||
regex_ne = fr"frac_values_?\d*\[(\d+)].*"
|
||||
match = re.search(regex_ne, line)
|
||||
if match:
|
||||
num_entries = int(match.groups()[0])
|
||||
# print(f"num_entries: {num_entries}")
|
||||
lut = np.zeros(num_entries, dtype=np.uint16)
|
||||
|
||||
regex_fr = r"0x([0-9A-F]+).+Index:\s+(\d+).+=\s(0.\d+)"
|
||||
match = re.search(regex_fr, line)
|
||||
if match:
|
||||
reg, idx, frac = match.groups()
|
||||
reg = int(reg, 16)
|
||||
idx = int(idx)
|
||||
frac = float(frac)
|
||||
min_frac = frac if frac < min_frac else min_frac
|
||||
max_frac = frac if frac > max_frac else max_frac
|
||||
lut[idx] = reg
|
||||
|
||||
# print(f"min_frac: {min_frac} max_frac: {max_frac}")
|
||||
return lut, min_frac, max_frac
|
||||
|
||||
def _parse_register_file(self, register_file):
|
||||
"""
|
||||
This method reads the pre-saved register setup comments from get_pll_solution and parses them into parameters that
|
||||
can be used for later simulation.
|
||||
"""
|
||||
if not os.path.exists(register_file):
|
||||
assert False, f"Please initialize a lut_dco to produce a parsable register setup file {register_file}"
|
||||
|
||||
with open(register_file) as rf:
|
||||
reg_file = rf.read().replace('\n', '')
|
||||
input_freq = int(re.search(r".+Input freq:\s+(\d+).+", reg_file).groups()[0])
|
||||
F = int(re.search(r".+F:\s+(\d+).+", reg_file).groups()[0])
|
||||
R = int(re.search(r".+R:\s+(\d+).+", reg_file).groups()[0])
|
||||
f = int(re.search(r".+f:\s+(\d+).+", reg_file).groups()[0])
|
||||
p = int(re.search(r".+p:\s+(\d+).+", reg_file).groups()[0])
|
||||
OD = int(re.search(r".+OD:\s+(\d+).+", reg_file).groups()[0])
|
||||
ACD = int(re.search(r".+ACD:\s+(\d+).+", reg_file).groups()[0])
|
||||
|
||||
return input_freq, F, R, f, p, OD, ACD
|
||||
|
||||
def get_lut(self):
|
||||
"""
|
||||
Return the look up table
|
||||
"""
|
||||
return self.lut
|
||||
|
||||
def get_lut_size(self):
|
||||
"""
|
||||
Return the size of look up table
|
||||
"""
|
||||
return np.size(self.lut)
|
||||
|
||||
def print_stats(self, target_output_frequency):
|
||||
"""
|
||||
Returns a summary of the LUT range and steps.
|
||||
"""
|
||||
lut = self.get_lut()
|
||||
steps = np.size(lut)
|
||||
|
||||
register = int(lut[0])
|
||||
min_freq = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask)
|
||||
|
||||
register = int(lut[steps // 2])
|
||||
mid_freq = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask)
|
||||
|
||||
register = int(lut[-1])
|
||||
max_freq = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask)
|
||||
|
||||
ave_step_size = (max_freq - min_freq) / steps
|
||||
|
||||
print(f"LUT min_freq: {min_freq:.0f}Hz")
|
||||
print(f"LUT mid_freq: {mid_freq:.0f}Hz")
|
||||
print(f"LUT max_freq: {max_freq:.0f}Hz")
|
||||
print(f"LUT entries: {steps} ({steps*2} bytes)")
|
||||
print(f"LUT average step size: {ave_step_size:.6}Hz, PPM: {1e6 * ave_step_size/mid_freq:.6}")
|
||||
print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}")
|
||||
print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}")
|
||||
|
||||
return min_freq, mid_freq, max_freq, steps
|
||||
|
||||
|
||||
def plot_freq_range(self):
|
||||
"""
|
||||
Generates a plot of the frequency range of the LUT and
|
||||
visually shows the spacing of the discrete frequencies
|
||||
that it can produce.
|
||||
"""
|
||||
|
||||
frequencies = []
|
||||
for step in range(self.get_lut_size()):
|
||||
register = int(self.lut[step])
|
||||
self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask)
|
||||
frequencies.append(self.app_pll.get_output_frequency())
|
||||
|
||||
plt.clf()
|
||||
plt.plot(frequencies, color='green', marker='.', label='frequency')
|
||||
plt.title('PLL fractional range', fontsize=14)
|
||||
plt.xlabel(f'LUT index', fontsize=14)
|
||||
plt.ylabel('Frequency', fontsize=10)
|
||||
plt.legend(loc="upper right")
|
||||
plt.grid(True)
|
||||
# plt.show()
|
||||
plt.savefig("lut_dco_range.png", dpi=150)
|
||||
|
||||
def get_frequency_from_dco_control(self, dco_ctrl):
|
||||
"""
|
||||
given a set_point, a LUT, and an APP_PLL, calculate the frequency
|
||||
"""
|
||||
|
||||
if dco_ctrl is None:
|
||||
return self.last_output_frequency, self.lock_status
|
||||
|
||||
num_entries = self.get_lut_size()
|
||||
|
||||
set_point = int(dco_ctrl)
|
||||
if set_point < 0:
|
||||
set_point = 0
|
||||
self.lock_status = -1
|
||||
self.lock_count = lock_count_threshold
|
||||
elif set_point >= num_entries:
|
||||
set_point = num_entries - 1
|
||||
self.lock_status = 1
|
||||
self.lock_count = lock_count_threshold
|
||||
else:
|
||||
set_point = set_point
|
||||
if self.lock_count > 0:
|
||||
self.lock_count -= 1
|
||||
else:
|
||||
self.lock_status = 0
|
||||
|
||||
register = int(self.lut[set_point])
|
||||
|
||||
output_frequency = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask)
|
||||
self.last_output_frequency = output_frequency
|
||||
return output_frequency, self.lock_status
|
||||
|
||||
|
||||
|
||||
######################################
|
||||
# SIGMA DELTA MODULATOR IMPLEMENTATION
|
||||
######################################
|
||||
|
||||
class sdm:
|
||||
"""
|
||||
Experimental - taken from lib_xua synchronous branch
|
||||
Third order, 9 level output delta sigma. 20 bit unsigned input.
|
||||
"""
|
||||
# Limits for SDM modulator for stability
|
||||
sdm_in_max = 980000
|
||||
sdm_in_min = 60000
|
||||
|
||||
def __init__(self):
|
||||
# Delta sigma modulator state
|
||||
self.sdm_x1 = 0
|
||||
self.sdm_x2 = 0
|
||||
self.sdm_x3 = 0
|
||||
|
||||
# # generalized version without fixed point shifts. WIP!!
|
||||
# # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934)
|
||||
# # This is work in progress - the integer model matches the firmware better
|
||||
# def do_sigma_delta(self, sdm_in):
|
||||
# if sdm_in > self.sdm_in_max:
|
||||
# print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}")
|
||||
# sdm_in = self. sdm_in_max
|
||||
# self.lock_status = 1
|
||||
|
||||
# elif sdm_in < self.sdm_in_min:
|
||||
# print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}")
|
||||
# sdm_in = self.sdm_in_min
|
||||
# self.lock_status = -1
|
||||
|
||||
# else:
|
||||
# self.lock_status = 0
|
||||
|
||||
# sdm_out = int(self.sdm_x3 * 0.002197265625)
|
||||
|
||||
# if sdm_out > 8:
|
||||
# sdm_out = 8
|
||||
# if sdm_out < 0:
|
||||
# sdm_out = 0
|
||||
|
||||
# self.sdm_x3 += int((self.sdm_x2 * 0.03125) - (sdm_out * 768))
|
||||
# self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384))
|
||||
# self.sdm_x1 += int(sdm_in - (sdm_out * 131072))
|
||||
|
||||
# return int(sdm_out), self.lock_status
|
||||
|
||||
def do_sigma_delta_int(self, sdm_in):
|
||||
# takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934)
|
||||
# Third order, 9 level output delta sigma. 20 bit unsigned input.
|
||||
sdm_in = int(sdm_in)
|
||||
|
||||
sdm_out = ((self.sdm_x3<<4) + (self.sdm_x3<<1)) >> 13
|
||||
|
||||
if sdm_out > 8:
|
||||
sdm_out = 8
|
||||
if sdm_out < 0:
|
||||
sdm_out = 0
|
||||
|
||||
self.sdm_x3 += (self.sdm_x2>>5) - (sdm_out<<9) - (sdm_out<<8)
|
||||
self.sdm_x2 += (self.sdm_x1>>5) - (sdm_out<<14)
|
||||
self.sdm_x1 += sdm_in - (sdm_out<<17)
|
||||
|
||||
return sdm_out
|
||||
|
||||
|
||||
class sigma_delta_dco(sdm):
|
||||
"""
|
||||
DCO based on the sigma delta modulator
|
||||
PLL solution profiles depending on target output clock
|
||||
|
||||
These are designed to work with a SDM either running at
|
||||
1MHz:
|
||||
- 10ps jitter 100Hz-40kHz with very low freq noise floor -100dBc
|
||||
or 500kHz:
|
||||
- 50ps jitter 100Hz-40kHz with low freq noise floor -93dBc.
|
||||
|
||||
"""
|
||||
|
||||
profiles = {"24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":24.576e6, "mod_init":478151, "sdm_rate":1000000},
|
||||
"22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":22.5792e6, "mod_init":498283, "sdm_rate":1000000},
|
||||
"24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1, "output_frequency":24.576e6, "mod_init":553648, "sdm_rate":500000},
|
||||
"22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1, "output_frequency":22.5792e6, "mod_init":555326, "sdm_rate":500000}
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, profile):
|
||||
"""
|
||||
Create a sigmal delta DCO targetting either 24.576 or 22.5792MHz
|
||||
"""
|
||||
self.profile = profile
|
||||
self.p_value = 8 # 8 frac settings + 1 non frac setting
|
||||
|
||||
input_freq, F, R, f, p, OD, ACD, _, _, _ = list(self.profiles[profile].values())
|
||||
|
||||
self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD)
|
||||
self.sdm_out = 0
|
||||
self.f = 0
|
||||
|
||||
sdm.__init__(self)
|
||||
|
||||
def _sdm_out_to_freq(self, sdm_out):
|
||||
"""
|
||||
Translate the SDM steps to register settings
|
||||
"""
|
||||
if sdm_out == 0:
|
||||
# Step 0
|
||||
self.f = 0
|
||||
return self.app_pll.update_frac(self.f, self.p_value - 1, False)
|
||||
else:
|
||||
# Steps 1 to 8 inclusive
|
||||
self.f = sdm_out - 1
|
||||
return self.app_pll.update_frac(self.f, self.p_value - 1, True)
|
||||
|
||||
def do_modulate(self, input):
|
||||
"""
|
||||
Input a control value and output a SDM signal
|
||||
"""
|
||||
# self.sdm_out, lock_status = sdm.do_sigma_delta(self, input)
|
||||
self.sdm_out = sdm.do_sigma_delta_int(self, input)
|
||||
|
||||
frequency = self._sdm_out_to_freq(self.sdm_out)
|
||||
|
||||
return frequency
|
||||
|
||||
def print_stats(self):
|
||||
"""
|
||||
Returns a summary of the SDM range and steps.
|
||||
"""
|
||||
|
||||
steps = self.p_value + 1 # +1 we have frac off state
|
||||
min_freq = self._sdm_out_to_freq(0)
|
||||
max_freq = self._sdm_out_to_freq(self.p_value)
|
||||
target_output_frequency = self.profiles[self.profile]["output_frequency"]
|
||||
|
||||
|
||||
ave_step_size = (max_freq - min_freq) / steps
|
||||
|
||||
print(f"SDM min_freq: {min_freq:.0f}Hz")
|
||||
print(f"SDM max_freq: {max_freq:.0f}Hz")
|
||||
print(f"SDM steps: {steps}")
|
||||
print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}")
|
||||
print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}")
|
||||
|
||||
return min_freq, max_freq, steps
|
||||
|
||||
|
||||
def plot_freq_range(self):
|
||||
"""
|
||||
Generates a plot of the frequency range of the LUT and
|
||||
visually shows the spacing of the discrete frequencies
|
||||
that it can produce.
|
||||
"""
|
||||
|
||||
frequencies = []
|
||||
for step in range(self.p_value + 1): # +1 since p value is +1 in datasheet
|
||||
frequencies.append(self._sdm_out_to_freq(step))
|
||||
|
||||
plt.clf()
|
||||
plt.plot(frequencies, color='green', marker='.', label='frequency')
|
||||
plt.title('PLL fractional range', fontsize=14)
|
||||
plt.xlabel(f'SDM step', fontsize=14)
|
||||
plt.ylabel('Frequency', fontsize=10)
|
||||
plt.legend(loc="upper right")
|
||||
plt.grid(True)
|
||||
# plt.show()
|
||||
plt.savefig("sdm_dco_range.png", dpi=150)
|
||||
|
||||
def write_register_file(self):
|
||||
with open(register_file, "w") as reg_vals:
|
||||
reg_vals.write(f"/* Autogenerated SDM App PLL setup by {Path(__file__).name} using {self.profile} profile */\n")
|
||||
reg_vals.write(self.app_pll.gen_register_file_text())
|
||||
reg_vals.write(f"#define SW_PLL_SDM_CTRL_MID {self.profiles[self.profile]['mod_init']}\n")
|
||||
reg_vals.write(f"#define SW_PLL_SDM_RATE {self.profiles[self.profile]['sdm_rate']}\n")
|
||||
reg_vals.write("\n\n")
|
||||
|
||||
return register_file
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
This module is not intended to be run directly. This is here for internal testing only.
|
||||
"""
|
||||
dco = lut_dco()
|
||||
print(f"LUT size: {dco.get_lut_size()}")
|
||||
dco.plot_freq_range()
|
||||
dco.print_stats(12288000)
|
||||
|
||||
sdm_dco = sigma_delta_dco("24.576_1M")
|
||||
sdm_dco.write_register_file()
|
||||
sdm_dco.print_stats()
|
||||
sdm_dco.plot_freq_range()
|
||||
for i in range(30):
|
||||
output_frequency = sdm_dco.do_modulate(500000)
|
||||
# print(i, output_frequency)
|
||||
60
lib_sw_pll/python/sw_pll/pfd_model.py
Normal file
60
lib_sw_pll/python/sw_pll/pfd_model.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
class port_timer_pfd():
|
||||
def __init__(self, nominal_output_hz, nominal_control_rate_hz, ppm_range=1000):
|
||||
self.output_count_last = 0.0 # Integer value of last output_clock_count
|
||||
self.first_loop = True
|
||||
self.ppm_range = ppm_range
|
||||
self.expected_output_clock_count_inc = nominal_output_hz / nominal_control_rate_hz
|
||||
|
||||
def get_error(self, output_clock_count_float, period_fraction=1.0):
|
||||
|
||||
"""
|
||||
Calculate frequency error from the port output_count taken at the ref clock time.
|
||||
Note it uses a floating point input clock count to make simulation easier. This
|
||||
handles fractional counts and carries them properly.
|
||||
|
||||
If the time of sampling the output_count is not precisely 1.0 x the ref clock time,
|
||||
you may pass a fraction to allow for a proportional value using period_fraction. This is optional.
|
||||
"""
|
||||
|
||||
output_count_int = int(output_clock_count_float) # round down to nearest int to match hardware
|
||||
output_count_inc = output_count_int - self.output_count_last
|
||||
output_count_inc = output_count_inc / period_fraction
|
||||
|
||||
expected_output_clock_count = self.output_count_last + self.expected_output_clock_count_inc
|
||||
|
||||
error = output_count_inc - int(self.expected_output_clock_count_inc)
|
||||
|
||||
# Apply out of range detection so that the controller ignores startup or missed control loops (as per C)
|
||||
if abs(error) > (self.ppm_range / 1e6) * self.expected_output_clock_count_inc:
|
||||
# print("PFD FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc)
|
||||
self.first_loop = True
|
||||
else:
|
||||
self.first_loop = False
|
||||
|
||||
self.output_count_last = output_count_int
|
||||
|
||||
return error, self.first_loop
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
This module is not intended to be run directly. This is here for internal testing only.
|
||||
"""
|
||||
|
||||
nominal_output_hz = 12288000
|
||||
nominal_control_rate_hz = 93.75
|
||||
expected_output_clock_inc = nominal_output_hz / nominal_control_rate_hz
|
||||
|
||||
pfd = port_timer_pfd(nominal_output_hz, nominal_control_rate_hz)
|
||||
|
||||
output_clock_count_float = 0.0
|
||||
for output_hz in range(nominal_output_hz - 1000, nominal_output_hz + 1000, 10):
|
||||
output_clock_count_float += output_hz / nominal_output_hz * expected_output_clock_inc
|
||||
error = pfd.get_error(output_clock_count_float)
|
||||
print(f"actual output Hz: {output_hz} output_clock_count: {output_clock_count_float} error: {error}")
|
||||
|
||||
|
||||
270
lib_sw_pll/python/sw_pll/pll_calc.py
Normal file
270
lib_sw_pll/python/sw_pll/pll_calc.py
Normal file
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#======================================================================================================================
|
||||
# Copyright XMOS Ltd. 2021
|
||||
#
|
||||
# Original Author: Joe Golightly
|
||||
#
|
||||
# File type: Python script
|
||||
#
|
||||
# Description:
|
||||
# Stand-alone script to provide correct paramaters and registers settings to achieve desired clock frequency from
|
||||
# xcore.ai PLLs
|
||||
#
|
||||
#
|
||||
# Status:
|
||||
# Released for internal engineering use
|
||||
#
|
||||
#======================================================================================================================
|
||||
|
||||
# PLL Structure + Final Output Divider
|
||||
# PLL Macro
|
||||
# +--------------------------------------------------------------------+
|
||||
# | |
|
||||
# ref clk input ---+--- ref_div --- PFC --- Filter --- VCO ---+--- /2 --- output_div ---+--- Final Output Div ---> output
|
||||
# | | | |
|
||||
# | +----- feedback_div -----+ |
|
||||
# | | |
|
||||
# +-----------------------------+--------------------------------------+
|
||||
# |
|
||||
# XMOS Fractional-N Control (Secondary PLL only)
|
||||
|
||||
# Terminology Note
|
||||
# TruCircuits effectively incorporate the /2 into the feedback divider to allow the multiplication ratio to equal the feedback divider value.
|
||||
# I find this terminology very confusing (particularly when talking about the fractional-n system as well) so I have shown the actual structure above.
|
||||
# Specs of the above system, extracted from the TruCircuits specs.
|
||||
# - Reference divider values (R) 1-64
|
||||
# - Feedback divider values (F) 1-8192
|
||||
# - Output divider values (OD) 1-8
|
||||
# - VCO Frequency 720MHz - 3.6GHz
|
||||
|
||||
# PFC frequency = Divided ref clock frequency = Fpfc = Fin / R.
|
||||
# VCO frequency = Fvco = Fpfc * F.
|
||||
# PLL Output frequency = Fout = Fvco / (2 * OD).
|
||||
# Overall PLL Fout = (Fin * F) / (2 * R * OD)
|
||||
|
||||
# After the PLL, the output frequency can be further divided by the final output divider which can divide by 1 to 65536.
|
||||
|
||||
# For the App Clock output, there is an additional divide by 2 at the output to give a 50/50 duty cycle.
|
||||
|
||||
# All frequencies are in MHz.
|
||||
|
||||
import math
|
||||
from operator import itemgetter
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
def print_regs(args, op_div, fb_div, ref_div, fin_op_div):
|
||||
if args.app:
|
||||
app_pll_ctl_reg = (1 << 27) | (((op_div)-1) << 23) | ((int(fb_div[0])-1) << 8) | (ref_div-1)
|
||||
app_pll_div_reg = (1 << 31) | (fin_op_div-1)
|
||||
app_pll_frac_reg = 0
|
||||
if (fb_div[1] != 0): # Fractional Mode
|
||||
#print(fb_div)
|
||||
app_pll_frac_reg = (1 << 31) | ((fb_div[1]-1) << 8) | (fb_div[2]-1)
|
||||
|
||||
print('APP PLL CTL REG 0x' + '{:08X}'.format(app_pll_ctl_reg))
|
||||
print('APP PLL DIV REG 0x' + '{:08X}'.format(app_pll_div_reg))
|
||||
print('APP PLL FRAC REG 0x' + '{:08X}'.format(app_pll_frac_reg))
|
||||
else:
|
||||
pll_ctl_reg = (((op_div)-1) << 23) | ((int(fb_div[0])-1) << 8) | (ref_div-1)
|
||||
pll_div_reg = fin_op_div-1
|
||||
print('PLL CTL REG 0x' + '{:08X}'.format(pll_ctl_reg))
|
||||
print('SWITCH/CORE DIV REG 0x' + '{:08X}'.format(pll_div_reg))
|
||||
|
||||
def print_solution(args, ppm_error, input_freq, out_freq, vco_freq, ref_div, fb_div, op_div, fin_op_div):
|
||||
if (fb_div[1] != 0): # Fractional-N mode
|
||||
fb_div_string = '{:8.3f}'.format(fb_div[0]) + " (m = " + '{:3d}'.format(fb_div[1]) + ", n = " + '{:3d}'.format(fb_div[2]) + ")"
|
||||
else: # Integer mode
|
||||
fb_div_string = '{:4d}'.format(int(fb_div[0]))
|
||||
fb_div_string = '{:27}'.format(fb_div_string)
|
||||
print("Found solution: IN " + '{:3.3f}'.format(input_freq) + "MHz, OUT " + '{:3.6f}'.format(out_freq) + "MHz, VCO " + '{:4.2f}'.format(vco_freq) + "MHz, RD " + '{:2d}'.format(ref_div) + ", FD " + fb_div_string + ", OD " + '{:2d}'.format(op_div) + ", FOD " + '{:4d}'.format(fin_op_div) + ", ERR " + str(round((ppm_error),3)) + "ppm")
|
||||
print_regs(args, op_div, fb_div, ref_div, fin_op_div)
|
||||
|
||||
def print_solution_set(args, solutions):
|
||||
if args.raw:
|
||||
sol_str = ' Raw'
|
||||
else:
|
||||
sol_str = ' Filtered'
|
||||
print('*** Found ' + str(len(solutions)) + sol_str + ' Solutions ***')
|
||||
print('');
|
||||
for solution in solutions:
|
||||
print_solution(args, solution['ppm_error'], solution['input_freq'], solution['out_freq'], solution['vco_freq'], solution['ref_div'], solution['fb_div'], solution['op_div'], solution['fin_op_div'])
|
||||
|
||||
|
||||
def find_pll():
|
||||
parser = argparse.ArgumentParser(description='A script to calculate xcore.ai PLL settings to achieve desired output clock frequencies.')
|
||||
parser.add_argument("-i", "--input", type=float, help="PLL reference input frequency (MHz)", default=24.0)
|
||||
parser.add_argument("-t", "--target", type=float, help="Target output frequency (MHz)", default=600.0)
|
||||
parser.add_argument("-e", "--error", type=int, help="Allowable frequency error (ppm)", default=0)
|
||||
parser.add_argument("-m", "--denmax", type=int, help="Maximum denominator in frac-n config", default=0)
|
||||
parser.add_argument("-p", "--pfcmin", type=float, help="Minimum phase frequency comparator frequency (MHz)", default=1.0)
|
||||
parser.add_argument("-s", "--maxsol", type=int, help="Maximum number of raw solutions", default=200)
|
||||
parser.add_argument("-a", "--app", help="Use the App PLL", action="store_true")
|
||||
parser.add_argument("-r", "--raw", help="Show all solutions with no filtering", action="store_true")
|
||||
parser.add_argument("--header", help="Output a header file with fraction option reg values", action="store_true")
|
||||
parser.add_argument("--fracmax", type=float, help="Maximum fraction value to use", default=1.0)
|
||||
parser.add_argument("--fracmin", type=float, help="Minimum fraction value to use", default=0.0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
input_freq = args.input
|
||||
output_target = args.target
|
||||
ppm_error_max = args.error
|
||||
|
||||
# PLL Reference divider (R) 1-64
|
||||
ref_div_list = list(range(1,65))
|
||||
|
||||
# PLL Output divider (OD) 1-8
|
||||
op_div_list = list(range(1,9))
|
||||
|
||||
# Post PLL output divider 1-65536
|
||||
fin_op_div_list = list(range(1,65537))
|
||||
|
||||
# To create the feedback divider list we need to create the list of fractions we can use for when using frac-n mode.
|
||||
# den_max is the highest number we want to use as the denominator. This is useful to set as higher den_max values will have higher jitter so ideally this should be as low as possible.
|
||||
if args.app:
|
||||
pll_type = "App PLL"
|
||||
den_max = args.denmax
|
||||
else:
|
||||
pll_type = "Core PLL"
|
||||
den_max = 0
|
||||
if (args.denmax != 0):
|
||||
print("Core PLL does not have frac-n capability. Setting fracmax to 0")
|
||||
|
||||
# Fraction is m/n - m is numerator, n is denominator.
|
||||
frac_list_raw = []
|
||||
for m in range(1, den_max): # numerator from 1 to (den_max - 1)
|
||||
for n in range(m+1, den_max+1): # denominator from (numerator+1) to den_max
|
||||
frac = float(m)/float(n)
|
||||
if (args.fracmin < frac < args.fracmax):
|
||||
frac_list_raw.append([frac,m,n]) # We store the fraction as a float plus the integer numerator and denominator.
|
||||
|
||||
# Sort the fraction list based on the first element (the fractional value) then by the numerator of the fraction.
|
||||
# This means we'll get the more desirable fraction to use first. So 1/2 comes before 2/4 even though they both produce the fraction 0.5
|
||||
|
||||
frac_list_sorted = sorted(frac_list_raw, key=itemgetter(0,1))
|
||||
|
||||
# Now we want to weed out useless fractional divide values.
|
||||
# For example 1/2 and 2/4 both result in a divide ratio of 0.5 but 1/2 is preferable as the denominator is lower and so it will cause Phase Freq Comparator jitter to be at higher freq and so will be filtered more by the analogue loop filter.
|
||||
|
||||
frac_list = []
|
||||
last_item = 0.0
|
||||
for item in frac_list_sorted:
|
||||
if (item[0] > last_item): # Fractional value has to be greater than the last value or not useful
|
||||
#print("{0:.4f}".format(item[0]) + ", " + "{0:2d}".format(item[1]) + ", " + "{0:2d}".format(item[2]))
|
||||
frac_list.append(item)
|
||||
last_item = item[0]
|
||||
|
||||
# Output a header file containing the list of fractions as register values
|
||||
if args.header:
|
||||
with open("fractions.h", "w") as f:
|
||||
print("// Header file listing fraction options searched", file=f)
|
||||
print("// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register.", file=f)
|
||||
print("short frac_values_" + str(den_max) + "[" + str(len(frac_list)) +"] = {", file=f)
|
||||
for index, item in enumerate(frac_list):
|
||||
#print(item[0],item[1],item[2])
|
||||
frac_str = str(item[1]) + "/" + str(item[2])
|
||||
frac_str_line = "Index: {:>3} ".format(index) + 'Fraction: {:>5}'.format(frac_str) + " = {0:.4f}".format(item[0])
|
||||
print("0x" + format( (((item[1]-1) << 8) | (item[2]-1)),'>04X') + ", // " + frac_str_line, file=f)
|
||||
print("};", file=f)
|
||||
|
||||
# Feedback divider 1-8192 - we store a total list of the integer and fractional portions.
|
||||
fb_div_list = []
|
||||
for i in range(1,8193):
|
||||
fb_div_list.append([i,0,0]) # This is when not using frac-n mode.
|
||||
for item in frac_list:
|
||||
fb_div_list.append([i+item[0], item[1], item[2]])
|
||||
|
||||
# Actual Phase Comparator Limits
|
||||
pc_freq_min = 0.22 # 220kHz
|
||||
pc_freq_max = 1800.0 # 1.8GHz
|
||||
|
||||
# ... but for lower jitter ideally we'd use a higher minimum PC freq of ~1MHz.
|
||||
if (0.22 <= args.pfcmin <= 1800.0):
|
||||
pc_freq_min = args.pfcmin
|
||||
|
||||
# Actual VCO Limits (/1 output from PLL goes from 360 - 1800MHz so before the /2 this is 720 - 3600MHz)
|
||||
vco_freq_min = 720.0 #720MHz
|
||||
vco_freq_max = 3600.0 #3.6GHz
|
||||
|
||||
# New constraint of /1 output of PLL being 800MHz max (Both Core and App PLLs)
|
||||
# So this doesn't constrain VCO freq becuase you have the output divider.
|
||||
pll_out_max = 800.0 # 800MHz
|
||||
|
||||
# Print a summary of inputs
|
||||
|
||||
print("Using " + pll_type)
|
||||
print("Input Frequency = " + str(input_freq) + "MHz")
|
||||
print("Target Output Frequency = " + str(output_target) + "MHz")
|
||||
print("Allowable frequency error = " + str(ppm_error_max) + "ppm")
|
||||
print("Minimum Phase Frequency Comparator Frequency = " +str(pc_freq_min) + "MHz")
|
||||
if (pll_type == "App PLL"):
|
||||
print("Maximum denominator in frac-n config = " + str(den_max))
|
||||
print("")
|
||||
|
||||
# Main loop
|
||||
|
||||
raw_solutions = []
|
||||
|
||||
for ref_div in ref_div_list:
|
||||
pc_freq = input_freq/ref_div
|
||||
if (pc_freq_min <= pc_freq <= pc_freq_max): # Check pc clock is in valid freq range.
|
||||
for fb_div in fb_div_list:
|
||||
vco_freq = pc_freq * fb_div[0]
|
||||
if (vco_freq_min <= vco_freq <= vco_freq_max): # Check vco is in valid freq range.
|
||||
for op_div in op_div_list:
|
||||
pll_out_freq = vco_freq/(2*op_div)
|
||||
if (pll_out_freq <= pll_out_max): # Check PLL out freq is in valid freq range.
|
||||
for fin_op_div in fin_op_div_list:
|
||||
if (len(raw_solutions) >= args.maxsol): # Stop when we've reached the max number of raw solutions
|
||||
break
|
||||
# See if our output freq is what we want?
|
||||
out_freq = vco_freq/(2*op_div*fin_op_div)
|
||||
if args.app:
|
||||
out_freq = out_freq / 2 # fixed /2 for 50/50 duty cycle on app_clk output
|
||||
# Calculate parts per million error
|
||||
ppm_error = ((out_freq - output_target)/output_target) * 1000000.0
|
||||
if (abs(ppm_error) <= (ppm_error_max+0.01)): # Hack a tiny additional error in to handle the floating point calc errors.
|
||||
raw_solutions.append({'ppm_error':ppm_error, 'input_freq':input_freq, 'out_freq':out_freq, 'vco_freq':vco_freq, 'ref_div':ref_div, 'fb_div':fb_div, 'op_div':op_div, 'fin_op_div':fin_op_div})
|
||||
if (out_freq < output_target):
|
||||
break
|
||||
|
||||
# First filter out less desirable solutions with the same vco frequency and RD value. Keep the results with the highest PLL OD value.
|
||||
|
||||
# print_solution_set(raw_solutions)
|
||||
|
||||
solutions_sorted1 = sorted(raw_solutions, key=itemgetter('op_div'), reverse=True) # OD, higher first
|
||||
solutions_sorted2 = sorted(solutions_sorted1, key=itemgetter('ref_div')) # Ref Div, lower first
|
||||
solutions_sorted3 = sorted(solutions_sorted2, key=itemgetter('vco_freq'), reverse=True) # vco, higher first
|
||||
|
||||
# print_solution_set(solutions_sorted3)
|
||||
|
||||
filtered_solutions = []
|
||||
for count, solution in enumerate(solutions_sorted3):
|
||||
if count == 0:
|
||||
filtered_solutions.append(solution)
|
||||
else:
|
||||
# Only keep solution If vco or ref_div values are different from last solution
|
||||
if (solution['vco_freq'] != last_solution['vco_freq']) | (solution['ref_div'] != last_solution['ref_div']):
|
||||
filtered_solutions.append(solution)
|
||||
last_solution = solution
|
||||
|
||||
# print_solution_set(filtered_solutions)
|
||||
|
||||
# Final overall sort with lowest ref divider first
|
||||
final_filtered_solutions = sorted(filtered_solutions, key=itemgetter('ref_div'))
|
||||
|
||||
if args.raw:
|
||||
print_solution_set(args, raw_solutions)
|
||||
else:
|
||||
print_solution_set(args, final_filtered_solutions)
|
||||
|
||||
|
||||
|
||||
# When invoked as main program, invoke the profiler on a script
|
||||
if __name__ == '__main__':
|
||||
find_pll()
|
||||
308
lib_sw_pll/python/sw_pll/sw_pll_sim.py
Normal file
308
lib_sw_pll/python/sw_pll/sw_pll_sim.py
Normal file
@@ -0,0 +1,308 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
from sw_pll.app_pll_model import get_pll_solution
|
||||
from sw_pll.pfd_model import port_timer_pfd
|
||||
from sw_pll.dco_model import lut_dco, sigma_delta_dco, lock_status_lookup
|
||||
from sw_pll.controller_model import lut_pi_ctrl, sdm_pi_ctrl
|
||||
from sw_pll.analysis_tools import audio_modulator
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
|
||||
def plot_simulation(freq_log, target_freq_log, real_time_log, name="sw_pll_tracking.png"):
|
||||
plt.clf()
|
||||
plt.plot(real_time_log, freq_log, color='red', marker='.', label='actual frequency')
|
||||
plt.plot(real_time_log, target_freq_log, color='blue', marker='.', label='target frequency')
|
||||
plt.title('PLL tracking', fontsize=14)
|
||||
plt.xlabel(f'Time in seconds', fontsize=10)
|
||||
plt.ylabel('Frequency', fontsize=10)
|
||||
plt.legend(loc="upper right")
|
||||
plt.grid(True)
|
||||
# plt.show()
|
||||
plt.savefig(name, dpi=150)
|
||||
|
||||
|
||||
##############################
|
||||
# LOOK UP TABLE IMPLEMENTATION
|
||||
##############################
|
||||
|
||||
class sim_sw_pll_lut:
|
||||
"""
|
||||
Complete SW PLL simulation class which contains all of the components including
|
||||
Phase Frequency Detector, Controller and Digitally Controlled Oscillator using
|
||||
a Look Up Table method.
|
||||
"""
|
||||
def __init__( self,
|
||||
target_output_frequency,
|
||||
nominal_nominal_control_rate_frequency,
|
||||
Kp,
|
||||
Ki,
|
||||
Kii=None):
|
||||
"""
|
||||
Init a Lookup Table based SW_PLL instance
|
||||
"""
|
||||
|
||||
self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency)
|
||||
self.controller = lut_pi_ctrl(Kp, Ki, Kii=Kii, verbose=False)
|
||||
self.dco = lut_dco(verbose=False)
|
||||
|
||||
self.target_output_frequency = target_output_frequency
|
||||
self.time = 0.0
|
||||
self.control_time_inc = 1 / nominal_nominal_control_rate_frequency
|
||||
|
||||
def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False):
|
||||
"""
|
||||
This should be called once every control period.
|
||||
|
||||
output_clock_count is fed into the PDF and period_fraction is an optional jitter
|
||||
reduction term where the control period is not exact, but can be compensated for.
|
||||
"""
|
||||
|
||||
error, first_loop = self.pfd.get_error(output_clock_count, period_fraction=period_fraction)
|
||||
dco_ctl = self.controller.get_dco_control_from_error(error, first_loop=first_loop)
|
||||
output_frequency, lock_status = self.dco.get_frequency_from_dco_control(dco_ctl)
|
||||
if first_loop: # We cannot claim to be locked if the PFD sees an error
|
||||
lock_status = -1
|
||||
|
||||
if verbose:
|
||||
print(f"Raw error: {error}")
|
||||
print(f"dco_ctl: {dco_ctl}")
|
||||
print(f"Output_frequency: {output_frequency}")
|
||||
print(f"Lock status: {lock_status_lookup[lock_status]}")
|
||||
|
||||
return output_frequency, lock_status
|
||||
|
||||
|
||||
|
||||
def run_lut_sw_pll_sim():
|
||||
"""
|
||||
Test program / example showing how to run the simulator object
|
||||
"""
|
||||
|
||||
# Example profiles to produce typical frequencies seen in audio systems. ALl assume 24MHz input clock to the hardware PLL.
|
||||
profiles = [
|
||||
# 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size
|
||||
{"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95},
|
||||
# 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size
|
||||
{"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905},
|
||||
# 2 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size
|
||||
{"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81},
|
||||
# 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size
|
||||
{"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884},
|
||||
# 4 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size
|
||||
{"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806},
|
||||
]
|
||||
|
||||
profile_used = 1
|
||||
profile = profiles[profile_used]
|
||||
|
||||
nominal_output_hz = profile["target_output_frequency"]
|
||||
|
||||
# This generates the needed header files read later by sim_sw_pll_lut
|
||||
# 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size
|
||||
get_pll_solution(24000000, nominal_output_hz, max_denom=80, min_F=200, ppm_max=5, fracmin=0.695, fracmax=0.905)
|
||||
|
||||
output_frequency = nominal_output_hz
|
||||
nominal_control_rate_hz = profile["nominal_ref_frequency"] / 512
|
||||
simulation_iterations = 100
|
||||
Kp = 0.0
|
||||
Ki = 1.0
|
||||
Kii = 0.0
|
||||
|
||||
sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii)
|
||||
sw_pll.dco.print_stats(nominal_output_hz)
|
||||
|
||||
output_clock_count = 0
|
||||
|
||||
test_tone_hz = 1000
|
||||
audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=48000, test_tone_hz=test_tone_hz)
|
||||
|
||||
|
||||
freq_log = []
|
||||
target_freq_log = []
|
||||
real_time_log = []
|
||||
real_time = 0.0
|
||||
period_fraction = 1.0
|
||||
|
||||
ppm_shift = +50
|
||||
|
||||
for loop in range(simulation_iterations):
|
||||
output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=False)
|
||||
|
||||
# Now work out how many output clock counts this translates to
|
||||
measured_clock_count_inc = output_frequency / nominal_control_rate_hz * (1 - ppm_shift / 1e6)
|
||||
|
||||
# Add some jitter to the output_count to test jitter compensation
|
||||
jitter_amplitude = 100 # measured in output clock counts
|
||||
clock_count_sampling_jitter = jitter_amplitude * (np.random.sample() - 0.5)
|
||||
period_fraction = (measured_clock_count_inc + clock_count_sampling_jitter) * measured_clock_count_inc
|
||||
|
||||
output_clock_count += measured_clock_count_inc * period_fraction
|
||||
|
||||
real_time_log.append(real_time)
|
||||
target_output_frequency = nominal_output_hz * (1 + ppm_shift / 1e6)
|
||||
target_freq_log.append(target_output_frequency)
|
||||
freq_log.append(output_frequency)
|
||||
|
||||
time_inc = 1 / nominal_control_rate_hz
|
||||
scaled_frequency_shift = test_tone_hz * (output_frequency - target_output_frequency) / target_output_frequency
|
||||
audio.apply_frequency_deviation(real_time, real_time + time_inc, scaled_frequency_shift)
|
||||
|
||||
real_time += time_inc
|
||||
|
||||
|
||||
plot_simulation(freq_log, target_freq_log, real_time_log, "tracking_lut.png")
|
||||
|
||||
audio.modulate_waveform()
|
||||
audio.save_modulated_wav("modulated_tone_1000Hz_lut.wav")
|
||||
audio.plot_modulated_fft("modulated_fft_lut.png", skip_s=real_time / 2) # skip so we ignore the inital lock period
|
||||
|
||||
|
||||
|
||||
######################################
|
||||
# SIGMA DELTA MODULATOR IMPLEMENTATION
|
||||
######################################
|
||||
|
||||
class sim_sw_pll_sd:
|
||||
"""
|
||||
Complete SW PLL simulation class which contains all of the components including
|
||||
Phase Frequency Detector, Controller and Digitally Controlled Oscillator using
|
||||
a Sigma Delta Modulator method.
|
||||
"""
|
||||
|
||||
def __init__( self,
|
||||
target_output_frequency,
|
||||
nominal_nominal_control_rate_frequency,
|
||||
Kp,
|
||||
Ki,
|
||||
Kii=None):
|
||||
"""
|
||||
Init a Sigma Delta Modulator based SW_PLL instance
|
||||
"""
|
||||
|
||||
self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=3000)
|
||||
self.dco = sigma_delta_dco("24.576_1M")
|
||||
self.controller = sdm_pi_ctrl( (self.dco.sdm_in_max + self.dco.sdm_in_min) / 2,
|
||||
self.dco.sdm_in_max,
|
||||
self.dco.sdm_in_min,
|
||||
Kp,
|
||||
Ki,
|
||||
Kii)
|
||||
|
||||
self.target_output_frequency = target_output_frequency
|
||||
self.time = 0.0
|
||||
self.control_time_inc = 1 / nominal_nominal_control_rate_frequency
|
||||
|
||||
self.control_setting = (self.controller.sdm_in_max + self.controller.sdm_in_min) / 2 # Mid way
|
||||
|
||||
|
||||
def do_control_loop(self, output_clock_count, verbose=False):
|
||||
"""
|
||||
Run the control loop (which runs at a tiny fraction of the SDM rate)
|
||||
This should be called once every control period.
|
||||
|
||||
output_clock_count is fed into the PDF and period_fraction is an optional jitter
|
||||
reduction term where the control period is not exact, but can be compensated for.
|
||||
"""
|
||||
|
||||
error, first_loop = self.pfd.get_error(output_clock_count)
|
||||
ctrl_output, lock_status = self.controller.do_control_from_error(error)
|
||||
self.control_setting = ctrl_output
|
||||
|
||||
if verbose:
|
||||
print(f"Raw error: {error}")
|
||||
print(f"ctrl_output: {ctrl_output}")
|
||||
print(f"Lock status: {lock_status_lookup[lock_status]}")
|
||||
|
||||
return self.control_setting
|
||||
|
||||
def do_sigma_delta(self):
|
||||
"""
|
||||
Run the SDM which needs to be run constantly at the SDM rate.
|
||||
See DCO (dco_model) for details
|
||||
"""
|
||||
frequncy = self.dco.do_modulate(self.control_setting)
|
||||
|
||||
return frequncy
|
||||
|
||||
|
||||
def run_sd_sw_pll_sim():
|
||||
"""
|
||||
Test program / example showing how to run the simulator object
|
||||
"""
|
||||
nominal_output_hz = 24576000
|
||||
nominal_control_rate_hz = 100
|
||||
nominal_sd_rate_hz = 1e6
|
||||
output_frequency = nominal_output_hz
|
||||
|
||||
simulation_iterations = 1000000
|
||||
Kp = 0.0
|
||||
Ki = 32.0
|
||||
Kii = 0.25
|
||||
|
||||
sw_pll = sim_sw_pll_sd(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii)
|
||||
sw_pll.dco.write_register_file()
|
||||
sw_pll.dco.print_stats()
|
||||
|
||||
output_clock_count = 0
|
||||
|
||||
test_tone_hz = 1000
|
||||
audio = audio_modulator(simulation_iterations * 1 / nominal_sd_rate_hz, sample_rate=6144000, test_tone_hz=test_tone_hz)
|
||||
|
||||
freq_log = []
|
||||
target_freq_log = []
|
||||
real_time_log = []
|
||||
real_time = 0.0
|
||||
|
||||
ppm_shift = +50
|
||||
|
||||
# For working out when to do control calls
|
||||
control_time_inc = 1 / nominal_control_rate_hz
|
||||
control_time_trigger = control_time_inc
|
||||
|
||||
for loop in range(simulation_iterations):
|
||||
|
||||
output_frequency = sw_pll.do_sigma_delta()
|
||||
|
||||
# Log results
|
||||
freq_log.append(output_frequency)
|
||||
target_output_frequency = nominal_output_hz * (1 + ppm_shift / 1e6)
|
||||
target_freq_log.append(target_output_frequency)
|
||||
real_time_log.append(real_time)
|
||||
|
||||
# Modulate tone
|
||||
sdm_time_inc = 1 / nominal_sd_rate_hz
|
||||
scaled_frequency_shift = test_tone_hz * (output_frequency - target_output_frequency) / target_output_frequency
|
||||
audio.apply_frequency_deviation(real_time, real_time + sdm_time_inc, scaled_frequency_shift)
|
||||
|
||||
# Accumulate the real number of output clocks
|
||||
output_clock_count += output_frequency / nominal_sd_rate_hz * (1 - ppm_shift / 1e6)
|
||||
|
||||
# Check for control loop run ready
|
||||
if real_time > control_time_trigger:
|
||||
control_time_trigger += control_time_inc
|
||||
|
||||
# Now work out how many output clock counts this translates to
|
||||
sw_pll.do_control_loop(output_clock_count)
|
||||
|
||||
real_time += sdm_time_inc
|
||||
|
||||
|
||||
plot_simulation(freq_log, target_freq_log, real_time_log, "tracking_sdm.png")
|
||||
|
||||
audio.modulate_waveform()
|
||||
audio.save_modulated_wav("modulated_tone_1000Hz_sdm.wav")
|
||||
audio.plot_modulated_fft("modulated_fft_sdm.png", skip_s=real_time / 2) # skip so we ignore the inital lock period
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 2:
|
||||
assert 0, "Please select either LUT or SDM: sw_pll_sim.py <LUT/SDM>"
|
||||
if sys.argv[1] == "LUT":
|
||||
run_lut_sw_pll_sim() # Run LUT sim - generates "register_setup.h" and "fractions.h"
|
||||
elif sys.argv[1] == "SDM":
|
||||
run_sd_sw_pll_sim() # Run SDM sim - generates "register_setup.h"
|
||||
else:
|
||||
assert 0, "Please select either LUT or SDM: sw_pll_sim.py <LUT/SDM>"
|
||||
19
lib_sw_pll/settings.yml
Normal file
19
lib_sw_pll/settings.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
################################
|
||||
# Settings file for docs build #
|
||||
################################
|
||||
|
||||
project: SW_PLL
|
||||
version: 2.2.0
|
||||
documentation:
|
||||
title: Software PLL
|
||||
root_doc: doc/index.rst
|
||||
exclude_patterns_path: doc/exclude-patterns.inc
|
||||
substitutions_path: doc/substitutions.inc
|
||||
doxygen_projects:
|
||||
SW_PLL:
|
||||
doxyfile_path: doc/Doxyfile.inc
|
||||
pdfs:
|
||||
doc/index:
|
||||
pdf_title: '{{project}} Programming Guide \newline'
|
||||
pdf_filename: '{{project}}_programming_guide_v{{version}}'
|
||||
17
lib_sw_pll/setup.py
Normal file
17
lib_sw_pll/setup.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright 2023 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="sw_pll",
|
||||
version="2.0.0",
|
||||
packages=["sw_pll"],
|
||||
package_dir={
|
||||
"": "python"
|
||||
},
|
||||
install_requires=[
|
||||
"numpy",
|
||||
"matplotlib",
|
||||
"pyvcd"
|
||||
]
|
||||
)
|
||||
32
lib_sw_pll/tools/ci/checkout-submodules.sh
Normal file
32
lib_sw_pll/tools/ci/checkout-submodules.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#! /usr/bin/env bash
|
||||
#
|
||||
# clone required repos and install python packages needed by ci
|
||||
|
||||
# clone NAME URL HASH
|
||||
#
|
||||
# supposedly, this is the quickest way to clone a single
|
||||
# commit from a remote git repo
|
||||
clone() {
|
||||
mkdir $1
|
||||
pushd $1
|
||||
git init
|
||||
git remote add origin $2
|
||||
git fetch --depth 1 origin $3
|
||||
git checkout FETCH_HEAD
|
||||
git submodule update --init --recursive --depth 1 --jobs $(nproc)
|
||||
popd
|
||||
}
|
||||
|
||||
set -ex
|
||||
rm -rf modules
|
||||
mkdir -p modules
|
||||
pushd modules
|
||||
|
||||
clone fwk_core git@github.com:xmos/fwk_core.git v1.0.2
|
||||
clone fwk_io git@github.com:xmos/fwk_io.git v3.3.0
|
||||
|
||||
clone infr_scripts_py git@github.com:xmos/infr_scripts_py.git 1d767cbe89a3223da7a4e27c283fb96ee2a279c9
|
||||
clone infr_apps git@github.com:xmos/infr_apps.git 9a0e6899ffae42d82e6047b49e1186eee42289af
|
||||
|
||||
pip install -e infr_apps -e infr_scripts_py
|
||||
popd
|
||||
8
lib_sw_pll/tools/ci/do-ci-build.sh
Normal file
8
lib_sw_pll/tools/ci/do-ci-build.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#! /usr/bin/env bash
|
||||
#
|
||||
# build stuff
|
||||
|
||||
set -ex
|
||||
|
||||
cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake
|
||||
cmake --build build --target all --target test_app --target test_app_low_level_api --target test_app_sdm_dco --target test_app_sdm_ctrl --target simple_lut --target simple_sdm --target i2s_slave_lut -j$(nproc)
|
||||
11
lib_sw_pll/tools/ci/do-ci-tests.sh
Normal file
11
lib_sw_pll/tools/ci/do-ci-tests.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#! /usr/bin/env bash
|
||||
#
|
||||
# test stuff
|
||||
|
||||
set -ex
|
||||
|
||||
pushd tests
|
||||
pytest --junitxml=results.xml -rA -v --durations=0 -o junit_logging=all
|
||||
ls bin
|
||||
popd
|
||||
|
||||
11
lib_sw_pll/tools/ci/do-model-examples.sh
Normal file
11
lib_sw_pll/tools/ci/do-model-examples.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#! /usr/bin/env bash
|
||||
#
|
||||
# test stuff
|
||||
|
||||
set -ex
|
||||
|
||||
pushd python/sw_pll
|
||||
python sw_pll_sim.py LUT
|
||||
python sw_pll_sim.py SDM
|
||||
popd
|
||||
|
||||
Reference in New Issue
Block a user