Compare commits

...

12 Commits

Author SHA1 Message Date
morgan f58ab13e9b improve jitter generation
wave_gen: change to gussian jitter distribution
wrapper: reduce gtx_jitter to meet real world performance
docs: remove noise limitation
2023-12-13 17:14:20 +08:00
morgan 734e87eede add dcxo frequency change settling time
wrapper: add default settling value
sim: gate frequency change behind settling time
example: update adpll_period to 200μs to account for settling delay
example: update docs for adpll_period minimum value
docs: remove the settling time limitation
2023-12-13 15:53:11 +08:00
morgan 6e562732e5 turn .py files into wrpll_simulation package
py files & notebook: update import
docs: update install instructions
2023-12-13 15:51:46 +08:00
morgan 8be1abe80b git: ignore __pycache__ 2023-12-13 15:46:04 +08:00
morgan 2e2a346610 toolchain: migrate from requirements.txt to poetry
poetry: add deps from requirements.txt
flake: import poetry with poetry2nix
doc: update installation section
2023-12-13 15:46:04 +08:00
morgan fa8ad0b686 flake: remove flake-util and add formatter 2023-12-13 15:46:04 +08:00
morgan 97db1ea08b docs: update limitation section 2023-12-13 15:45:29 +08:00
morgan daabecc4b6 add i2c communication delay
wrapper: add default delay and convert to timesteps
sim: add i2c delay before changing DCXOs frequencies
2023-12-13 15:45:29 +08:00
morgan 7c4a680787 notebook: update examples
refactor into import, simulation and plotting 3 codeblocks
remove extra config for wrapper
add docs string for RNG
change adpll_period & start_up_delay unit
2023-12-13 15:44:11 +08:00
morgan 0b724e84da wrapper: QoL improvement
time array is generated inside of wrapper
set default value for RNGs and ddmtd config
adpll_write_period and start_up_delay argument use seconds as their unit
freq_diff_error RNG now used the seed argument and uniform RNG
2023-12-12 13:33:02 +08:00
morgan 0a66f4c343 fix helper PLL glitches
sim: rename collector to phase_collector
sim: add period_collector for gtx tags
sim: trigger helper PLL after period_collector_r
sim: remove cycle slip compensation
sim: generator gtx in time loop
wave_gen: replace square generator with phase accumulator
wave_gen&wrpll: add white_noise generator
docs: remove section & png about cycle slipping and deglitcher failure
2023-12-12 13:32:22 +08:00
morgan da5908e754 sim: improve code style and cleanup
sim & wave_gen: use njit instead of jit(nopython=True)
sim: removing extra == 1
wrapper: remove unused import
2023-12-12 10:27:22 +08:00
17 changed files with 3190 additions and 347 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.direnv/**
.vscode/**
.venv/**
__pycache__

View File

@ -2,18 +2,24 @@
A time domain simulation for WRPLL
## Installing dependencies
## Installation
### Poetry:
```bash
poetry install
poetry run jupyter lab
```
### Poetry shell:
```bash
poetry install
poetry shell
```
### Nix:
```bash
nix develop
```
### Others:
```bash
python -m venv .venv
source .venv/bin/activate
(venv) pip install -r requirements.txt
```
## Quick start
- Three notebook examples are included
@ -48,21 +54,3 @@ source .venv/bin/activate
</details><br>
## Limitation
As the simulation is not cycle nor delay accurate, there will be more glitches than the hardware implementation
### Helper PLL glitches (remedies are added to follow hardware behavior)
- Cycle slipping issue will appear as $|\Delta{period}| \sim N$
- During hardware testing, slipping issue is not common
- It's recommended to turn cycle_slip_comp ON to reduce slipping and have a more accurate simulation
![cycle_slip](img/cycle_slipping.png)
- Deglitcher fail issue will appear as $|\Delta{period}| \sim N/2$
- There are no such issue for hardware
- It's recommended to set blind_period higher than the hardware setting (around 300 is sufficient)
![deglitch_fail](img/deglitch_fail.png)

View File

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
@ -18,13 +18,34 @@
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1698974481,
"narHash": "sha256-yPncV9Ohdz1zPZxYHQf47S8S0VrnhV7nNhCawY46hDA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "4bb5e752616262457bc7ca5882192a564c0472d2",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1701539137,
"narHash": "sha256-nVO/5QYpf1GwjvtpXhyxx5M3U/WN0MwBro4Lsk+9mL0=",
"lastModified": 1702233072,
"narHash": "sha256-H5G2wgbim2Ku6G6w+NSaQaauv6B6DlPhY9fMvArKqRo=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "933d7dc155096e7575d207be6fb7792bc9f34f6d",
"rev": "781e2a9797ecf0f146e81425c822dca69fe4a348",
"type": "github"
},
"original": {
@ -34,10 +55,34 @@
"type": "github"
}
},
"root": {
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"nixpkgs"
],
"systems": "systems_2",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1702365004,
"narHash": "sha256-IRFvmyP1uk1hchRVxaXTqu6YoZCvMM/NVtUf2hD2Tag=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "c12ac880114d52a3cad5fa02b00f2e2090e89982",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
},
"systems": {
@ -54,6 +99,41 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"id": "systems",
"type": "indirect"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1699786194,
"narHash": "sha256-3h3EH1FXQkIeAuzaWB+nK0XK54uSD46pp+dMD3gAcB4=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "e82f32aa7f06bbbd56d7b12186d555223dc399d1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",

View File

@ -1,38 +1,51 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
flake-utils.url = "github:numtide/flake-utils";
poetry2nix = {
url = "github:nix-community/poetry2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system:
outputs = { nixpkgs, poetry2nix, ... }:
let
pkgs = import nixpkgs {
inherit system;
pkgs = import nixpkgs { system = "x86_64-linux"; };
poetry2nixlib = (poetry2nix.lib.mkPoetry2Nix { inherit pkgs; });
poetryEnv = poetry2nixlib.mkPoetryEnv {
python = pkgs.python3;
projectDir = ./.;
pyproject = ./pyproject.toml;
poetrylock = ./poetry.lock;
overrides = poetry2nixlib.overrides.withDefaults (self: super: {
trace-updater = super.trace-updater.overridePythonAttrs (
# ModuleNotFoundError: No module named 'setuptools'
old: {
buildInputs = (old.buildInputs or [ ]) ++ [ super.setuptools ];
});
tsdownsample = super.tsdownsample.override {
# for bypassing building with maturin
preferWheel = true;
};
plotly-resampler = super.plotly-resampler.overridePythonAttrs (
# ModuleNotFoundError: No module named 'poetry'
old: {
buildInputs = (old.buildInputs or [ ]) ++ [ super.poetry-core ];
});
scipy = super.scipy.override {
# fail to build
preferWheel = true;
};
});
};
in rec {
devShell = pkgs.mkShell {
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
devShell.x86_64-linux = pkgs.mkShell {
name = "WRPLL-sim";
venvDir = "./.venv";
buildInputs = with pkgs.python3Packages; [
python
numba
numpy
notebook # use jupyter notebook without missing libstdc++.so.6
venvShellHook
];
# Only run once when venv is created
postVenvCreation = ''
unset SOURCE_DATE_EPOCH
pip install -r requirements.txt
'';
postShellHook = ''
# allow pip to install wheels
unset SOURCE_DATE_EPOCH
'';
buildInputs = with pkgs; [ poetryEnv poetry nixpkgs-fmt ];
};
}
);
}
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

2742
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

23
pyproject.toml Normal file
View File

@ -0,0 +1,23 @@
[tool.poetry]
name = "wrpll-simulation"
version = "0.1.0"
description = ""
authors = ["morgan <mc@m-labs.hk>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
scipy = "^1.11.4"
numpy = "^1.26.2"
ipykernel = "^6.27.1"
ipywidgets = "^8.1.1"
jupyter = "^1.0.0"
numba = "^0.58.1"
pandas = "^2.1.4"
plotly = "^5.18.0"
plotly-resampler = "^0.9.1"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@ -1,11 +0,0 @@
scipy
numpy
ipykernel
ipywidgets
jupyter
jupyterlab
notebook
numba
pandas
plotly
plotly-resampler

View File

@ -39,74 +39,76 @@
"from plotly.subplots import make_subplots\n",
"import plotly.graph_objects as go\n",
"import numpy as np\n",
"from wave_gen import square_arr\n",
"from wrpll import WRPLL_simulator\n",
"\n",
"from wrpll_simulation.wrpll import WRPLL_simulator"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# settings\n",
"timestep = 4e-10 # even number is recommended to avoid strange glitches\n",
"total_steps = 300_000_000\n",
"timestep = 1e-10\n",
"total_steps = 200_000_000\n",
"sim_mode = \"both\"\n",
"adpll_period = int(100e-6/timestep) # in simulation steps, 100μs is minimum, smaller = more frequency adjustment and filter calulation per unit time\n",
"start_up_delay = int(100e-6/timestep) # in simulation steps, the frequency adjustment is DISABLE until steps > start_up_delay\n",
"adpll_period = 200e-6 # in seconds, the period that pll will trigger, (minimum > total DCXO frequency change delay)\n",
"start_up_delay = 100e-6 # in seconds, the frequency adjustment is DISABLE until time > start_up_delay\n",
"\n",
"gtx_freq = 125_001_519\n",
"\n",
"helper_init_freq = gtx_freq * (4096-1)/4096\n",
"\n",
"helper_filter = {\n",
" \"KP\": 2,\n",
" \"KI\": 4,\n",
" \"KI\": 0.5,\n",
" \"KD\": 0,\n",
"}\n",
"\n",
"main_filter = { \n",
"main_filter = {\n",
" \"KP\": 12,\n",
" \"KI\": 0,\n",
" \"KD\": 0,\n",
"}\n",
"\n",
"\n",
"t = np.linspace(0, timestep*total_steps, total_steps)\n",
"\n",
"# simulation will start with\n",
"# - random phase for main & helper\n",
"# - gussian based base_adpll error\n",
"# - gussian jitter for gtx, main and helper with the set standard deviation\n",
"# simulation have RNG for\n",
"# - gtx, main and helper jitter\n",
"# - starting phase for main and helper\n",
"# - base_adpll error\n",
"\n",
"wrpll_sim = WRPLL_simulator(\n",
" time=t,\n",
" timestep=timestep,\n",
" total_steps=total_steps,\n",
" sim_mode=sim_mode,\n",
" helper_filter=helper_filter,\n",
" main_filter=main_filter,\n",
" gtx_freq=gtx_freq,\n",
" adpll_write_period=adpll_period,\n",
" start_up_delay=start_up_delay,\n",
" helper_init_freq=helper_init_freq,\n",
" # preset\n",
" gtx_jitter_SD=19e-12, # 0 = no jitter\n",
" dcxo_jitter_SD=9e-12,\n",
" dcxo_freq=125_000_000,\n",
" freq_acquisition_SD=100,\n",
" N=4096, # hardware used 4096\n",
" blind_period=300, # 300 is used to remove most glitches in simulation (for details see README 'Limitation')\n",
" cycle_slip_comp=True,\n",
")\n",
"wrpll_sim.run()\n",
"\n",
"wrpll_sim.run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# faster than pyplot with resampling feature\n",
"# see https://github.com/predict-idlab/plotly-resampler\n",
"\n",
"fig = FigureWidgetResampler(make_subplots(rows=4, shared_xaxes=True))\n",
"fig.add_trace(go.Scattergl(name='phase error'), hf_x=t, hf_y=wrpll_sim.phase_err, row=1, col=1)\n",
"fig.add_trace(go.Scattergl(name='phase error'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.phase_err, row=1, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='freq error (ppm)'), hf_x=t, hf_y=(\n",
"fig.add_trace(go.Scattergl(name='freq error (ppm)'), hf_x=wrpll_sim.time, hf_y=(\n",
" wrpll_sim.mainfreq-gtx_freq) * (1e6/gtx_freq), row=2, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='period error'), hf_x=t, hf_y=wrpll_sim.period_err, row=3, col=1)\n",
"fig.add_trace(go.Scattergl(name='period error'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.period_err, row=3, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='gtx'), hf_x=t, hf_y=wrpll_sim.gtx+1, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='main'), hf_x=t, hf_y=wrpll_sim.main, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=t, hf_y=wrpll_sim.helper-1, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='gtx'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.gtx+1, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='main'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.main, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.helper-1, row=4, col=1)\n",
"\n",
"\n",
"fig.update_layout(\n",

View File

@ -39,21 +39,27 @@
"from plotly.subplots import make_subplots\n",
"import plotly.graph_objects as go\n",
"import numpy as np\n",
"from wave_gen import square_arr\n",
"from wrpll import WRPLL_simulator\n",
"\n",
"from wrpll_simulation.wrpll import WRPLL_simulator"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# settings\n",
"timestep = 4e-10 # even number is recommended to avoid strange glitches\n",
"total_steps = 200_000_000\n",
"timestep = 1e-10\n",
"total_steps = 100_000_000\n",
"sim_mode = \"helper_pll\"\n",
"adpll_period = int(100e-6/timestep) # in simulation steps, 100μs is minimum, smaller = more frequency adjustment and filter calulation per unit time\n",
"start_up_delay = int(100e-6/timestep) # in simulation steps, the frequency adjustment is DISABLE until steps > start_up_delay\n",
"adpll_period = 200e-6 # in seconds, the period that pll will trigger, (minimum > total DCXO frequency change delay)\n",
"start_up_delay = 100e-6 # in seconds, the frequency adjustment is DISABLE until time > start_up_delay\n",
"\n",
"gtx_freq = 125_001_519\n",
"\n",
"helper_filter = {\n",
" \"KP\": 2,\n",
" \"KI\": 4,\n",
" \"KI\": 0.5,\n",
" \"KD\": 0,\n",
"}\n",
"\n",
@ -63,45 +69,39 @@
" \"KD\": 0,\n",
"}\n",
"\n",
"\n",
"t = np.linspace(0, timestep*total_steps, total_steps)\n",
"\n",
"# simulation will start with\n",
"# - random phase for main & helper\n",
"# - gussian based base_adpll error\n",
"# - gussian jitter for gtx, main and helper with the set standard deviation\n",
"# simulation have RNG for\n",
"# - gtx, main and helper jitter\n",
"# - starting phase for main and helper\n",
"# - base_adpll error\n",
"\n",
"wrpll_sim = WRPLL_simulator(\n",
" time=t,\n",
" timestep=timestep,\n",
" total_steps=total_steps,\n",
" sim_mode=sim_mode,\n",
" helper_filter=helper_filter,\n",
" main_filter=main_filter,\n",
" gtx_freq=gtx_freq,\n",
" adpll_write_period=adpll_period,\n",
" start_up_delay=start_up_delay,\n",
" # preset\n",
" gtx_jitter_SD=19e-12, # 0 = no jitter\n",
" dcxo_jitter_SD=9e-12,\n",
" dcxo_freq=125_000_000,\n",
" freq_acquisition_SD=500,\n",
" N=4096, # hardware used 4096\n",
" blind_period=300, # 300 is used to remove most glitches in simulation (for details see README 'Limitation')\n",
" cycle_slip_comp=True,\n",
")\n",
"\n",
"wrpll_sim.run()\n",
"\n",
"wrpll_sim.run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# faster than pyplot with resampling feature\n",
"# see https://github.com/predict-idlab/plotly-resampler\n",
"\n",
"fig = FigureWidgetResampler(make_subplots(rows=3, shared_xaxes=True))\n",
"fig.add_trace(go.Scattergl(name='period error'), hf_x=t, hf_y=wrpll_sim.period_err, row=1, col=1)\n",
"fig = FigureWidgetResampler(make_subplots(rows=2, shared_xaxes=True))\n",
"fig.add_trace(go.Scattergl(name='period error'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.period_err, row=1, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='gtx'), hf_x=t, hf_y=wrpll_sim.gtx+1, row=2, col=1)\n",
"fig.add_trace(go.Scattergl(name='main'), hf_x=t, hf_y=wrpll_sim.main, row=2, col=1)\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=t, hf_y=wrpll_sim.helper-1, row=2, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=t, hf_y=wrpll_sim.helper_adpll, row=3, col=1)\n",
"fig.add_trace(go.Scattergl(name='gtx'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.gtx+1, row=2, col=1)\n",
"fig.add_trace(go.Scattergl(name='main'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.main, row=2, col=1)\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.helper-1, row=2, col=1)\n",
"\n",
"\n",
"fig.update_layout(\n",
@ -109,7 +109,7 @@
"\n",
" yaxis1=dict(title=\"beating period error\"),\n",
" yaxis2=dict(title=\"Signal\"),\n",
" height=500,\n",
" height=1000,\n",
" showlegend=True,\n",
" title_text=\"PLL example\",\n",
" legend=dict(\n",

View File

@ -42,74 +42,79 @@
"from plotly.subplots import make_subplots\n",
"import plotly.graph_objects as go\n",
"import numpy as np\n",
"from wave_gen import square_arr\n",
"from wrpll import WRPLL_simulator\n",
"\n",
"from wrpll_simulation.wrpll import WRPLL_simulator"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# settings\n",
"timestep = 4e-10 # even number is recommended to avoid strange glitches\n",
"total_steps = 200_000_000\n",
"timestep = 1e-10\n",
"total_steps = 100_000_000\n",
"sim_mode = \"main_pll\"\n",
"adpll_period = int(100e-6/timestep) # in simulation steps, 100μs is minimum, smaller = more frequency adjustment and filter calulation per unit time\n",
"start_up_delay = int(100e-6/timestep) # in simulation steps, the frequency adjustment is DISABLE until steps > start_up_delay\n",
"adpll_period = 200e-6 # in seconds, the period that pll will trigger, (minimum > total DCXO frequency change delay)\n",
"start_up_delay = 100e-6 # in seconds, the frequency adjustment is DISABLE until time > start_up_delay\n",
"\n",
"gtx_freq = 125_001_519\n",
"\n",
"helper_init_freq = gtx_freq * (4096-1)/4096\n",
"\n",
"helper_filter = { # unused\n",
"helper_filter = { # unused\n",
" \"KP\": 2,\n",
" \"KI\": 4,\n",
" \"KI\": 0.5,\n",
" \"KD\": 0,\n",
"}\n",
"\n",
"main_filter = { \n",
"main_filter = {\n",
" \"KP\": 12,\n",
" \"KI\": 0,\n",
" \"KD\": 0,\n",
"}\n",
"\n",
"\n",
"t = np.linspace(0, timestep*total_steps, total_steps)\n",
"# simulation have RNG for\n",
"# - gtx, main and helper jitter\n",
"# - starting phase for main and helper\n",
"# - base_adpll error\n",
"\n",
"# simulation will start with\n",
"# - random phase for main & helper\n",
"# - gussian based base_adpll error\n",
"# - gussian jitter for gtx, main and helper with the set standard deviation\n",
"\n",
"wrpll_sim = WRPLL_simulator(\n",
" time=t,\n",
" timestep=timestep,\n",
" total_steps=total_steps,\n",
" sim_mode=sim_mode,\n",
" helper_filter=helper_filter,\n",
" main_filter=main_filter,\n",
" gtx_freq=gtx_freq,\n",
" adpll_write_period=adpll_period,\n",
" start_up_delay=start_up_delay,\n",
" helper_init_freq=helper_init_freq,\n",
" # preset\n",
" gtx_jitter_SD=19e-12, # 0 = no jitter\n",
" dcxo_jitter_SD=9e-12,\n",
" dcxo_freq=125_000_000,\n",
" freq_acquisition_SD=100,\n",
" N=4096, # hardware used 4096\n",
" blind_period=300, # 300 is used to remove most glitches in simulation (for details see README 'Limitation')\n",
" cycle_slip_comp=True,\n",
" helper_init_freq=helper_init_freq\n",
")\n",
"wrpll_sim.run()\n",
"\n",
"wrpll_sim.run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# faster than pyplot with resampling feature\n",
"# see https://github.com/predict-idlab/plotly-resampler\n",
"\n",
"fig = FigureWidgetResampler(make_subplots(rows=4, shared_xaxes=True))\n",
"fig.add_trace(go.Scattergl(name='phase error'), hf_x=t, hf_y=wrpll_sim.phase_err, row=1, col=1)\n",
"fig.add_trace(go.Scattergl(name='phase error'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.phase_err, row=1, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='freq error (ppm)'), hf_x=t, hf_y=(\n",
"fig.add_trace(go.Scattergl(name='freq error (ppm)'), hf_x=wrpll_sim.time, hf_y=(\n",
" wrpll_sim.mainfreq-gtx_freq) * (1e6/gtx_freq), row=2, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='period error'), hf_x=t, hf_y=wrpll_sim.period_err, row=3, col=1)\n",
"fig.add_trace(go.Scattergl(name='period error'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.period_err, row=3, col=1)\n",
"\n",
"fig.add_trace(go.Scattergl(name='gtx'), hf_x=t, hf_y=wrpll_sim.gtx+1, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='main'), hf_x=t, hf_y=wrpll_sim.main, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=t, hf_y=wrpll_sim.helper-1, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='gtx'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.gtx+1, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='main'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.main, row=4, col=1)\n",
"fig.add_trace(go.Scattergl(name='helper'), hf_x=wrpll_sim.time, hf_y=wrpll_sim.helper-1, row=4, col=1)\n",
"\n",
"\n",
"fig.update_layout(\n",

View File

@ -1,69 +0,0 @@
import numpy as np
from numba import jit
@jit(nopython=True)
def square(time, freq, jitter_SD, jitter, cycle_num):
"""
A scipy like square wave with jitter
Parameters
----------
time : timestamp, in seconds
freq : frequency in Hz
jitter_SD : standard deviation of jitter, in seconds
jitter : gaussian noise with `jitter_SD` as its standard deviation
cycle_num : time // period = cycle_num
----------
"""
period = 1/(freq)
quarter = (period / 4)
out = 0
# A T/4 shift is applied to the following to match scipy square fn
# ┌──────┐
# ───┘ └────
# T/4 3T/4
nth_cycle, t = np.divmod(time + period / 4, period)
if t >= (quarter + jitter) and t <= (3*quarter + jitter):
out = 1
else:
out = 0
# update jitter every cycle
if nth_cycle != cycle_num:
jitter = np.random.normal(0, jitter_SD)
cycle_num = nth_cycle
return out, cycle_num, jitter
@jit(nopython=True)
def square_arr(time, freq, jitter_SD):
"""
A scipy like square wave with jitter
Parameters
----------
time : numpy array
freq : frequency in Hz
jitter_SD : standard deviation of jitter, in seconds
----------
"""
wave = np.zeros(len(time))
jitter = np.random.normal(0, jitter_SD)
cycle_num = 0
for i, t in enumerate(time):
wave[i], cycle_num, jitter = square(t, freq, jitter_SD, jitter, cycle_num)
return wave

View File

View File

@ -1,12 +1,13 @@
import numpy as np
from numba import jit
from wave_gen import square
from numba import njit
from wrpll_simulation.wave_gen import square
@jit(nopython=True)
@njit
def simulation_jit(
time,
gtx,
gtx_freq,
gtx_jitter,
helper_pll,
main_pll,
h_KP,
@ -16,13 +17,15 @@ def simulation_jit(
m_KI,
m_KD,
dcxo_freq,
dcxo_jitter_SD,
h_jitter,
m_jitter,
base_adpll,
N,
adpll_write_period,
i2c_comm_delay,
dcxo_settling_delay,
blind_period,
start_up_delay,
cycle_slip_comp=True,
helper_init_freq=0
):
@ -30,6 +33,7 @@ def simulation_jit(
main = np.zeros(arr_len, dtype=np.int8)
helper = np.zeros(arr_len, dtype=np.int8)
gtx = np.zeros(arr_len, dtype=np.int8)
gtx_tag = 0
gtx_ready = 0
@ -40,7 +44,6 @@ def simulation_jit(
main_ready = 0
main_FF = 0
main_beating = np.zeros(arr_len, dtype=np.int8)
collector_r = 0
phase_err_arr = np.zeros(arr_len, dtype=np.int16)
period_err_arr = np.zeros(arr_len, dtype=np.int16)
@ -50,15 +53,20 @@ def simulation_jit(
helperfreq = np.zeros(arr_len, dtype=np.int32)
mainfreq = np.zeros(arr_len, dtype=np.int32)
# initial condition
main_init_offset = (np.random.uniform(0, 360)) / (360 * dcxo_freq)
helper_init_offset = (np.random.uniform(0, 360)) / (360 * dcxo_freq)
phase_collector_r = 0
colr_gtx_tag = colr_main_tag = 0
phase_collector_state = 0
# intermediate values
helper_jitter = np.random.normal(0, dcxo_jitter_SD)
main_jitter = np.random.normal(0, dcxo_jitter_SD)
helper_cycle_num = 0
main_cycle_num = 0
period_collector_r = 0
colr_last_gtx_tag = 0
beating_period = 0
period_collector_state = 0
# initial condition
timestep = time[1] - time[0]
gtx_phase = 0
h_phase = np.random.uniform(0, 360)
m_phase = np.random.uniform(0, 360)
if main_pll and not helper_pll:
helper_freq = helper_init_freq
@ -66,12 +74,10 @@ def simulation_jit(
helper_freq = dcxo_freq * (1 + base_adpll * 0.0001164 / 1_000_000) * ((N-1) / N)
main_freq = dcxo_freq * (1 + base_adpll * 0.0001164 / 1_000_000)
FSM_state = 0
coll_gtx_tag = coll_main_tag = 0
last_gtx_tag = last_gtx_FF = last_gtx_beat = 0
last_main_tag = last_main_FF = last_main_beat = 0
last_helper = last_main = 0
last_helper = 0
counter = 0
gtx_blind_counter = 0
@ -84,40 +90,36 @@ def simulation_jit(
wait_main = True
# firmware values
FW_gtx_tag = FW_last_gtx_tag = 0
FW_gtx_tag = 0
FW_main_tag = 0
period_err = last_period_err = 0
h_prop = h_integrator = h_derivative = 0
h_adpll = base_adpll
h_i2c_active = False
h_i2c_active_index = 0
period_colr_arm = h_i2c_active = False
phase_err = last_phase_err = 0
m_prop = m_integrator = m_derivative = 0
m_adpll = base_adpll
m_i2c_active = False
m_i2c_active_index = 0
phase_colr_arm = m_i2c_active = False
adpll_active = False
adpll_max = 8161512
def clip(n, minn, maxn): return max(min(maxn, n), minn)
for i, t in enumerate(time):
helper[i], helper_cycle_num, helper_jitter = square(
t + helper_init_offset, helper_freq, dcxo_jitter_SD, helper_jitter, helper_cycle_num)
main[i], main_cycle_num, main_jitter = square(
t + main_init_offset, main_freq, dcxo_jitter_SD, main_jitter, main_cycle_num)
h_phase += 360 * helper_freq * (timestep + h_jitter[i])
helper[i] = square(h_phase)
# continuous glitchless output, assume very small frequency change
if h_i2c_active and helper[i] == 1:
helper_freq = dcxo_freq * (1 + h_adpll * 0.0001164 / 1_000_000) * ((N-1) / N)
h_i2c_active = False
m_phase += 360 * main_freq * (timestep + m_jitter[i])
main[i] = square(m_phase)
if m_i2c_active and main[i] == 1:
main_freq = dcxo_freq * (1 + m_adpll * 0.0001164 / 1_000_000)
m_i2c_active = False
gtx_phase += 360 * gtx_freq * (timestep + gtx_jitter[i])
gtx[i] = square(gtx_phase)
if last_helper == 0 and helper[i] == 1:
if not last_helper and helper[i]:
gtx_FF, gtx_beating[i] = DDMTD(gtx[i], last_gtx_FF)
main_FF, main_beating[i] = DDMTD(main[i], last_main_FF)
@ -130,12 +132,15 @@ def simulation_jit(
main_blind_counter, main_blinded, blind_period,
last_main_beat, last_main_tag, counter)
collector_r, wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state = Collector_FSM(gtx_ready, main_ready, gtx_tag, main_tag,
wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state)
phase_collector_r, wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, phase_collector_state = phase_collector_FSM(gtx_ready, main_ready, gtx_tag, main_tag,
wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, phase_collector_state)
if collector_r == 1:
FW_gtx_tag = coll_gtx_tag
FW_main_tag = coll_main_tag
if phase_collector_r:
FW_gtx_tag = colr_gtx_tag
FW_main_tag = colr_main_tag
period_collector_r, colr_last_gtx_tag, beating_period, period_collector_state = period_collector_FSM(gtx_ready, gtx_tag,
colr_last_gtx_tag, period_collector_state)
counter += 1
@ -157,31 +162,34 @@ def simulation_jit(
if i > start_up_delay:
# Firmware filters
if adpll_active and collector_r == 1:
if cycle_slip_comp:
period = FW_gtx_tag - FW_last_gtx_tag
if period > 3 * N/2:
period = period - N
period_err = N - period
else:
period_err = (N - (FW_gtx_tag - FW_last_gtx_tag))
if i % adpll_write_period == 0:
period_colr_arm = phase_colr_arm = True
# Firmware filters
if period_colr_arm and period_collector_r:
period_colr_arm = False
period_err = N - beating_period
if helper_pll:
h_prop = period_err * h_KP
h_integrator += period_err * h_KI
h_derivative = (period_err - last_period_err) * h_KD
h_adpll = clip(int(base_adpll + h_prop + h_integrator + h_derivative), -adpll_max, adpll_max)
last_period_err = period_err
h_i2c_active_index = i
h_i2c_active = True
if phase_colr_arm and phase_collector_r:
phase_colr_arm = False
tag_diff = ((FW_main_tag - FW_gtx_tag) % N)
if tag_diff > N/2:
phase_err = tag_diff - N
else:
phase_err = tag_diff
if helper_pll:
h_prop = period_err * h_KP
h_integrator += period_err * h_KI
h_derivative = (period_err - last_period_err) * h_KD
# h_derivative = 0
h_adpll = clip(int(base_adpll + h_prop + h_integrator + h_derivative), -adpll_max, adpll_max)
last_period_err = period_err
h_i2c_active = True
if main_pll:
m_prop = phase_err * m_KP
@ -190,16 +198,21 @@ def simulation_jit(
m_adpll = clip(int(base_adpll + m_prop + m_integrator + m_derivative), -adpll_max, adpll_max)
last_phase_err = phase_err
m_i2c_active_index = i
m_i2c_active = True
adpll_active = False
# i2c communication delay
if i % adpll_write_period == 0:
adpll_active = True
FW_last_gtx_tag = FW_gtx_tag
if h_i2c_active and i >= i2c_comm_delay + dcxo_settling_delay + h_i2c_active_index:
helper_freq = dcxo_freq * (1 + h_adpll * 0.0001164 / 1_000_000) * ((N-1) / N)
h_i2c_active = False
if m_i2c_active and i >= i2c_comm_delay + dcxo_settling_delay + m_i2c_active_index:
main_freq = dcxo_freq * (1 + m_adpll * 0.0001164 / 1_000_000)
m_i2c_active = False
last_helper = helper[i]
last_main = main[i]
# Data
period_err_arr[i] = period_err
@ -209,18 +222,18 @@ def simulation_jit(
helperfreq[i] = helper_freq
mainfreq[i] = main_freq
return period_err_arr, phase_err_arr, helper_adpll_arr, main_adpll_arr, gtx_beating, main_beating, helper, main, helperfreq, mainfreq
return period_err_arr, phase_err_arr, helper_adpll_arr, main_adpll_arr, gtx_beating, main_beating, gtx, helper, main, helperfreq, mainfreq
@jit(nopython=True)
@njit
def DDMTD(sig_in, last_FF):
return sig_in, last_FF
@jit(nopython=True)
@njit
def Deglitcher(beating, t_out, t_ready, blind_counter, blinded, blind_period, last_beat, last_tag, counter):
if blind_counter == 0 and beating == 1 and last_beat == 0: # rising
if blind_counter == 0 and beating and not last_beat: # rising
t_out = counter
t_ready = 1
blinded = True
@ -228,7 +241,7 @@ def Deglitcher(beating, t_out, t_ready, blind_counter, blinded, blind_period, la
t_out = last_tag
t_ready = 0
if beating == 1:
if beating:
blind_counter = blind_period - 1
if blind_counter != 0:
@ -237,38 +250,56 @@ def Deglitcher(beating, t_out, t_ready, blind_counter, blinded, blind_period, la
return t_out, t_ready, blind_counter, blinded
@jit(nopython=True)
def Collector_FSM(g_tag_r, m_tag_r, gtx_tag, main_tag, wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state):
@njit
def phase_collector_FSM(g_tag_r, m_tag_r, gtx_tag, main_tag, wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, FSM_state):
collector_r = 0
match FSM_state:
case 0: # IDEL
if g_tag_r == 1 and m_tag_r == 1:
coll_gtx_tag = gtx_tag
coll_main_tag = main_tag
if g_tag_r and m_tag_r:
colr_gtx_tag = gtx_tag
colr_main_tag = main_tag
FSM_state = 3 # OUTPUT
elif g_tag_r == 1:
coll_gtx_tag = gtx_tag
elif g_tag_r:
colr_gtx_tag = gtx_tag
wait_main = True
FSM_state = 2 # WAITMAIN
elif m_tag_r == 1:
coll_main_tag = main_tag
elif m_tag_r:
colr_main_tag = main_tag
wait_gtx = True
FSM_state = 1 # WAITGTX
case 1: # WAITGTX
if g_tag_r == 1:
coll_gtx_tag = gtx_tag
if g_tag_r:
colr_gtx_tag = gtx_tag
FSM_state = 3 # OUTPUT
case 2: # WAITMAIN
if m_tag_r == 1:
coll_main_tag = main_tag
if m_tag_r:
colr_main_tag = main_tag
FSM_state = 3 # OUTPUT
case 3: # OUTPUT
wait_gtx = wait_main = False
collector_r = 1
FSM_state = 0
return collector_r, wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state
return collector_r, wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, FSM_state
@njit
def period_collector_FSM(g_tag_r, gtx_tag, colr_last_gtx_tag, FSM_state):
collector_r = 0
beating_period = 0
match FSM_state:
case 0: # IDEL
if g_tag_r:
colr_last_gtx_tag = gtx_tag
FSM_state = 1
case 1: # OUTPUT
if g_tag_r:
beating_period = gtx_tag - colr_last_gtx_tag
collector_r = 1
FSM_state = 0 # IDEL
return collector_r, colr_last_gtx_tag, beating_period, FSM_state

View File

@ -0,0 +1,29 @@
import numpy as np
import numba as nb
from numba import njit
def gussian_jitter(RMS_jitter, size, seed=None):
return np.random.default_rng(seed).normal(0, RMS_jitter/2, size)
@njit(fastmath=True)
def square_with_jitter(time, freq, jitter):
n = len(time)
wave = np.empty(n)
timestep = time[1] - time[0]
phase = 0.
for i in range(n):
phase += 360 * freq * (timestep + jitter[i])
wave[i] = square(phase)
return wave
@njit
def square(x):
if np.mod(x, 360) < 180:
return 0
else:
return 1

View File

@ -1,31 +1,33 @@
import numpy as np
from numba import jit
from wave_gen import square_arr
from sim import simulation_jit
from wrpll_simulation.sim import simulation_jit
from wrpll_simulation.wave_gen import gussian_jitter
class WRPLL_simulator():
def __init__(
self,
time,
timestep,
total_steps,
sim_mode,
helper_filter,
main_filter,
gtx_freq,
gtx_jitter_SD,
dcxo_freq,
dcxo_jitter_SD,
freq_acquisition_SD,
N,
adpll_write_period,
blind_period,
start_up_delay,
cycle_slip_comp,
helper_init_freq=None
i2c_comm_delay=85.6e-6,
dcxo_settling_delay=100e-6,
gtx_jitter=200e-15,
dcxo_freq=125_000_000,
dcxo_jitter=95e-15,
freq_acquisition_error=100,
N=4069,
blind_period=128,
helper_init_freq=None,
seed=None
):
self.time = time
self.time = np.linspace(0, timestep*total_steps, total_steps)
self.sim_mode = sim_mode
self.h_KP = helper_filter["KP"]
self.h_KI = helper_filter["KI"]
@ -35,22 +37,26 @@ class WRPLL_simulator():
self.m_KD = main_filter["KD"]
# init condition
self.gtx_freq = gtx_freq
self.gtx_jitter = gussian_jitter(gtx_jitter, len(self.time), seed)
self.dcxo_freq = dcxo_freq
self.dcxo_jitter_SD = dcxo_jitter_SD
self.h_jitter = gussian_jitter(dcxo_jitter, len(self.time), seed)
self.m_jitter = gussian_jitter(dcxo_jitter, len(self.time), seed)
self.N = N
self.helper_init_freq = helper_init_freq
self.gtx = square_arr(time, gtx_freq, gtx_jitter_SD)
# freq_acquisition() error
freq_diff = gtx_freq - dcxo_freq + np.random.normal(0, freq_acquisition_SD)
freq_diff = gtx_freq - dcxo_freq + \
np.random.default_rng(seed).uniform(-freq_acquisition_error, freq_acquisition_error)
self.base_adpll = int(freq_diff * (1 / dcxo_freq) * (1e6 / 0.0001164))
# sim config
self.adpll_write_period = adpll_write_period
self.i2c_comm_delay = int(i2c_comm_delay/timestep)
self.dcxo_settling_delay = int(dcxo_settling_delay/timestep)
self.blind_period = blind_period
self.start_up_delay = start_up_delay
self.cycle_slip_comp = cycle_slip_comp
self.adpll_write_period = int(adpll_write_period/timestep)
self.start_up_delay = int(start_up_delay/timestep)
if type(self.sim_mode) is not str:
raise ValueError(f"pll_type {type(self.sim_mode)} is not a string")
@ -69,9 +75,10 @@ class WRPLL_simulator():
def run(self):
print("running simulation...")
self.period_err, self.phase_err, self.helper_adpll, self.main_adpll, self.gtx_beating, self.main_beating, self.helper, self.main, self.helperfreq, self.mainfreq = simulation_jit(
self.period_err, self.phase_err, self.helper_adpll, self.main_adpll, self.gtx_beating, self.main_beating, self.gtx, self.helper, self.main, self.helperfreq, self.mainfreq = simulation_jit(
self.time,
self.gtx,
self.gtx_freq,
self.gtx_jitter,
self.helper_pll,
self.main_pll,
self.h_KP,
@ -81,13 +88,15 @@ class WRPLL_simulator():
self.m_KI,
self.m_KD,
self.dcxo_freq,
self.dcxo_jitter_SD,
self.h_jitter,
self.m_jitter,
self.base_adpll,
self.N,
self.adpll_write_period,
self.i2c_comm_delay,
self.dcxo_settling_delay,
self.blind_period,
self.start_up_delay,
self.cycle_slip_comp,
self.helper_init_freq
)
print("Done!")