Theory ref — Yu & Horng §0, §3 (Fig. 2):
This whole pipeline is the all-digital realization of Yu's phase-canceling feedback
demodulation loop. The PI loop pins the round-trip phase at the anti-phase point
θ=π; the delay value itself is the radar output,
x̂(t) = −½·c·[d(t) − a].
[Yu guide]
The _1Mhz_beta010 design implements a 40 kHz IQ radar signal processor
on a Cyclone V FPGA. A relay feedback oscillator generates a stable 40 kHz reference waveform.
A PI phase-lock loop steers the oscillator phase via variable-delay injection.
Quadrature transmit waveforms drive two DAC channels (90° apart). On receive, dual ADC inputs
are processed through image rejection and clutter cancellation before peak detection and UART reporting.
Theory ref — Yu & Horng §1 (SIL), §3:relay_feedback2 is the SIL oscillator running at the 40 kHz IF (in Wu's
design the SIL/Adler locking is digital; 5.8 GHz is only the over-the-air carrier). uinj
is the injection-locking input — generalized Adler
Δω = (ω/2Q)·η·sinθ/(1+η·cosθ), with η=0.5, Q=25. The relay /
comparator element is exactly what Yu notes "makes an all-digital FPGA implementation possible."
[Yu guide]
relay_feedback2 is the core 40 kHz oscillator of the radar IQ signal
processing system. It implements a relay feedback oscillator — a nonlinear control loop
where a relay (sign function) element forces sustained sinusoidal-like oscillation at the natural
resonant frequency of the embedded digital filter.
relay_feedback2 是整個雷達 IQ 訊號處理系統的核心 40 kHz 振盪器。它實作一個繼電回授振盪器——一種非線性控制迴路:由繼電器(符號函數)元件強迫系統在內嵌數位濾波器的自然諧振頻率上維持類似弦波的持續振盪。
The oscillation frequency is determined by the integrator gain coefficient
332/1024 ≈ 0.3242, chosen so that at a 10 MHz clock rate the closed-loop
natural frequency lands at 40 kHz.
Note:
The comment on line 45 explicitly records the change from the original 306/1024 coefficient
(prior frequency) to 332/1024 to achieve 40 kHz resonance.
Injection signal from PI controller. Adds phase correction to the oscillator. Sign-extended to 18 bits internally as y_in. | 來自 PI 控制器的注入訊號。為振盪器加入相位修正。內部符號延伸成 18 位元的 y_in。
u
output
13 (signed)
Main oscillation output — f1024[23:11] (divided by 2). Feeds the demodulator. | 主振盪輸出——f1024[23:11](除以 2)。餵入解調器。
u_0d5
output
13 (signed)
Half-period delayed version of u — average of current and previous sample. Used for the 12-step delay chain in top-level demodulation. | u 的半週期延遲版——目前與前一取樣的平均。用於頂層解調的 12 級延遲鏈。
v
output
14 (signed)
Full-resolution oscillation — f1024[23:10]. Becomes the Tx baseband Q signal sent to the DAC. | 全解析度振盪——f1024[23:10]。成為送往 DAC 的 Tx 基頻 Q 訊號。
Internal State Registers | 內部狀態暫存器
Register | 暫存器
Width | 位寬
Description | 說明
f1024
24-bit signed
Bandpass filter state F(z). Updated on negedge clk10MHz. The oscillation waveform lives in bits [23:10]. | 帶通濾波器狀態 F(z)。在 negedge clk10MHz 更新。振盪波形位於位元 [23:10]。
g1024
28-bit signed
Integrator state G(z). Updated on posedge clk10MHz. Initial value 4'b0001,24'b0 to seed oscillation from rest. | 積分器狀態 G(z)。在 posedge clk10MHz 更新。初始值 4'b0001,24'b0,作為從靜止起振的種子。
e_1, e_2
18-bit signed each
One- and two-cycle delayed error signals for the F(z) recursion. | 供 F(z) 遞迴使用的延遲一與兩個週期的誤差訊號。
u_past
13-bit signed
Previous-cycle value of u, used to compute the half-period delayed output u_0d5. | u 的前一週期值,用來計算半週期延遲輸出 u_0d5。
res_in
18-bit signed
Relay output (sign of f1024) plus injection y_in. The nonlinear element input to F(z). | 繼電器輸出(f1024 的符號)加上注入 y_in。進入 F(z) 的非線性元件輸入。
Architecture & Signal Flow | 架構與信號流
Block Diagram | 方塊圖
Cross-Reference — F(z) ↔ Yu's SIL Oscillator | 交叉對照——F(z) ↔ Yu 的 SIL 振盪器
Theory ref — Yu & Horng §1 (SIL) & §3 (Fig. 2):
The F(z) bandpass filter — together with the G(z) integrator and the relay
(sign(f1024)) — is Wu's all-digital realization of the SIL oscillator
that Yu & Horng describe. In Yu's paper the oscillator is itself a relay-feedback
(comparator → square wave) SIL oscillator whose frequency is pulled by the reflected echo
(self-injection locking, generalized Adler Δω = (ω/2Q)·η·sinθ/(1+η·cosθ)) — so this
design uses the same relay approach, synthesized in the FPGA. The resonator's selectivity
(quality factor) is realized here by the 1/1024 leak (pole at 1023/1024) —
the digital stand-in for the oscillator's finite quality factor.
[Yu §1][Yu §3, Fig. 2]
Yu & Horng's paper figure — FPGA implementation of the SIL oscillator | Yu & Horng 論文圖——SIL 振盪器的 FPGA 實作
The paper's canonical block diagram (redrawn). It is the same topology as relay_feedback2: G₁ = the F(z) bandpass, G₂ = the integrator, sign(·)→2⁸ = the relay (±256), and 2⁷·u_inj = the injection.
Paper vs this RTL:
same topology as relay_feedback2 — G₁ = F(z), G₂ = integrator,
sign(·)→2⁸ = relay (±256), 2⁷·u_inj = injection. The constants differ slightly:
the pole is drawn as 2045/2¹¹ here vs 1023/1024 (= 2046/2¹¹) in this design,
and the injection scale is 2⁷ vs ×2⁶ in the RTL — but the structure is identical.
bandpass, numerator (1+z⁻¹)², leaky pole near z = 1
Integrator — G(z) / G₂ | 積分器——G(z)/G₂
1/(1−z⁻¹), gain 166
Relay element | 繼電器元件
sign(state) × 2⁸ = ±256
Summing node | 加總節點
e = relay + injection − scaled integrator → into F(z)/G₁
Output u | 輸出 u
resonator state scaled down (≈ ÷2¹⁰)
Clock | 時脈
10 MHz
What differs (constants & detail):
相異之處(常數與細節):
Aspect | 面向
This RTL (§2 diagram) | 本 RTL(§2 圖)
Yu paper figure | Yu 論文圖
Leak / pole | 洩漏/極點
1023/1024 (= 2046/2¹¹)
2045/2¹¹
Injection scale | 注入縮放
×2⁶ (×64), uinj 3-bit
2⁷ (×128)
u word length | u 字長
f1024[23:11] = 13-bit
14-bit (1/2¹⁰)
Numerator written as | 分子寫法
(z⁻⁰·⁵+z⁻¹·⁵) half-step form
(1+2z⁻¹+z⁻²) expanded
Integrator-feedback scaling | 積分器回授縮放
g1024[27:10] subtracted (−1/2¹⁰ implied)
explicit −1/2¹⁰ block
Clock detail | 時脈細節
F(z) negedge / G(z) posedge interleave (→ z⁻⁰·⁵)
single 10 MHz shown
Extra outputs shown | 額外輸出
v (DAC, 14b), u_0d5 (delay chain)
only u
Bottom line: identical structure (relay + injection + integrator feedback around a (1+z⁻¹)² bandpass);
the differences are word-lengths and exact constants (notably the pole 2046 vs 2045 over 2¹¹,
and the injection scale 2⁶ vs 2⁷), plus this RTL exposes extra taps (v, u_0d5) and the negedge/posedge
half-sample trick.
一句話總結:結構完全相同(繼電器+注入+積分器回授,包在 (1+z⁻¹)² 帶通外面);差異只在位寬與精確常數(特別是極點 2046 vs 2045(除以 2¹¹),以及注入縮放 2⁶ vs 2⁷),另外本 RTL 多引出了幾個抽頭(v、u_0d5),並使用 negedge/posedge 的半取樣技巧。
Adler Equation — Model, Not RTL | Adler 方程——是模型,不是 RTL
A common confusion: Wu's resonator does not implement or compute the Adler equation.
The Adler equation is an analytical model of injection-locking behavior, not a building block.
relay_feedback2 is a plain relay-feedback digital oscillator:
a linear F(z) bandpass + G(z) integrator (sets 40 kHz & Q), a
relay (sign(f1024)) that sustains the limit cycle, and an
injection (uinj → y_in) that is simply added to the relay output.
There is no sin, no cos, no Adler formula in the Verilog —
only adds, bit-shifts, and the sign function.
The Adler equation Δω = (ω/2Q)·η·sinθ/(1+η·cosθ)describes how an injection-locked
oscillator's frequency shifts with injection phase. In Wu's all-digital design, injection locking is
realized physically by adding y_in into the relay loop; the
frequency-pulling that emerges obeys the generalized Adler relation, but the hardware never
evaluates it. Adler is used by Yu (and this guide's theory refs) only to analyze
the loop — e.g. to derive the small-signal plant gain g(θ) ∝ cosθ near θ = π.
linear digital oscillator setting 40 kHz & Q | 設定 40 kHz 與 Q 的線性數位振盪器
Injection locking | 注入鎖定
yes — uinj added to relay (→ y_in)
physically realizes the locking | 物理性地實現鎖定
Adler equation | Adler 方程
no — no sin/cos in RTL
analytical model that describes the locking; Yu uses it for the plant model | 描述鎖定行為的解析模型;Yu 用它建立受控體模型
In one line: Wu didn't use the Adler equation to build the resonator — the
relay-feedback loop produces injection-locking behavior that the Adler equation merely describes.
Adler is theory/analysis (Yu's plant model), not RTL.
Both have an RTL — Wu's relay_feedback2 and Yu's "FPGA implementation of the SIL oscillator"
figure — and both are relay-feedback loops (F(z)/G(z) + sign()
relay + added injection) with no sin/cos/Adler in the gates. Adler lives in the
control-design math (both use it there); the RTL embodies the injection-locking behavior without ever
computing it.
relay-feedback square wave (comparator → square); u_out, u_inj square, base B = 4/π·A_inj
same relay-feedback square wave, realized digitally (relay_feedback2) at the 40 kHz IF — the 4/π factor cancels in B/A
Note — relay feedback is shared, not a difference:
Both Yu's ultrasonic radar and this design use a relay-feedback (comparator)
oscillator producing a square wave — Yu notes the relay element is exactly
what "makes an all-digital FPGA implementation possible," which is what relay_feedback2
does. The sinusoidal carrier appears only in Wu's 5.8 GHz EM thesis as the analog RF
carrier; in this FPGA design the 5.8 GHz is just an external up-conversion of the 40 kHz IF,
while the SIL/Adler locking itself is the digital square-wave relay loop.
[Yu §4 — 方波 vs 弦波]
⚠ Don't confuse the two "F"s:
Yu's guide also uses the symbol F(s), but for a different block — the
demodulation low-pass filter in the small-signal plant
P(s) = g(θ)·e−sT/8·F(s) (Yu §4, eq. 6 / A32). That F(s) belongs to
the receive/demod chain (≈ this design's demodulator + PI LPF), not
the oscillator. The F(z) on this page is the oscillator resonator and maps to Yu
§1/§3, not Yu's §4 F(s).
[Yu §4]
Z-Domain Primer — How to Read These Formulas | z 域入門——這些公式怎麼讀(click to expand)
Every filter in this design is written as a z-domain transfer function
(F(z), G(z), …). If that notation is unfamiliar, here is the minimum needed
to read — and derive — them.
本設計的每個濾波器都寫成 z 域轉移函數(F(z)、G(z)…)。若不熟悉這種記法,以下是讀懂——並自行推導——它們所需的最少知識。
1. The one rule that matters: z⁻¹ = one-sample delay | 1. 唯一要緊的規則:z⁻¹=延遲一個取樣
z⁻¹ means "delay by one clock sample." In this design one sample = one
clk10MHz tick = T = 100 ns. The dictionary:
delayed k samples (k registers) | 延遲 k 個取樣(k 個暫存器)
a·x[n] + b·y[n]
a·X(z) + b·Y(z)
linear: scale & add carry over | 線性:縮放與相加照樣成立
So a chain of D flip-flops is multiplication by z⁻¹ per stage — e.g. the e_1/e_2 registers are z⁻¹ and z⁻² taps of e.
所以一串 D 正反器就是每級乘上一個 z⁻¹——例如 e_1/e_2 暫存器就是 e 的 z⁻¹ 與 z⁻² 抽頭。
2. Difference equation ⇄ transfer function | 2. 差分方程 ⇄ 轉移函數
To turn RTL/recursion into a transfer function: replace each x[n−k] with z⁻ᵏ·X, then collect and divide. Worked on a simple leaky integrator y[n] = y[n−1] + x[n]:
y[n] = y[n-1] + x[n]
Y = z⁻¹·Y + X (substitute: y[n-1] → z⁻¹·Y)
Y − z⁻¹·Y = X (move the Y term to the left)
Y·(1 − z⁻¹) = X (factor out Y)
H(z) = Y/X = 1 / (1 − z⁻¹) (divide both sides by 1 − z⁻¹)
The middle two lines are ordinary algebra — treat z⁻¹ as a constant multiplier (like
a in y = a·y + x → y(1 − a) = x). Subtract z⁻¹·Y from both sides,
then factor Y out of Y − z⁻¹·Y.
中間兩行只是普通代數——把 z⁻¹ 當成常數乘子(就像 y = a·y + x → y(1 − a) = x 中的 a)。兩邊同減 z⁻¹·Y,再從 Y − z⁻¹·Y 提出 Y。
That 1/(1−z⁻¹) is the pure accumulator/integrator — exactly the shape of G(z) here.
那個 1/(1−z⁻¹) 就是純累加器/積分器——正是這裡 G(z) 的形狀。
Why 1/(1−z⁻¹) accumulates — and how it differs from 1/z | 為什麼 1/(1−z⁻¹) 會累加——它與 1/z 有何不同
It is the transfer function of y[n] = y[n−1] + x[n]: each output adds the input onto the running total, so it sums all past inputs. Three ways to see it:
Series expansion:1/(1−z⁻¹) = 1 + z⁻¹ + z⁻² + z⁻³ + …, so the impulse response is all 1's (a unit step) — feed in one sample and the output holds it forever.
Pole:1 − z⁻¹ = 0 ⇒ z = 1 (DC), the digital twin of the analog integrator 1/s (pole at s = 0).
極點:1 − z⁻¹ = 0 ⇒ z = 1(直流),是類比積分器 1/s(極點在 s = 0)的數位孿生。
1/z is a completely different thing — it is just z⁻¹, a pure
one-sample delay (y[n] = x[n−1]): it shifts the signal one sample later and passes
it through unchanged. It sums nothing.
The crux: z⁻¹alone = a delay; z⁻¹in the denominator = feedback,
and that feedback is what turns a delay into accumulation. Mirror image: the numerator
(1 − z⁻¹) by itself is a differencery[n] = x[n] − x[n−1]
(discrete derivative, zero at z = 1) — the inverse of the accumulator.
Impulse response of each (feed in a single 1 at n=0, watch the output):
兩者的脈衝響應(在 n=0 餵入單一個 1,觀察輸出):
Same input (a single 1 at n=0): the delay just relocates it to
n=1; the accumulator adds it to a running total that never decreases. A
leaky integrator (like F(z)'s 1/1024 pole) would instead decay slowly back toward
0 rather than holding flat at 1.
The closer a pole sits to the unit circle, the less it decays per sample → higher Q, narrower bandwidth. That is exactly the role of the 1/1024 leak (pole at 1023/1024).
Pole → analog corner: a real pole at z = 1 − ε (ε small) maps to
s ≈ −ε/T, i.e. corner frequency ωc ≈ ε/T — how ωc = 1/(1024T)
falls out of the ε = 1/1024 leak.
Worked example — this design's F(z):
From the recursion f[n] = f[n-1] − f[n-1]/1024 + e[n] + 2e[n-1] + e[n-2], substitute
z⁻ᵏ and collect: F·(1 − (1023/1024)z⁻¹) = E·(1 + z⁻¹)², so
F(z) = (1+z⁻¹)² / (1 − (1023/1024)z⁻¹). Read it off: a pole at 1023/1024
(just inside the unit circle ⇒ high-Q resonator, corner ωc = 1/(1024T)) and a double
zero at z = −1 (the (1+z⁻¹)² numerator, a gentle low-pass smoothing).
Read straight off the diagram: the three blue feed-forward taps 1, 2, 1 are the
numerator (1+z⁻¹)² (double zero at z = −1); the single purple feedback gain
1023/1024 through one z⁻¹ is the denominator pole at 1023/1024 (the
1/1024 leak). Both z⁻¹ blocks are one-clock delays (registers).
6. The integrator: 1/s and its z-domain forms | 6. 積分器:1/s 與它的 z 域形式
The integrator is the single most important building block in this loop — it is both G(z)
(the resonator's frequency-setting state) and the integral term of the PI_antiWindup
controller. In continuous time, integration is just division by s:
There is no single z-domain integrator — the continuous 1/s maps to
different discrete forms depending on how you approximate the area under the curve between samples.
All three substitute for s below (T = sample period = 100 ns here):
z 域積分器並非只有一種——連續的 1/s 依「取樣間的曲線下面積如何近似」而映射到不同的離散形式。下表三種方法都是對 s 的代換(此處 T=取樣週期=100 ns):
Method | 方法
s →
Integrator H(z) | 積分器 H(z)
Difference equation | 差分方程
Forward Euler (rectangle, left edge) | 前向歐拉(矩形取左緣)
(z−1)/T
T·z⁻¹ / (1 − z⁻¹)
y[n] = y[n−1] + T·x[n−1]
Backward Euler (rectangle, right edge) | 後向歐拉(矩形取右緣)
(z−1)/(Tz)
T / (1 − z⁻¹)
y[n] = y[n−1] + T·x[n]
Tustin / bilinear (trapezoid) | Tustin/雙線性(梯形)
(2/T)·(z−1)/(z+1)
(T/2)·(1+z⁻¹) / (1 − z⁻¹)
y[n] = y[n−1] + (T/2)(x[n]+x[n−1])
All three share the pole at z = 1 — the 1/(1−z⁻¹) accumulator from
section 2, the digital twin of the analog integrator's pole at s = 0 (infinite DC gain).
They differ only in the numerator (when/how the input sample is weighted).
三者都共享 z = 1 的極點——第 2 節的 1/(1−z⁻¹) 累加器,類比積分器 s = 0 極點(無限直流增益)的數位孿生。差別只在分子(輸入取樣在何時、如何加權)。
Backward Euler is the workhorse for digital PI/PID: always stable, causal (needs no
future sample), and is exactly the running sum y[n] = y[n−1] + T·x[n].
Tustin is the most accurate — it maps the entire left-half s-plane to inside the unit
circle, so a stable analog filter is always a stable digital one — at the cost of an extra zero at
z = −1.
Tustin(雙線性)最精確——它把整個左半 s 平面映射到單位圓內,所以穩定的類比濾波器永遠對應穩定的數位濾波器——代價是在 z = −1 多一個零點。
This design uses Backward Euler:
Both integrators here are the pure 1/(1 − z⁻¹) form (pole exactly at z = 1, no leak).
G(z) = 0.5 × (332/1024) / (1 − z⁻¹) is a backward-Euler accumulator whose gain
332/1024 ≈ 0.3242 folds the sample period T and the integrator gain into a
single coefficient — that coefficient is what sets the 40 kHz resonant frequency (see
Integrator Gain — Frequency Tuning). The
PI_antiWindup integral term is the same shape: acc ← acc + Kᵢ·error, with
anti-windup clamping the accumulator so the z = 1 pole cannot run away.
e[n] = res_in[n] - g1024[27:10][n] // [27:10] = top 18 bits of the 28-bit g1024 ≈ ÷1024 (arith. >>10); [n] = value at sample n
What is e?e is the error signal that drives the F(z) bandpass filter — the input
to the resonator recursion. It is the difference between the loop drive
res_in (the relay output sign(f1024) plus the phase injection
y_in) and the scaled integrator state g1024[27:10] (G(z)). Subtracting the
integrator feedback from the drive is the classic second-order-resonator structure; the resulting
e is pushed through F(z) via three taps (e[n],
e[n-1], e[n-2], stored in registers e_1/e_2),
closing the loop that sustains the 40 kHz oscillation. This is the g1024 → e → F(z)
path shown in the block diagram above.
e 是什麼?e 是驅動 F(z) 帶通濾波器的誤差訊號——諧振器遞迴式的輸入。它是迴路驅動 res_in(繼電器輸出 sign(f1024) 加上相位注入 y_in)與縮放後的積分器狀態 g1024[27:10](G(z))之間的差。把積分器回授從驅動中減去,正是經典的二階諧振器結構;得到的 e 經由三個抽頭(e[n]、e[n-1]、e[n-2],存放在暫存器 e_1/e_2)推入 F(z),閉合維持 40 kHz 振盪的迴路。這就是上方方塊圖中的 g1024 → e → F(z) 路徑。
Where res_in comes from:res_in is registered on posedge clk10MHz as the sum of two terms:
a relay (comparator) output plus the phase injectiony_in.
How the relay term { {9{f1024[23]}}, 1'b1, 8'b0 } evaluates to ±256:
繼電器項 { {9{f1024[23]}}, 1'b1, 8'b0 } 如何得出 ±256:
f1024 sign | f1024 符號
f1024[23]
bits [17:9] | 位元 [17:9]
full 18-bit word | 完整 18 位元字組
value | 數值
f1024 ≥ 0
0
000000000
0_0000_0000_1_0000_0000
+256
f1024 < 0
1
111111111
1_1111_1111_1_0000_0000
−256 (two's-complement)
Term | 項
Source | 來源
Meaning | 意義
±256·sign(f1024)
{ {9{f1024[23]}}, 1'b1, 8'b0 }
The relay element: takes the sign bit of the F(z) bandpass state
f1024[23] and emits a fixed magnitude — +256 when
f1024 ≥ 0, −256 when f1024 < 0 (18-bit signed).
This square-wave nonlinearity is what sustains the limit-cycle oscillation. | 繼電器元件:取 F(z) 帶通狀態的符號位元 f1024[23],輸出固定大小——f1024 ≥ 0 時 +256、f1024 < 0 時 −256(18 位元有號)。這個方波非線性正是維持極限環振盪的機制。
y_in
{{10{uinj[2]}}, uinj[1:0], 6'b0}
The injection from PI_antiWindup: the 3-bit uinj
sign-extended to 18 bits and left-shifted 6 (×64). This steers the oscillator phase and
closes the phase-lock loop. | 來自 PI_antiWindup 的注入:3 位元 uinj 符號延伸到 18 位元並左移 6(×64)。它調控振盪器相位、閉合相位鎖定迴路。
How y_in is built | y_in 如何構成 — {{10{uinj[2]}}, uinj[1:0], 6'b0}
Same concat/replication idiom as the relay term (uinj is signed [2:0],
y_in is signed [17:0]):
與繼電器項相同的串接/複製慣用寫法(uinj 是 signed [2:0],y_in 是 signed [17:0]):
uinj[2] — the sign bit of uinj.
uinj[2]——uinj 的符號位元。
{10{uinj[2]}} — 10 copies of the sign bit → sign-extension (10 bits).
{10{uinj[2]}}——符號位元複製 10 份 → 符號延伸(10 位元)。
uinj[1:0] — low 2 bits (2 bits).
uinj[1:0]——低 2 位元(2 位元)。
6'b0 — 6 zero bits (left-shift padding).
6'b0——6 個零位元(左移填充)。
Width: 10 + 2 + 6 = 18 bits ✓. Field layout:
位寬:10 + 2 + 6 = 18 位元 ✓。欄位配置如下:
[17:8] sign×10 | [7:6] uinj[1:0] | [5:0] 000000
The full 3-bit uinj lands at bits [8:6] (sign-extended above,
zero-padded below), so y_in = sign_extend(uinj) << 6 = uinj × 64 (×2⁶). Shown here
for all five symmetric levels delay_line2 emits (see
Injection Levels Are Symmetric for where they come from):
Injection Levels Are Symmetric — 5 Levels, Not 4 | 注入位準是對稱的——五階而非四階
The injection is symmetric with FIVE levels: +2, +1, 0, −1, −2 — easy to mistake for four if the
+2 case is overlooked. All five are present and active in the hardware: the +2
comes from delay_line2.v:34 (3'b000: out=3'b010;) and is in fact one of the
two dominant output levels.
From delay_line2.v (lines 27–34): the injection is a smoothed 1-bit signal — it sums
four consecutive delayed samples (out_d0..out_d3, each 0 or 1), so
sum ∈ {0,1,2,3,4}, then maps:
So the complete set is symmetric: {+2, +1, 0, −1, −2} → y_in {+128, +64, 0, −64, −128}.
It is also inverting: an all-low input (sum=0) gives +2, an all-high input
(sum=4) gives −2.
Note the feedback loop: res_in depends on f1024, then
e = res_in − g1024[27:10] drives f1024. The relay acting on the filter's
own sign is what makes it oscillate. (The §2 port table's "±1 scaled" relay output is this term;
the real scale is ±2⁸ = ±256.)
This is the F(z) half of relay_feedback2, running on the
falling edge of clk10MHz while g1024/res_in run
on the rising edge. That negedge/posedge interleave is what creates the half-sample
z⁻⁰·⁵ offset the transfer function needs.
Saves the current oscillator output for next cycle. f1024[23:11] is the top 13 bits =
u (line 12: u = f1024[23:11]); "cut in half" because it drops one more bit
than v = f1024[23:10] (an extra ÷2). It feeds u_temp = u + u_past →
u_0d5 = u_temp[13:1] = (u + u_past)/2, the half-period-averaged output.
把目前的振盪器輸出存起來供下一週期使用。f1024[23:11] 是最高 13 位元=u(第 12 行:u = f1024[23:11]);之所以說「砍半」,是因為它比 v = f1024[23:10] 再少取一個位元(多除以 2)。它餵入 u_temp = u + u_past → u_0d5 = u_temp[13:1] = (u + u_past)/2,即半週期平均輸出。
The f1024 update (F(z) recursion) | f1024 更新(F(z) 遞迴)
Every operand is sign-extended to 24 bits (the width of f1024) so the signed addition lines up:
e (18b) sign-extended to 24b | e(18 位元)符號延伸到 24 位元
+ e[n] (z⁰ tap)
{{5{e_1[17]}}, e_1, 1'b0}
appending 1'b0 = ×2, then sign-extend to 24b | 尾接 1'b0=×2,再符號延伸到 24 位元
+ 2·e[n-1] (z⁻¹ tap, ×2)
{{6{e_2[17]}}, e_2}
e_2 sign-extended to 24b | e_2 符號延伸到 24 位元
+ e[n-2] (z⁻² tap)
e_1 <= e; e_2 <= e_1;
A 2-tap shift register delaying the error: after this edge e_1 = e[n],
e_2 = e[n-1]. Because non-blocking <= evaluates all right-hand sides with
the old values first, the f1024 update uses the current combinational
e = e[n] (= res_in − g1024[27:10]) together with the still-old
e_1 = e[n-1] and e_2 = e[n-2].
Numerator(1+z⁻¹)² = the e + 2·e_1 + e_2 taps (1, 2, 1).
The comment writes it as (z⁻⁰·⁵+z⁻¹·⁵) because the negedge update shifts F by half a
sample relative to the posedge G/res_in — that is the z⁻⁰·⁵ fractional delay.
Why the widths (header comment):
The module notes that after retuning to 40 kHz the oscillation amplitude doubled and
f1024/g1024 overflowed, so this version gives "one more bit to f1024,
e, e_1." That is why f1024 is 24-bit and
e/e_1/e_2 are 18-bit signed — headroom so the +2·e[n-1]
term and the accumulation never overflow.
The error bus and the registers feeding it — res_in, y_in, e,
e_1, e_2 — are all declared signed [17:0]. Two design reasons:
誤差匯流排與餵入它的暫存器——res_in、y_in、e、e_1、e_2——全部宣告為 signed [17:0]。有兩個設計理由:
Why signed | 為什麼要有號
The oscillator signal is bipolar — it swings symmetrically above and below zero, so
every term on the bus takes both signs: the relay output is ±256, the injection
y_in ranges −128…+128, and the error e and integrator
feedback are both bidirectional. The datapath must therefore use two's-complement signed
arithmetic; an unsigned bus could not represent the negative half of the waveform.
振盪器訊號是雙極性的——它對稱地在零上下擺動,所以匯流排上的每一項都有正有負:繼電器輸出是 ±256,注入 y_in 的範圍是 −128…+128,誤差 e 與積分器回授也都是雙向的。因此資料路徑必須使用二補數有號運算;無號匯流排無法表示波形的負半邊。
Why 18 bits | 為什麼是 18 位元
The width is set by the error subtraction (relay_feedback2.v line 21):
位寬由誤差減法決定(relay_feedback2.v 第 21 行):
assign e = res_in - g1024[27:10];
g1024 is the 28-bit integrator state; the slice g1024[27:10]
is its top 18 bits (= 27−10+1), the ÷1024-scaled feedback term. Since
e = res_in − g1024[27:10], the error bus must be ≥18 bits to hold that
subtraction without truncating the feedback or overflowing — so res_in, y_in,
e, e_1, e_2 are all sized to one consistent 18-bit bus.
Preserve low-bit resolution — keeping 18 bits retains the fine bits of the ÷1024
integrator feedback that set the precise 40 kHz frequency and Q; truncating to ~10 bits would
coarsen the tuning.
In short: signed because the waveform is bipolar; 18 bits because
e = res_in − g1024[27:10], and the 18-bit integrator-feedback slice sets the bus width —
with headroom so the resonator recursion never overflows and the frequency/Q resolution is preserved.
簡而言之:有號是因為波形是雙極性的;18 位元是因為 e = res_in − g1024[27:10],18 位元的積分器回授切片決定了匯流排位寬——並保留餘裕,讓諧振器遞迴永不溢位、頻率/Q 解析度得以保留。
Integrator Gain — Frequency Tuning | 積分器增益——頻率調整
The coefficient 332/1024 is implemented as a shift-add approximation:
Frequency:
At 10 MHz clock, this gain yields a closed-loop natural frequency of
40 kHz. The original design used 306/1024 for a lower frequency;
the change to 332/1024 was the explicit redesign to reach 40 kHz.
The 1/1024 leak is the − f1024/1024 term in the F(z) recursion:
1/1024 洩漏就是 F(z) 遞迴式中的 − f1024/1024 項:
f1024[n] = f1024[n-1] − f1024[n-1]/1024 + (e terms)
= (1023/1024)·f1024[n-1] + (e terms)
It means: every clock sample the filter discards 1/1024 of its current state — it
keeps 1023/1024 ≈ 99.9% and loses ≈0.1%. ("Leak" = the leaky-bucket image:
the stored value continuously drains a tiny bit. 「漏」即漏水桶的比喻:儲存的值持續地洩漏一點點。)
It is the only loss/damping in the resonator.G(z) is a pure,
lossless integrator (pole exactly at z = 1); all damping comes from this one term.
Without it the filter is a pure accumulator — it integrates forever, drifts, and overflows.
它是諧振器中唯一的損耗/阻尼。G(z) 是純粹無損的積分器(極點正好在 z = 1);所有阻尼都來自這一項。少了它,濾波器就是純累加器——永遠積分、漂移、溢位。
It moves the pole just inside the unit circle. No leak → pole at z = 1
(marginally stable, infinite Q). With the leak → pole at z = 1023/1024, a lightly-damped
resonator with finite, high Q. The closer the pole to the unit circle, the smaller the leak and the
higher the Q.
它把極點移到單位圓內側一點點。沒有洩漏 → 極點在 z = 1(臨界穩定、無限 Q)。有洩漏 → 極點在 z = 1023/1024,一個輕阻尼、有限高 Q 的諧振器。極點越靠近單位圓,洩漏越小、Q 越高。
It models a real resonator's energy loss. A physical cavity (Yu's ultrasonic tank,
Q ≈ 25) dissipates a little energy each cycle; the 1/1024 leak is the digital stand-in that gives
this oscillator a finite Q instead of an ideal infinite one.
Decay-time picture:
Losing 1/1024 per sample is exponential decay — f[n] ∝ (1023/1024)ⁿ ≈ e^(−n/1024) — so the
natural ring-down time constant is τ ≈ 1024 samples = 1024·T seconds (a free
oscillation decays to 1/e after ~1024 cycles). The relay keeps pumping energy back in, so the net
result is a stable limit cycle: relay adds energy each cycle, the leak removes a
controlled amount → sustained, fixed-amplitude 40 kHz oscillation.
Why exactly 1/1024:1/1024 = 2⁻¹⁰, so the leak is just an arithmetic right-shift by 10
(f1024 >> 10) — no multiplier needed, and a convenient power-of-two for the frequency/Q
targets.
In one line: the 1/1024 leak is the resonator's controlled energy loss per sample — it turns
a would-be infinite-Q pure integrator into a stable, finite-Q bandpass at 40 kHz, and single-handedly
sets the bandwidth, corner frequency, and Q.
The leaky-integrator part of the F(z) recursion is:
F(z) 遞迴式中的洩漏積分器部分為:
f[n] = f[n-1] − (1/1024)·f[n-1] + (input)
Move f[n-1] left and divide by the sample period T:
把 f[n-1] 移到左邊,再除以取樣週期 T:
(f[n] − f[n-1]) / T = −(1/1024T)·f[n-1] + (input)/T
The left side is the backward-difference approximation of df/dt, so in continuous time
df/dt + (1/1024T)·f = input. Comparing with the standard first-order low-pass
df/dt + ωc·f = input:
A low-pass 1/(s + ωc) has its pole at s = −ωc, so again
ωc = 1/(1024T).
低通 1/(s + ωc) 的極點在 s = −ωc,所以同樣得到 ωc = 1/(1024T)。
Intuition:
The 1/1024 is the leak (the −f1024/1024 subtraction = pole at
1023/1024); the T converts "per sample" into "per second." Losing
1/1024 of the state each sample ⇒ a time constant of 1024·T seconds ⇒ corner frequency
ωc = 1/(1024T).
There is no explicit "Q" coefficient — the quality factor is set by the only lossy element in the
loop: the 1/1024 leak in the F(z) feedback path, i.e. the pole at
1023/1024 (the −f1024/1024 subtraction). G(z) is a pure, lossless
integrator (pole exactly at z=1), so all damping comes from this one term. The closer the pole sits
to the unit circle, the higher the Q.
vs Yu's Q = 25:
Yu & Horng's Q = 25 is the physical quality factor of the ultrasonic SIL
cavity. Here it becomes a design choice realized by the leak coefficient. The value differs because
this is a relay-feedback limit cycle — the relay sustains the amplitude regardless
of Q, so Q governs spectral selectivity/purity, not whether it oscillates. To raise Q, move
the pole nearer the unit circle: e.g. change the leak from f1024[23:10] (1/1024) to
f1024[23:11] (1/2048), roughly doubling Q and halving the bandwidth.
F(z) bandpass filter state. Negedge gives half-period offset from G(z), implementing the z^-0.5 fractional delay in the transfer function. | F(z) 帶通濾波器狀態。negedge 與 G(z) 相差半個週期,實現轉移函數中的 z^-0.5 分數延遲。
posedge clk10MHz
g1024, res_in
G(z) integrator and relay computation. The interleaved clocking between F and G is essential to the z^-0.5 term. | G(z) 積分器與繼電器運算。F 與 G 之間的交錯時脈是 z^-0.5 項的關鍵。
Phase Injection (uinj) | 相位注入 (uinj)
The 3-bit uinj input comes from the PI_antiWindup controller.
It is sign-extended into an 18-bit value y_in and added to the relay output
each cycle:
Signed 3-bit injection from PI_antiWindup controller | 來自 PI_antiWindup 控制器的 3 位元有號注入
y_in formation | y_in 構成
{{10{uinj[2]}}, uinj[1:0], 6'b0}
Sign-extend to 18 bits, then left-shift 6 (×64 scale) | 符號延伸到 18 位元,再左移 6(×64 縮放)
Left-shift factor | 左移因子
×64 (<<6)
Maps 3-bit uinj into 18-bit res_in amplitude range | 把 3 位元 uinj 映射到 18 位元 res_in 振幅範圍
res_in formation | res_in 構成
sign(f1024) + y_in
Relay output (±1 scaled) plus injection — updated on posedge | 繼電器輸出(±1 縮放)加注入——posedge 更新
This injection steers the oscillation phase, closing the overall phase-lock loop.
這個注入調控振盪相位,閉合整體的相位鎖定迴路。
Why is uinj 3 bits? | 為什麼 uinj 是 3 位元?
The width is set by how the injection is generated — it is a smoothed 1-bit delayed
signal, not a fine-resolution value. In PI_antiWindup the injection is produced by
delay_line2, whose input is a single bit (uin) delayed by a tunable
amount d through a 64k-RAM circular buffer. That delay line is the
tunable delay (TDL) of the phase-cancelling loop; d comes from the PI
output (d = PI_out[16:0]).
A raw 1-bit injection would be a hard square edge (harmonic-rich, poor for clean injection locking),
so delay_line2 combines several consecutive delayed samples into a small
signed multi-level value — its own comment reads "Smoother transition of input
injection for ILO". It emits five symmetric levels:
未經處理的 1 位元注入會是生硬的方波邊緣(諧波豐富,不利於乾淨的注入鎖定),所以 delay_line2 把數個連續延遲取樣合併成一個小的有號多位準值——它自己的註解寫著 "Smoother transition of input injection for ILO"。它送出五個對稱位準:
On a transition the 1-bit edge (red) would jump in one clock; the delay-line output (green) instead
walks through all five levels +2 → +1 → 0 → −1 → −2
(codes 010, 001, 000, 111, 110) over consecutive clocks — softening the edge before it is
scaled ×64 and injected. (The mapping is inverting: an all-low input window gives +2, an
all-high window gives −2; the steady levels +2 and −2 dominate, the rest appear only during the edge.)
So 3 bits is the minimum that fits:
a sign plus the small range −2…+2.
More bits would be useless — the source is only 1-bit, so there is no finer amplitude
information to carry, only a few edge-smoothing steps. Fewer bits cannot work — you
need sign + magnitude for −2…+2. Keeping it narrow also keeps the delay-line word small (1-bit stored
in RAM, widened to 3-bit only at the output smoothing stage).
The ×64 shift then scales this perturbation to ≈ −128…+128 against the relay's
±256 — i.e. the injection is a fraction of the oscillation amplitude, exactly the role of
Yu's injection ratio η = A_inj/A_osc: it nudges the phase, it does not carry precise
amplitude.
Phase error demodulation in demodulator.v | 於 demodulator.v 進行相位誤差解調
u_0d5
(u + u_past) / 2 — average of current & previous
Feeds a 12-register delay chain in _1Mhz_beta010.v to produce a reference delayed by 12.5 cycles of 2 MHz (= 6.25 µs = quarter period of 40 kHz) | 餵入 _1Mhz_beta010.v 的 12 級暫存器延遲鏈,產生延遲 12.5 個 2 MHz 週期的參考訊號(= 6.25 µs = 40 kHz 的四分之一週期)
v
f1024[23:10] — upper 14 bits (÷1, full resolution)
Tx DAC output (Q channel) after 62.5 ns delay and amplitude correction in FP domain | 經 62.5 週期延遲與浮點域振幅校正後的 Tx DAC 輸出(Q 通道)
Maps 3-bit uinj into 18-bit res_in range | 把 3 位元 uinj 映射到 18 位元 res_in 範圍
3. PI Phase-Lock Controller | PI 相位鎖定控制器
Theory ref — Yu & Horng §5, eq. (11),(12),(18):
Same PI controller C(s) = k_I/s + k_p regulating θ→π against the Doppler-phase
disturbance. Yu's design collapses to a single knob ω_BW (the PI zero at ω_c
cancels the plant pole → pure integrator −ω_BW/s). The module's anti-windup
maps onto Yu's stability region θ∈(0.5π, 1.5π) and max trackable speed
v_max = π·ω_BW/(5·ω_n)·c — beyond it the plant gain flips sign.
[Yu guide]
The system implements a digital phase-locked loop (PLL) using a PI
controller. The oscillator phase is measured by demodulation, corrected by a PI
integrator, and fed back as a variable-delay injection into the relay
oscillator. A PWM output carries the same control signal to an analog path.
本系統用 PI 控制器實作一個數位鎖相迴路(PLL)。振盪器相位經解調量測、由 PI 積分器修正,再以可變延遲注入回授進繼電振盪器。PWM 輸出把同一個控制訊號帶到類比路徑。
Which .v Files Implement This | 由哪些 .v 檔案實作
§3 spans three Verilog files, wired together in the top module _1Mhz_beta010.v:
§3 橫跨三個 Verilog 檔案,在頂層模組 _1Mhz_beta010.v 中接線在一起:
File | 檔案
Role | 角色
Instance in _1Mhz_beta010.v | 在 _1Mhz_beta010.v 中的實例
PI_antiWindup.v
the PI controller itself (the core of §3) — outputs u_inj, pwm_in, data | PI 控制器本體(§3 的核心)——輸出 u_inj、pwm_in、data
PI_antiWindup U4 (line 90)
demodulator.v
produces the phase error x256 that feeds the PI | 產生餵給 PI 的相位誤差 x256
demodulator U3 (line 49)
delay_line2.v
the tunable delay line that forms the injection u_inj | 構成注入訊號 u_inj 的可調延遲線
instantiated inside PI_antiWindup.v (not in the top module)
The controller proper is PI_antiWindup.v; its input comes from
demodulator.v (x256), and its injection output is built with
delay_line2.v. That is why delay_line2 does not appear in
_1Mhz_beta010.v directly — only demodulator (U3) and PI_antiWindup
(U4) are instantiated at the top; delay_line2 is nested one level down inside the PI block.
128K RAM circular buffer delays uin3 by d = PI_out[16:0] samples → phase correction injection | 128K RAM 環形緩衝把 uin3 延遲 d = PI_out[16:0] 個取樣 → 相位修正注入
8. PWM output | 8. PWM 輸出
PWM → pwm (1-bit)
50 MHz
10-bit pwm_in from PI_antiWindup drives analog output | 來自 PI_antiWindup 的 10 位元 pwm_in 驅動類比輸出
PI Controller Coefficients | PI 控制器係數
Parameter | 參數
Expression | 表達式
Value | 數值
Notes | 備註
Integral gain kI | 積分增益 kI
1/2⁷ + 1/2⁸ = 3/2⁸
≈ 0.01172 per sample
Applied at 50 kHz — accumulates into 32-bit int256 | 以 50 kHz 施加——累加進 32 位元的 int256
Proportional gain kP | 比例增益 kP
1/2¹⁵
≈ 3.05 × 10⁻⁵
Very small — system is predominantly integral action | 非常小——系統以積分作用為主
Output scale | 輸出縮放
int256[31:8]
÷ 256
Shifts integrator accumulator down to 24-bit PI_out | 把積分累加器右移縮成 24 位元的 PI_out
Output filter pole | 輸出濾波器極點
511/512
≈ 0.998
Cutoff ≈ 15.6 Hz at 50 kHz. DC gain = 1 (unity). Output = xf[33:10] | 在 50 kHz 下截止頻率 ≈ 15.6 Hz。直流增益=1(單位增益)。輸出=xf[33:10]
Error gain (shift_bit) | 誤差增益(shift_bit)
×2^shift_bit, shift_bit ∈ {0,1,2,3}
×1, ×2, ×4, ×8
Incremented by hardware change button; scales x256 − r | 由硬體 change 按鈕遞增;縮放 x256 − r
Delay range | 延遲範圍
d = PI_out[16:0]
0 – 131071 samples @ 50 MHz
= 0 – 2.62 ms → covers many periods of 40 kHz for phase wrap | = 0 – 2.62 ms → 涵蓋 40 kHz 的許多週期,供相位迴繞使用
What "Anti-Windup" Means | 「抗積分飽和」是什麼意思
The module name is PI_antiWindup — so what is the "windup" it defends against? It is a
problem that afflicts any controller with an integrator (the I term).
The integral term here is the pure accumulator int256 ← int256 + kI·error — a pole at
z = 1 (see the Z-Domain Primer §6), so it sums error
without limit. Now suppose the actuator saturates — the delay command
d = PI_out[16:0] hits its 0…131071 range limit, or pwm_in clips at full scale:
這裡的積分項是純累加器 int256 ← int256 + kI·error——極點在 z = 1(見 z 域入門 §6),所以它會無上限地累加誤差。現在假設致動器飽和了——延遲命令 d = PI_out[16:0] 撞到 0…131071 的範圍極限,或 pwm_in 在滿刻度處截波:
A large phase error demands a big output.
大的相位誤差要求大的輸出。
The output is clamped at its limit — but the error does not go away.
輸出被鉗制在極限值——但誤差並沒有消失。
The integrator keeps adding that non-zero error every 50 kHz sample, so int256 grows
huge — it "winds up."
When the error finally reverses, int256 is enormous, so the controller keeps
commanding the limit long after it should have backed off — it must "unwind" first.
Symptom: large overshoot and sluggish, oscillatory recovery after any saturation event —
the loop behaves as if it "remembers" too much. In this PLL that means the 40 kHz phase lock slews past
its target and takes many cycles to settle.
Anti-windup stops the integrator from accumulating while the output is saturated, so
int256 never runs away and the loop responds immediately once the actuator unsaturates.
Common schemes:
Freeze the int256 ← int256 + kI·error update whenever the output is at a limit
and the error would push it further into saturation. | 每當輸出在極限值且誤差會把它推得更深入飽和時,凍結 int256 ← int256 + kI·error 更新。
Back-calculation
Feed the difference between the commanded and the actual (clamped) output back
into the integrator, subtracting it so int256 tracks what the actuator can deliver. | 把命令值與實際(被鉗制)輸出之間的差回饋進積分器並減去,讓 int256 追蹤致動器實際能提供的值。
Integrator-state limit
Simply clamp int256 itself to a fixed max/min range. | 直接把 int256 本身鉗制在固定的最大/最小範圍內。
Why this loop specifically needs it:
The integral pole sits exactly on the unit circle (z = 1) — infinite DC gain, with
nothing to pull it back. Combined with a saturating delay command (d bounded to 17 bits)
and a clipping PWM, the accumulator would otherwise wind up indefinitely. Anti-windup bounds it so the
phase-injection output stays controllable and the 40 kHz lock recovers cleanly after limiting. This also
maps onto Yu & Horng's stability region θ ∈ (0.5π, 1.5π) noted at the top of this
section — staying inside the linear regime where the plant gain keeps its sign.
Injection_Method_Switch picks local (1) vs received-over-the-air (0)
feedback; ClutterCancel_OR_ImageReject_Switch picks the variant within each group.
Both are board slide switches — SW9 (PIN_AE19) and SW8 (PIN_Y11)
respectively, per C5G User Manual Table 3-3 in docs/ (input ports
@_1Mhz_beta010.v:662, mirrored on the LEDs via
r@:22; see also
§6 Board Controls).
The uin2 → uin3 chain is a two-flip-flop synchronizer clocked at
clk10MHz: uin is an asynchronous external pin and the two
_DB signals come from clk10MHz1 (a PLL copy), so double-registering
avoids metastability before the bit enters the control loop.
Where each 1-bit source comes from | 每個 1 位元來源從哪裡來
Source | 來源
Origin chain | 來源鏈
uin
Raw input pin (@_1Mhz_beta010.v:12) — feedback arrives
already as a digital square wave from outside the chip. | 原始輸入腳位(@_1Mhz_beta010.v:12)——回授從晶片外以數位方波形式直接進來。
u_out
assign u_out = u[12](@:35) — sign bit
of the 13-bit relay_feedback2 output u, i.e. a 40 kHz square wave.
Closes the loop on itself: a bypass/sanity mode with no radio path involved. | assign u_out = u[12](@:35)——13 位元 relay_feedback2 輸出 u 的符號位元,即 40 kHz 方波。迴路對自己閉合:不經無線電路徑的旁通/檢查模式。
Rx_ir_Amped_DB
Image-reject chain: Rx_IQ_Sum_B (15-bit) →
smittch_trigger with SH=7 (the former ×128 Large_Amplifier_ir)
→ debounce. | 影像抑制鏈:Rx_IQ_Sum_B(15 位元)→ SH=7 的 smittch_trigger(原 ×128 Large_Amplifier_ir)→ 去抖。
Rx_cc_Amped_DB
Clutter-cancel chain: Rx_cc = Rx_Amped − Tx_Q_Delay (Q24.8) in
clutter_cancel.v — subtracting the phase-aligned Tx quadrature
replica removes direct leakage — then smittch_trigger with SH=7 (the former ×128
Large_Amplifier_cc) → debounce. | 雜波消除鏈:clutter_cancel.v 中的 Rx_cc = Rx_Amped − Tx_Q_Delay(Q24.8)——減去相位對齊的 Tx 正交複本以移除直接洩漏——再經 SH=7 的 smittch_trigger(原 ×128 Large_Amplifier_cc)→ 去抖。
Why everything is 1-bit:
The receive signals are 15-bit (Rx_IQ_Sum_B) and Q24.8 (Rx_cc) fixed-point words, but the feedback loop only needs the
zero-crossing timing (phase), not amplitude. Two stages squash them to one bit:
① smittch_trigger(smittch_trigger.v) —
Schmitt trigger with thresholds ±200 LSB (parameter TH, scaled by FRAC; SH=7 on the "_Amped" instances stands in for the old ×128 amplifier, so they fire at ±1.56 LSB):
output goes high above +TH and only drops below −TH; the hysteresis band rejects noise around zero.
② Rx_Debounce(Rx_Debounce.v) — the output
toggles only after the input has held the opposite level for 16 consecutive clk10MHz1
cycles (1.6 µs); any bounce resets the counter.
Where uin3 goes:uin3 feeds PI_antiWindup port .uin(@_1Mhz_beta010.v:90). Inside, it is not used arithmetically —
it enters delay_line2(PI_antiWindup.v:48),
a 1-bit delay line clocked at 50 MHz whose tap index d is set by the PI output.
The delayed bit re-emerges as u_inj and drives the uinj port of
relay_feedback2(@:34). In other words, the PI
loop controls phase by adjusting how many 20 ns steps the selected 1-bit feedback
signal is delayed before re-injection into the relay oscillator.
The commented lines 97–98 and 100 of _1Mhz_beta010.v record earlier mux
iterations: one overrode the selection when Clutter_weak was asserted (the FP compare
@:603 checking whether the clutter amplitude-correction factor is below 5.0), one hard-wired the
clutter-cancel path, and one was a plain passthrough of uin.
Relation to Clutter Cancellation — CC Inside the Lock Loop | 與雜波消除的關係——CC 在鎖定迴路之內
In mode 0/0 the clutter canceller of §6 is not a side branch that merely
feeds the peak detector — it sits inside the injection-locking loop. The SIL
principle is that the oscillator locks its phase to the zero-crossing timing of whatever bit
arrives at relay_feedback2.uinj. The mux decides whose zero crossings those are,
and clutter cancellation decides what the receive-path candidate actually carries.
The problem: the raw receive signal Rx is dominated by direct
Tx→Rx leakage (clutter). Leakage phase is constant and carries no target information — injecting
raw Rx would make the loop lock to its own transmission, functionally the same as
the self-lock mode (u_out). The CC path subtracts an amplitude-matched,
phase-aligned replica of the transmit signal so that the zero crossings of the residual are set
by the target echo instead:
// clutter_cancel.v, fixed point since 2026-09-04// 1. amplitude match — ratio Tx/Rx as unsigned Q8.16 (free-running restoring divider)
div_fx #(.NW(32), .DW(16)) FX_Divider (.clk(~clk10MHz4), .num({1'b0, Tx_Q_Amp, 16'b0}), .den(Rx_Amp), .q(ratio_q), ...);
// 2. scale Rx up to the Tx level (15 x 25 signed product, kept as Q24.8)
wire signed [39:0] prod = Rx_IQ_Sum * $signed({1'b0, Clutter_Amp_Ratio});
always @(posedge clk10MHz2) Rx_Amped <= prod[39:8];
// 3. phase-align the Tx replica (sign bits in, 256 x 14-bit RAM delay line)
phase_detector Phase_Detector (~clk10MHz2, 1'b1, phase_diff_, Tx_Q[13], Rx_IQ_Sum[14]);
delay_line #(.W(14)) Variable_Delay_Line (clk10MHz2, Tx_Q_Delay, Tx_Q, phase_diff);
// 4. subtract — residual ≈ target echo (combinational, Q24.8)
assign Rx_cc = Rx_Amped - {{10{Tx_Q_Delay[13]}}, Tx_Q_Delay, 8'b0};
The residual is small (good cancellation removes most of the energy — see
§6 signal-level analysis), so it is boosted
×128 and squashed to the 1-bit phase signal the mux consumes:
// _1Mhz_beta010.v — the x128 is the SH=7 shift inside the trigger (Q24.8 input, FRAC=8)
smittch_trigger #(.W(32), .FRAC(8), .SH(7), .TH(200)) Trig_Rx_cc_Amped (clk10MHz1, Rx_cc_Amped_Trig, Rx_cc); // ±200 hysteresis on x·128
Rx_Debounce DB_Rx_cc_Amped (clk10MHz1, Rx_cc_Amped_DB, Rx_cc_Amped_Trig); // 16-cycle hold
The mux (0/0) passes Rx_cc_Amped_DB to uin3, and the loop closes
through the PI-controlled delay line back into the oscillator:
多工器(0/0)把 Rx_cc_Amped_DB 傳給 uin3,迴路經由 PI 控制的延遲線閉合、回到振盪器:
// PI_antiWindup.v:46–48 — PI output sets the delay-line tap (20 ns steps @ clk50MHz)wire [16:0] d;
assign d = PI_out[16:0];
delay_line2 U12(.in(uin), .out(u_inj), .d(d), .clk(clk50MHz));
// relay_feedback2.v:34,45 — the injected bit is summed into the resonator inputassign y_in = {{10{uinj[2]}}, uinj[1:0], 6'b0};
res_in <= {{9{f1024[23]}}, 1'b1, 8'b0} + y_in;
Consequence:
the quality of the subtraction at _1Mhz_beta010.v:627 directly determines whose phase
the PI controller locks the oscillator to. Good cancellation → the loop locks to the target
echo, and target range/motion appears in PI_out/data on the UART.
Poor cancellation → the leakage still dominates the zero crossings and the loop degenerates
toward self-lock, blind to the target.
Clutter_weak tie-in:Clutter_weak = (Clutter_Amp_Ratio > 24'd327680) in clutter_cancel.v
compares the Q8.16 ratio (= Tx_amp/Rx_amp) against 5.0
(was an FP_Comparator against 32'h40A00000). A large ratio means Rx is weak relative to Tx — i.e. little leakage
to cancel. The abandoned mux iteration at :97 used this flag to auto-fall-back to
the image-reject path when cancellation was unnecessary; the current code leaves the choice to
the CC_IR switch and only mirrors Clutter_weak on the LEDs
(@:22).
Theory ref — Yu & Horng "Yu vs Wu" table (freq-demod row):Wu-specific. This is the Low-IF I/Q transmit: the DAC emits 40 kHz I/Q, which the
external 5.8 GHz LO up-converts to 5.8 GHz + 40 kHz (image sideband suppressed ≈62 dB).
Yu's ultrasonic radar has no I/Q transmit — it radiates the SIL oscillation directly and
demodulates with the T/4 self-multiply discriminator instead.
[Yu guide]
Two 14-bit DAC channels output a quadrature (IQ) 40 kHz transmit waveform.
The Q channel is the raw oscillator output; the I channel is the Q channel delayed by
exactly 90° (62.5 cycles at 10 MHz), creating the orthogonal component needed for
image rejection on the receive side.
Register at negedge clk10MHz | 在 negedge clk10MHz 鎖存
nv_reg
_1Mhz_beta010.v (@:237)
3
3 pipeline delay regs (align latency with I path) | 3 級管線延遲暫存器(與 I 路徑延遲對齊)
nv_reg_d3 → DAC_DB
_1Mhz_beta010.v (@:257; DAC_DB @:206)
Fixed point since 2026-09-04. The step table above is the original Tx chain. Today v_raw → tx_gain (KEY2) → hilbert_90 gives I = y_90 and Q = y_0 × 1/1.022 (fixed point, see §8); Convert_b2FP/MULT/Convert_FP2b and nv_reg no longer exist (the IEEE-754 IP was deleted from the project on 2026-09-04).
(Historical) All IP modules above (delay_62d5_10MHz, Convert_b2FP, MULT,
Convert_FP2b) were instantiated and wired in _1Mhz_beta010.v; the
@:NNN notes give the line in _1Mhz_beta010.v where each instance / assignment
lives. The MSB-flip and pipeline-register logic (Q channel, and step 5's nv_62d5_Tx) are
plain assign/always blocks in _1Mhz_beta010.v itself.
MSB-flip lines (2's-complement → offset-binary, {~x[13], x[12:0]}):
the I channel (step 5) is at _1Mhz_beta010.v:236
(assign nv_62d5_Tx = {~nv_62d5[13], nv_62d5[12:0]}); the Q channel (step 1)
is at _1Mhz_beta010.v:188 (assign v_Tx = {~v[13], v[12:0]}). The active DAC outputs
use these (DAC_DA = nv_62d5_Tx @:205, DAC_DB = nv_reg_d3 @:206); the similar
v_62d5_Tx at :189 belongs to a commented-out path.
MSB flip:{~v[13], v[12:0]} converts 2's-complement signed to offset-binary (DAC format).
In 2's complement, MSB=1 means negative; in offset binary, MSB=1 means positive (mid-scale = 0x2000).
Flipping only the MSB switches between the two representations.
Why flipping the MSB gives offset-binary — the math | 為什麼翻轉 MSB 就是偏移二進位——數學推導(click to expand)
It works because flipping the MSB is arithmetically identical to adding 8192, and 8192
is exactly the offset between the two representations. Three steps:
1. The two formats differ by a constant offset of 2¹³ = 8192 | 1. 兩種格式只差一個常數偏移 2¹³ = 8192
Both formats use the same lower 13 bits with the same positive weights; only the
meaning of the whole code differs. Two's complement gives a signed value T (−8192…+8191);
offset binary reads the code as a plain unsigned U and the analog stage treats
U − 8192 as the value. To make the DAC output the signed value T:
Either way only bit 13 flips and bits 0–12 are unchanged — exactly what XOR-ing the MSB does:
無論哪種情況,只有位元 13 翻轉、位元 0–12 不變——正是對 MSB 做 XOR 的效果:
T + 2¹³ (mod 2¹⁴) ≡ T ⊕ 2¹³
3. Therefore {~x[13], x[12:0]} is the +8192 offset | 3. 所以 {~x[13], x[12:0]} 就是 +8192 偏移
Flipping the MSB and leaving the rest = adding 8192 = two's-complement → offset-binary. No adder, no
extra logic — one inverter on one wire. (The plot below is the picture of this: a single straight line
out = in + 8192.)
翻轉 MSB、其餘不動=加 8192=二補數 → 偏移二進位。不需要加法器、不需要額外邏輯——一條線上一個反相器。(下方的圖就是這件事的圖像:一條直線 out = in + 8192。)
Bit-weight view — the deepest reason:
In two's complement the MSB has weight −2¹³; in the DAC's unsigned/offset reading the
same bit has weight +2¹³. The lower 13 bits are identical in both. The only
term that changes sign between the encodings is the MSB, so the only bit you must touch to switch
representations is the MSB. Flipping it swaps that one term from −2¹³·b₁₃ to
+2¹³·b₁₃ — the entire difference between the two formats.
Mapping of the 14-bit signed value to the DAC output code. Flipping the MSB is exactly
adding half-scale (+8192), so the transfer curve is a single straight,
monotonic line — no wrap, no discontinuity:
The line is monotonic and continuous: the most-negative signed code (−8192) lands at DAC
code 0 (negative full-scale), signed zero lands at mid-scale (0x2000 = 0 V
differential), and the most-positive code (+8191) lands at 0x3FFF (positive
full-scale). That is why a bare {~x[13], x[12:0]} is sufficient — no adder needed, since
XOR-ing the sign bit is the +8192 offset.
Analog voltage range — board-dependent:
The DAC pin set (DAC_DA/DB[13:0], DAC_CLK_A/B, DAC_WRT_A/B,
DAC_MODE) is the Terasic THDB-ADA AD/DA daughter-card convention, which
carries a dual 14-bit AD9767 DAC. The AD9767 is a current-output part; the
actual output voltage is set by the card's output stage (full-scale current IOUTFS + load /
op-amp), not by anything in _1Mhz_beta010.qsf. Nominal full scale on the
THDB-ADA is ≈ 2 Vpp per channel, so
±V_FS ≈ ±1 V with mid-scale ≈ 0 V — but treat these as typical values and confirm
against your THDB-ADA configuration / output load. The code → voltage map stays linear and
monotonic regardless of the absolute scale: code 0 = −full, 8192 = mid, 16383 = +full.
Worked example — gain, offset & overflow wraparound (formula chain) | 實例——增益、偏移與溢位迴繞(公式鏈)(click to expand)
A worked illustration of why a gained/offset signal must respect the bus width before the
{~x[13], x[12:0]} conversion. Starting from a full-scale 14-bit sine, applying a float
gain and a DC offset, then forcing the result back through a 14-bit signed field, the overflow
wraps (folds) instead of clipping. The formulas used to generate the plots:
With −5000 the asymmetry flips: the positive peak now lands at +7287 — inside ±8191, so it does NOT
fold (clean half-sine). But the negative lobe drops to −17286, even past the 15-bit floor, so
the broad span n ≈ 55…95 wraps: as z crosses −8192 the value jumps to +8191 and descends through the
rail down to ≈ −902 at the trough. One clean lobe, one badly folded lobe.
Interactive waveform plots built from these formulas (self-contained HTML in
plot/): plot/sine_x1p5_signed14.html (×1.5 full-scale fold) and
plot/sine_x1p5_minus5000.html (×1.5 − 5000, freq ×3). The key lesson mirrors the
Tx amplitude correction: the hardware keeps the corrected value
in range (small ×1.022 trim), so it never overflows the 14-bit DAC field — a large gain
like ×1.5 would, and the wrap would turn clean peaks into worst-case discontinuities.
Pipeline alignment:
The I-channel FP pipeline (b→FP → MULT → FP→b at 200 MHz) takes several clock cycles.
The Q channel adds 3 × negedge clk10MHz delays (nv_reg_d3) to keep I and Q
time-aligned at the DAC outputs.
管線對齊:
I 通道的浮點管線(200 MHz 下 b→FP → MULT → FP→b)需要數個時脈週期。Q 通道加上 3 個 negedge clk10MHz 延遲(nv_reg_d3),讓 I 與 Q 在 DAC 輸出端保持時間對齊。
× 1.022 — what "channel balance" means | ×1.022——「通道平衡」是什麼意思(click to expand)
This is the I/Q amplitude-balance trim for image rejection. In a quadrature (I/Q)
transmitter the I and Q paths must be equal in amplitude and exactly 90° apart; any
gain mismatch between the two DAC channels (and their analog filters/buffers) leaks a residual
image sideband that caps the achievable rejection. The Q channel (DAC_DB)
is left at unity gain, so the I channel (DAC_DA) carries the whole correction —
scaled up by ≈2.2% to cancel the hardware mismatch (empirically tuned; the source
lists ×1.00, ×1.022, ×1.05 as the trim options tried, supporting
the ≈62 dB image suppression cited in §4).
The factor lives in _1Mhz_beta010.v as the multiplier constant
32'h3f82d0e5 — the IEEE-754 single-precision literal for ≈ 1.022
(sign 0, exponent 0x7F = 2⁰, mantissa 0x02D0E5/2²³ ≈ 0.022 → 1.022). It is
applied in the floating-point domain via the Convert_b2FP → MULT → Convert_FP2b chain
(step 4 above). The Rx side has an analogous balance trim, 32'h3f83d70a ≈ 1.03.
Note: this describes the original trim (boost I by ×1.022). The current
code applies the same balance the other way round (gain < 1) — see
§8 I/Q Amplitude Balance & Overflow.
這是影像抑制用的 I/Q 振幅平衡微調。在正交(I/Q)發射機中,I 與 Q 路徑必須振幅相等、相位正好相差 90°;兩個 DAC 通道(及其類比濾波器/緩衝器)之間的任何增益不匹配都會洩漏殘餘的影像邊帶,限制可達到的抑制。Q 通道(DAC_DB)保持單位增益,所以整個校正由 I 通道(DAC_DA)承擔——放大約 2.2%以抵消硬體不匹配(經驗調校;原始碼列出曾嘗試 ×1.00、×1.022、×1.05 三個微調選項,支撐 §4 所引的約 62 dB 影像抑制)。
注意:這裡描述的是原始微調(把 I 放大 ×1.022)。目前的程式碼把同樣的平衡反過來施加(增益 < 1)——見 §8 I/Q 振幅平衡與溢位。
Hilbert Transform & Quadrature | 希爾伯特變換與正交
The quadrature (Q) channel v_62d5 is a 90° phase-shifted copy of the I
channel v. The clean textbook way to make a 90° shift across a signal is the
Hilbert transform; this design uses a cheaper fixed T/4 delay that
is only exact at 40 kHz. Both aim at the same goal: build the complex analytic signal so the
Adder can cancel the image sideband.
正交(Q)通道 v_62d5 是 I 通道 v 的 90° 相移複本。對整個訊號做 90° 相移的教科書正解是希爾伯特變換;本設計採用更便宜的固定 T/4 延遲,只在 40 kHz 精確。兩者目標相同:建立複數解析訊號,讓 Adder 能消除影像邊帶。
What the Hilbert transform is:
A linear operator that takes a real signal x(t) and returns another
real signal x̂(t) — every frequency component phase-shifted by −90°,
amplitude unchanged. Time-domain it is convolution with 1/(πt); frequency-domain it just
multiplies the spectrum by a pure imaginary sign function.
H(f) = −j·sgn(f) = −j for f > 0, +j for f < 0 // multiply by −j = rotate positive freqs −90° in the complex plane
The output x̂(t) is still real (the ±j on ±f are conjugate-symmetric, so a real input
gives a real output). The complex number only appears in the next step — the
analytic signal, which uses x̂ as the imaginary part:
x_a(t) = x(t) + j·x̂(t) // spectrum is one-sided (negative frequencies cancelled) → this is what kills the image
Delay vs. true Hilbert transform:
A pure time delay of T/4 gives exactly −90° only at the design frequency
(40 kHz) and its odd harmonics; a real Hilbert transform gives −90° at every
frequency. Because the radar signal is a narrowband 40 kHz tone, the single-frequency
delay is a valid, far cheaper approximation than a broadband Hilbert FIR.
延遲 vs 真正的希爾伯特變換:
純時間延遲 T/4 只在設計頻率(40 kHz)及其奇次諧波上剛好是 −90°;真正的希爾伯特變換在每個頻率都給 −90°。因為雷達訊號是窄帶 40 kHz 單音,單頻延遲是有效且遠比寬帶希爾伯特 FIR 便宜的近似。
Mapping to this design:v = x(t) is I; v_62d5 ≈ x̂(t) is Q (the 90° copy from the T/4 delay line, still
a real 14-bit value). Only when I and Q are treated as I + jQ does the signal become
complex — and the Adder-based combine exploits the resulting one-sided spectrum
(positive/negative-frequency phase cancellation) to suppress the image, the ≈62 dB rejection cited
in §4. See the T/4 = 6.25 µs derivation and the data_0d5 half-sample trim in
Signal Derivation.
Theory ref — Yu & Horng §B-②, eq. (A29)–(A32):
The cleanest correspondence: 62.5 cycles @ 10 MHz = 6.25 µs = T/4 of 40 kHz = 90°,
identical to Yu's quadrature delay T/4 = π/(2ω_n). Yu uses T/4 in the self-multiply FM
discriminator (w = LP{u(t)·u(t−T/4)} ∝ Δω, cos→sin at max slope); Wu reuses the same 90°
to form the Hartley image-reject quadrature pair. Yu's guide states it directly:
"Yu's T/4 time delay ⟷ Wu's 90° I/Q phase split."
[Yu guide]
Delays the 14-bit signed Q sample by exactly 62.5 cycles of 10 MHz
= 6.25 µs = one quarter period of 40 kHz.
This is the key building block for image rejection in the Rx path:
delaying the Q-channel ADC by 90° so it can be added to the I-channel to cancel
the unwanted image frequency.
Fixed point since 2026-09-04. The Rx image reject no longer runs in IEEE-754: the two Convert_b2FP, the ×1.0 MULT, delay_62d5_10MHz_FP (FP Adder + MULT ×0.5 around the 62-register chain) and the FP Adder are gone, with the 200/400 MHz PLL clocks they ran on. Both ADC buses are captured on the falling edge of the encode clock clk10MHz1, I is balanced by ×1/1.03 in fixed point (Rx_I_ * 63627 >>> 16), Q goes through the fixed-point delay_62d5_10MHz (the module the Tx path used before its Hilbert pair), and Rx_IQ_Sum_B = I + Q_delay is a 15-bit register. The old Rx_I_d1/d2/ctrl trim registers only compensated FP pipeline latency and are gone: the Q lag is exactly 62.5 samples by construction. Simulation (tb_rx_path.v): target peak 5909 vs 5910 before, image residual 114 vs 141 before. Next step for this block: the hilbert_90 pair, as on Tx.
Hilbert pair since 2026-09-05. The fixed 62.5-sample delay is gone from the Rx path too. Two hilbert_90 instances (L=62, clk10MHz4): Q feeds one and its y_90 is Q shifted by exactly −90° at any frequency; I feeds the other and its y_0 is the matching midpoint tap x[n-1-L], so both legs carry the same 63-sample bulk delay by construction. Rx_IQ_Sum_B = Rx_I_0 + Rx_Q_90. L only sets the quadrature gain sin(2πf·L/fclk) = 0.99994 at 40 kHz. tb_rx_path.v at 40 / 44 kHz: image residual 87 / 57 LSB (the 1/1.03 balance floor) versus 114 / 552 with the 62.5-sample delay, whose 90° held only at exactly fclk/250. The sections below on how 62.5 cycles were achieved, the signal-flow diagram and the port list describe delay_62d5_10MHz, now kept in the tree but unused; the theory (T/4 = 90°, Hartley sum) is unchanged.
Where I/Q Mixer / Low-IF / Hartley Lives in the Verilog | I/Q 混頻器/低中頻/Hartley 在 Verilog 的哪裡
The "frequency demodulation = I/Q mixer + Low-IF (40 kHz) + Hartley image rejection"
chain splits into an analog half and a digital (FPGA) half — only the
second half is in Verilog.
1. I/Q mixer (down-conversion) — NOT in Verilog | 1. I/Q 混頻器(降頻)——不在 Verilog 裡
The actual mixing (RF × LO) happens in the analog front end with a 5.8 GHz quadrature
LO. The FPGA only generates the quadrature Tx through the DACs (§4) and receives the two
already-down-converted 40 kHz IF channels through the ADCs (in _1Mhz_beta010.v):
Why the FPGA sees 40 kHz, not 5.8 GHz | 為什麼 FPGA 看到的是 40 kHz 而不是 5.8 GHz
A common confusion with cos(ωLO − ωRF)t (eq. 2.16): the
40 kHz is not the LO — it is the difference itself. The echo and
the LO both sit at ~5.8 GHz and differ by only ~40 kHz:
ωLO − ωRF = ~5.8 GHz − ~5.8 GHz ≈ 40 kHz (the IF / beat frequency)
The subtraction (mixing) happens in the analog front end — the 5.8 GHz quadrature
mixer / self-injection-locked oscillator — whose output is the low 40 kHz beat. The FPGA's ADC only
ever samples that 40 kHz; it never digitizes 5.8 GHz.
5.8 GHz echo --+
+--> [ analog mixer / SIL ] --> 40 kHz IF --> ADC --> FPGA
5.8 GHz LO ---+ (does the subtraction) (all digital work @ 40 kHz)
Nyquist for 40 kHz needs only >80 kHz sampling; the ADC/processing clocks
(250 kHz, 10 MHz) are far above that, so it is trivial. Digitizing 5.8 GHz directly would need an
>11.6 GHz ADC — impossible here, which is the entire reason for the analog
down-conversion. It is also why the digital IQ_demod.v mixer is left unused: the mix to 40 kHz
is analog, and the ADCs receive the two already-down-converted 40 kHz IF channels
(Rx_I_, Rx_Q_, lines 321–322).
Image-reject sum (I + Q_delay) → Rx | 影像抑制相加(I + Q_delay)→ Rx
Rx_IQ_Sum_B <= I + Q_delay
_1Mhz_beta010.v : Image Reject module
In short: the mixer is analog; the Low-IF + Hartley image rejection is the
digital hilbert_90 pair (90°) + 15-bit adder (I + Q_90 → Rx_IQ_Sum_B) path in
_1Mhz_beta010.v — exactly the §5 building block, reusing Yu's T/4 delay as the 90° I/Q split.
How the Image Cancels — Deriving (2.16) & (2.17) | 影像如何被消除——推導 (2.16) 與 (2.17)
Theory ref — Yu & Horng eq. (2.13)–(2.17):
Equations (2.16)/(2.17) come from adding SB (2.14) and SC (2.15),
then separating by frequency component. The paper's line “式(2.15) 與 式(2.15) 相加”
is a typo — it should read “式(2.14) 與 式(2.15)”, i.e.
SB + SC.
The two signals being summed (B is the cos / in-phase branch; C is point A after the 90° phase shift):
被相加的兩個訊號(B 是 cos/同相分支;C 是 A 點經 90° 相移之後):
SB(t) = ½ ALOARF cos(ωLO − ωRF)t + ½ ALOAIM cos(ωLO − ωIM)t (2.14)
SC(t) = ½ ALOARF cos(ωLO − ωRF)t − ½ ALOAIM cos(ωLO − ωIM)t (2.15)
The only difference is the sign of the image (IM) term: + in SB, − in SC. Adding them and grouping by frequency:
唯一的差別是影像(IM)項的符號:SB 中是 +、SC 中是 −。把它們相加並按頻率分組:
Wanted (RF) part — same sign → reinforces:
想要的(RF)部分——同號 → 相長:
SIF(t) = ½ ALOARF cos(ωLO−ωRF)t + ½ ALOARF cos(ωLO−ωRF)t
= ALOARF cos(ωLO−ωRF)t (2.16)
Image (IM) part — opposite sign → cancels:
影像(IM)部分——異號 → 相消:
SIM(t) = ½ ALOAIM cos(ωLO−ωIM)t − ½ ALOAIM cos(ωLO−ωIM)t
= 0 (2.17)
So (2.16) is the RF-frequency component of the sum (wanted signal, doubled) and (2.17) is the
image-frequency component (identically zero). The image is cancelled, leaving only the wanted signal.
Interactive — watch the image cancel (2.16 & 2.17) (click to expand)
Top = the wanted (RF) component, bottom = the image (IM) component.
Faint grey = the two summed terms (SB and SC); the bold curve = their sum. With a perfect 90°
shift the wanted doubles and the image vanishes. Add phase error and the image leaks back — that
residual is the image-rejection ratio (IRR).
Why the image term flips sign | 為什麼影像項會翻號
The wanted RF and its image sit on opposite sides of the LO: if
ωRF = ωLO − ωIF then
ωIM = ωLO + ωIF, so the two difference
frequencies have opposite sign:
A 90° phase shift (Hilbert transform) shifts positive and negative
frequencies in opposite directions. So when SA's sin terms become
cos in SC, the RF and IM terms acquire opposite signs — exactly the
+/− pattern in (2.15). Because SB keeps both terms +
and SC flips only the image, the sum doubles the wanted signal and nulls the image — the
Hartley image-reject principle.
90° 相移(希爾伯特變換)把正頻率與負頻率往相反方向移。所以當 SA 的 sin 項在 SC 中變成 cos 時,RF 與 IM 項獲得相反的符號——正是 (2.15) 中的 +/− 型態。因為 SB 兩項都保持 +,而 SC 只翻轉影像,相加就使想要的訊號加倍、影像歸零——Hartley 影像抑制原理。
Redrawn (dark theme) from Wu, M.S. thesis 圖 2.5 — 90° phase-shifter frequency-domain analysis. The 90° shift rotates the −ωc component +90° (→+j) and the +ωc component −90° (→−j) — opposite directions, which is why one sideband adds and the other cancels. Drag the bottom-right corner to resize.
Caveat for your notes: the source's “(2.15)+(2.15)” is a typo for “(2.14)+(2.15)”.
Adding (2.15) to itself would give
ALOARF cos(ωLO−ωRF)t − ALOAIM cos(ωLO−ωIM)t,
which does not cancel the image.
Why You Can't Drop Q — Especially When Ad = Aim | 為什麼不能丟掉 Q——尤其當 Ad = Aim
A tempting shortcut: if the wanted echo and the image happen to have the
same amplitude (Ad = Aim), can you skip the Q
channel and just read Rx_I_? No — and that is precisely the case
where Q matters most.
What each ADC channel actually carries | 每個 ADC 通道實際攜帶什麼
Both the desired (RF) target and the image (IM) are already down-converted to the same
40 kHz IF, and both appear in both channels. With
ω0 = 2π·40 kHz / 10 MHz = 2π/250 rad/sample:
The desired and image are identical in the I channel — both are the same
40 kHz cosine. The only thing that distinguishes them is the sign of the Q
term (desired +sin, image −sin) — i.e. which way the
phasor rotates. That sign is what the 90° delay + add exploits to cancel the image.
The Hartley cancel (I + 90°-delayed Q) works because Q carries the
opposite-sign image term. With no Q there is nothing to cancel against — you simply measure the
sum of target and image, and can never separate them again.
When Ad = Aim:
Rx = 2·Ad·cos(ω0n) — the reading is
half signal, half image, an irrecoverable 50/50 mix.
IRR = 0 dB. The image corrupts the measurement completely, and this is the
worst case, not a simplification.
當 Ad = Aim:Rx = 2·Ad·cos(ω0n)——讀值是一半訊號、一半影像,不可恢復的 50/50 混合。IRR=0 dB。影像徹底汙染量測,而且這是最糟的情況,不是簡化。
The condition that does let you ignore Q is the opposite one:
Aim ≪ Ad (image negligible). Then
Rx_I_ ≈ Ad·cos(ω0n) and no rejection is needed.
Equal amplitudes are the maximum-harm case, so Q is essential.
Intuition: a real (I-only) signal has a symmetric spectrum — it cannot tell
+40 kHz (desired) from −40 kHz (image). Q is the imaginary part
that forms the complex/analytic signal and separates positive from negative frequency. Zero it out and
the two frequencies fold on top of each other. So keep both channels flowing through
hilbert_90 (Rx_IR_hilbert_Q/_I) + the 15-bit adder (Rx_IQ_Sum_B)
— that combine is the entire point of the architecture.
How 62.5 Cycles Is Achieved (Non-Integer Delay) | 62.5 週期如何實現(非整數延遲)
An integer register chain gives only whole-cycle delays. To get the fractional 0.5 cycle,
the module first interpolates between the current and previous sample, then chains 62 registers:
Fixed-point average:data_temp = {in[13],in} + {past[13],past} (15 bits) and data_0d5 = data_temp[14:1] — the
(a + b) / 2 average as a plain add and arithmetic shift, one negedge register. (The removed FP twin
did the same with a 7-cycle Adder and a 5-cycle MULT × 32'h3f000000 on clk_200, ~60 ns
that the I path had to compensate.)
Role in the Image Rejection Architecture | 在影像抑制架構中的角色
The two ADC channels carry I and Q components of the received 40 kHz signal,
nominally 90° apart. Adding them after phase-aligning cancels the image frequency
while reinforcing the wanted frequency:
The Q channel also receives a ×1.03 amplitude correction
(32'h3f83d70a) before the delay to compensate for ADC channel gain mismatch.
The I channel gets two extra pipeline registers (Rx_I_d1, Rx_I_d2) to align
its latency with the FP pipeline delays on the Q path.
Tx I/Q Alignment — the Single nv_reg Register | Tx I/Q 對齊——單一 nv_reg 暫存器
Resolved 2026-09-04. The floating-point ×1.022 pipeline on the I channel is gone: the I/Q balance is now a fixed-point ×(1/1.022) on the Q channel (v_L * 64125 >> 16). Both channels leave the Hilbert registers with identical latency and go straight to DAC_DA/DAC_DB, so nv_reg was deleted exactly as the box below anticipated. The rest of this section is kept as history of why the register existed.
TEMPORARY — DELETE THIS SECTION AND THE nv_reg REGISTER TOGETHER | 暫時性——本節與 nv_reg 暫存器要一起刪除
nv_reg exists only to compensate the latency of the floating-point
amplitude-correction pipeline on the I channel. When that pipeline
(Convert_b2FP → MULT → Convert_FP2b) is replaced by a
fixed-point combinational multiplier (like the Q10 multiply inside
tx_gain.v), the I channel will have zero extra sample delay — then
nv_reg must be removed (Q would otherwise lag I by one sample
= 1.44° at 40 kHz), and this whole section becomes obsolete.
nv_reg 的存在只是為了補償 I 通道浮點振幅校正管線的延遲。當該管線(Convert_b2FP → MULT → Convert_FP2b)改成定點組合邏輯乘法器(像 tx_gain.v 裡的 Q10 乘法)之後,I 通道就不再多出取樣延遲——屆時必須刪除nv_reg(否則 Q 會落後 I 一個取樣 = 40 kHz 時 1.44°),本節也隨之作廢。
Image rejection needs I and Q aligned in time at the transmit output too, not just
on receive. Since the hilbert_90 rework the two channels leave the module with
identical bulk delay; the only remaining imbalance is the floating-point pipeline
that sits on the I channel alone, and it is worth exactly one sample.
影像抑制也需要 I 與 Q 在發射輸出端時間對齊,而不只是接收端。改用 hilbert_90 之後,兩通道離開模組時的整體延遲完全相同;唯一剩下的不平衡是只掛在 I 通道上的浮點管線,而它剛好等於一個取樣。
always @(negedgeclk10MHz) beginnv_reg <= v_Tx; // ONE register = the FP pipeline's one-sample slip (line 178)end// KEY2 gain (tx_gain) is applied upstream on v, before the Hilbert pair — both channels inherit itassignDAC_DA = {~nv_62d5[13], nv_62d5[12:0]}; // I: hilbert y_90 -> FP x1.022 -> offset binaryassignDAC_DB = nv_reg; // Q: hilbert y_0 -> nv_reg (offset binary)
Why exactly one register — the measured timing: at negedge T0 the
hilbert_90 outputs update. The I sample enters
Convert_b2FP → MULT → Convert_FP2b (lines 157–171), whose latency
is 17 clk_200 cycles = 85 ns (measured by simulating the
real IP cores, tb_fp_latency). The DAC latches on the rising edge at
T0+50 ns — too early — so it actually captures the I sample one edge later, at
T0+150 ns. Without nv_reg the Q sample would be latched at T0+50 ns, one
sample ahead of its I partner; the register re-times it to T0+100 ns so both are
captured at T0+150 ns — the same edge, preserving the exact 90° from
hilbert_90.
Rule of thumb: one nv_reg is correct as long as the FP chain's total latency stays
between 50 ns and 150 ns; each additional 100 ns bracket needs one more Q
register. Each register of mismatch shifts I/Q by 1 sample = 1.44° at 40 kHz /
10 MHz.
Why negedge: the DAC latches on the rising edge of
clk10MHz (DAC_CLK_A/B = clk10MHz, lines 149–150). Launching the
data on the falling edge gives a clean half-cycle (~50 ns) of setup/hold margin,
so the value is already stable when the DAC's rising edge captures it — the
"negedge 10 MHz align" idea.
History (2026-09-01): this used to be a d1→d2→d3 chain (plus unused taps
out to d7) feeding DAC_DB from nv_reg_d3. Against the
measured 85 ns FP latency that tap overcompensated by 2 samples, making the
shipped I/Q differential 60.5 samples ≈ 87.1° instead of 90°. The dead chains were
deleted together with the hilbert_90 rework.
6. Clutter Cancellation & Amplitude Matching | 雜波消除與振幅匹配(click to collapse)
Theory ref — Yu & Horng "Yu vs Wu" table (clutter row):Wu-only. Digital range-correlation clutter cancellation has no ultrasonic-SIL
counterpart (the clutter row is blank for Yu) — it exists because Wu's microwave Low-IF receiver
needs it. The amplitude matching makes the clutter subtraction clean.
[Yu guide]
理論對照 — Yu & Horng「Yu vs Wu」表(雜波列):Wu 專屬。數位距離相關的雜波消除在超音波 SIL 中沒有對應(Yu 的雜波列是空白的)——它存在是因為 Wu 的微波低中頻接收機需要它。振幅匹配讓雜波減法乾淨俐落。[Yu 導讀]
Fixed point since 2026-09-04.clutter_cancel.v is integer arithmetic on the 10 MHz clocks: peak-to-peak amplitudes from amplitude_calc, the ratio Tx_Q_Amp / Rx_Amp as unsigned Q8.16 from div_fx (a free-running 32/16 restoring divider that only publishes complete results — the DIV_OPT_VALID guard is gone), the KEY3 latch synchronised to clk10MHz4 instead of always @(negedge CC), phase_detector on the two sign bits, a 256 × 14-bit RAM delay_line, one 15 × 25 multiply and a subtraction giving Rx_cc as signed Q24.8. The ×128 Amplifier_For_CC_Resolve (FP MULT × 32'h43000000) no longer exists as a block: the same gain is the SH=7 shift parameter of the fixed-point smittch_trigger instances (fire at |x| > 200/128 LSB) and a >>> 1 of the Q24.8 value for Rx_cc_Amped_B. Convert_FP2b_16bit became round-half-up + saturate to 16 bits. The DIV/MULT/Subtractor latencies (14–21 / 5 / 7 cycles at 400 MHz) are replaced by one register on each leg of the subtraction. Closed-loop check tb_clutter_cancel.v: ratio 2.5009 for 2.5, phase estimate exact, leakage residual 0.04%, a target added after calibration survives at 753 of an expected 750 LSB p-p. The port list, gain constant and instantiation table below describe the removed amplifier and are kept for reference.
(Historical) A small wrapper module that applied a fixed gain of 128× to a 32-bit
IEEE 754 floating-point signal using the MULT IP core, in the receive path
after the clutter-cancel / image-reject processing, before peak detection and amplitude measurement.
(歷史)一個小型包裝模組,使用 MULT IP 核對 32 位元 IEEE 754 浮點訊號施加固定 128× 增益,位於接收路徑雜波消除/影像抑制處理之後、峰值偵測與振幅量測之前。
Port List | 埠列表
Port | 埠
Dir | 方向
Width | 位寬
Description | 說明
clk
input
1
Clock for the MULT FP multiplier pipeline (driven by ~clk_400 at top level). | MULT 浮點乘法器管線的時脈(頂層由 ~clk_400 驅動)。
Why 128×?
Signals coming out of the CC/IR mixing and subtraction paths are small relative to the
ADC full-scale. The ×128 boost brings them into a range suitable for the
peak_detector amplitude measurement without saturating.
Instantiations in _1Mhz_beta010.v | 在 _1Mhz_beta010.v 中的實例
Since 2026-09-04 the canceller is its own module, clutter_cancel.v, instantiated once in
_1Mhz_beta010.v as U_CC. The instantiations listed below now live inside that module (a pure
extraction: same instances, same names, same clocks); the core keeps only the shared detection path
(Schmitt triggers with the ×128 as SH=7, debounce, CC_Saver, peak extractors). The two
Large_Amplifier_* instances below were removed with the fixed-point conversion.
Feedback role — this path also closes the SIL lock loop:
besides feeding the peak detector, Rx_cc is squashed to 1 bit
(smittch_trigger with SH=7 → Rx_Debounce@_1Mhz_beta010.v:652–653) and, when both board switches
are 0, the resulting Rx_cc_Amped_DB is selected by the
uin3 feedback mux as the injection source for the
PI phase-lock loop. The clutter subtraction therefore determines whose phase the oscillator
locks to — leakage removed → lock to target echo. See
§3 Relation to Clutter Cancellation.
Why ×128 is Needed — Signal Level Analysis | 為什麼需要 ×128——訊號位準分析
Both downstream consumers — smittch_trigger (Schmitt trigger) and
peak_extractor — need a signal with enough swing to cross their detection
thresholds reliably. The two paths produce inherently small residuals for
different physical reasons:
Clutter cancellation subtracts most of the signal. The large direct-path
leakage from the transmitter is cancelled; only the tiny target echo remains.
Good cancellation → residual ≪ original Rx level.
| 雜波消除把大部分訊號減掉了。發射端的大直接路徑洩漏被消除;只留下微小的目標回波。消除得好 → 殘差 ≪ 原始 Rx 位準。
IR
Rx (image-reject sum)
Image rejection sums two ADC channels 90° apart to cancel the unwanted image
frequency. The wanted signal survives but its amplitude is naturally at a low
level relative to the ADC full-scale.
| 影像抑制把相差 90° 的兩個 ADC 通道相加以消除不要的影像頻率。想要的訊號存活下來,但其振幅相對 ADC 滿刻度天生就低。
Without the ×128 boost, neither the Schmitt trigger nor the peak extractor would
see enough swing to function correctly.
沒有 ×128 的提升,施密特觸發器與峰值擷取器都看不到足夠的擺幅、無法正常運作。
Where it sits in the Rx path | 它在 Rx 路徑中的位置
The gain is ×128 (27, ≈ +42 dB, a 7-bit left shift) applied after the
cancellation / rejection stage and before the threshold detectors. It used to be a floating-point
Amplifier_For_CC_Resolve block (MULT × 0x43000000); since 2026-09-04 it is the SH=7
parameter of the fixed-point smittch_trigger and a >>> 1 of the Q24.8 residual for
Rx_cc_Amped_B. The same gain is used in both receive branches:
Cancellation and image rejection deliberately remove the large components (the Tx direct-path
leakage and the image sideband), leaving only a small difference near the quantization / noise
floor. The ×128 restores that residual to a level where smittch_trigger
reliably toggles and peak_extractor registers meaningful peaks — so the boost is a
receive-path stage that recovers usable signal swing lost to the cancellation math.
Summary:
Both paths end at the same two consumers: a Schmitt trigger (needs adequate swing to
toggle) and a peak extractor (needs amplitude well above noise floor). The ×128 gain
is the bridge between the sub-full-scale residual from CC/IR processing and the
minimum level these blocks require.
Board Controls — Switches on the C5G | 板上控制——C5G 的開關
Every run-time control of the clutter canceller is a slide switch on the
Cyclone V GX Starter Kit. Pin mappings are from Table 3-3 of the board manual
(docs/C5G_User_Manual.pdf, extracted text in docs/manual.txt):
雜波消除器的每一個執行期控制都是 Cyclone V GX Starter Kit 上的滑動開關。腳位對應取自板卡手冊表 3-3(docs/C5G_User_Manual.pdf,文字擷取在 docs/manual.txt):
Control | 控制
FPGA pin | FPGA 腳位
Signal | 訊號
Role in the design | 在設計中的角色
SW9
PIN_AE19
Injection_Method_Switch
0 = feed the lock loop from the receive path (required for CC); 1 = local feedback (CC output unused by the loop) | 0=以接收路徑餵鎖定迴路(CC 所需);1=本地回授(迴路不使用 CC 輸出)
SW8
PIN_Y11
ClutterCancel_OR_ImageReject_Switch
The clutter-cancel selector: with SW9 = 0, 0 = clutter-cancel path (Rx_cc_Amped_DB), 1 = image-reject path (Rx_ir_Amped_DB) | 雜波消除選擇器:SW9=0 時,0=雜波消除路徑(Rx_cc_Amped_DB),1=影像抑制路徑(Rx_ir_Amped_DB)
SW7–SW5
AC10, V10, AB10
—
Unused by this design
SW4
PIN_W11
Mult_or_Add
How the manual trim is applied to the amplitude-match ratio: 1 = multiply by Amp_CC_Mult, 0 = add Amp_CC_Add | 手動微調如何施加到振幅匹配比:1=乘上 Amp_CC_Mult,0=加上 Amp_CC_Add
SW3
PIN_AC8
Amp_Sel[3]
Trim amount (MSB = SW3 … LSB = SW0) — the 4-bit code selects both constants from the lookup tables below (0x0 = no trim … 0xF = max trim) | 微調量(MSB=SW3 … LSB=SW0)——4 位元碼從下方查找表同時選出兩個常數(0x0=無微調 … 0xF=最大微調)
SW2
PIN_AD13
Amp_Sel[2]
SW1
PIN_AE10
Amp_Sel[1]
SW0
PIN_AC9
Amp_Sel[0]
KEY1
PIN_P12
change
Push-button, PI controller only (not CC): each press steps the 2-bit shift_bit counter, doubling the PI set-point weight (×29…×212, wraps after 4 presses). Active-low, so it counts on release. | 按鈕,僅供 PI 控制器(非 CC):每按一次使 2 位元 shift_bit 計數器前進一步,把 PI 設定點權重加倍(×29…×212,按四次後迴繞)。低態有效,所以在放開時計數。
KEY0
PIN_P11
reset
Push-button, PI controller only (not CC): clears the PI integrator (.rst of PI_antiWindup) — use it if the loop has wound up after a mode change. | 按鈕,僅供 PI 控制器(非 CC):清除 PI 積分器(PI_antiWindup 的 .rst)——模式切換後迴路積分飽和時使用。
KEY3
PIN_Y16
CC
Push-button, clutter canceller: on press (falling edge of CC, synchronised to clk10MHz4) it commits the fixed-point divider's amplitude-match result — Clutter_Amp_Ratio <= ratio_sat (Q8.16, saturated at 255.99) — and latches phase_diff (clutter_cancel.v). | 按鈕,雜波消除器:按下時(negedge CC)提交 FP 除法器算出的振幅匹配結果——Clutter_Amp_Ratio <= Clutter_Amp_Ratio_Extra,由 DIV_OPT_VALID 穩定交握保護——並同時鎖存 phase_diff(@_1Mhz_beta010.v:354–358)。
KEY2
Y15
—
Unused by this design
So of the ten slide switches, 7 are used (SW9, SW8, SW4, SW3–SW0) and SW7–SW5 are
free; of the four push-buttons (Table 3-2), 3 are used — and one of them,
KEY3 (CC), belongs to the clutter canceller itself: pressing it
commits the freshly computed amplitude-match ratio into the working register (see the table row
above). Only KEY2 is unconnected. The buttons idle high and go low while pressed, which has a subtle
consequence: change is sampled at 1.25 kHz into change_d and the
counter fires on posedge change_d(@_1Mhz_beta010.v:68–76),
so shift_bit actually steps when KEY1 is released, not when it is pressed.
Each step doubles the weight of the set-point word subtracted from the demodulated signal
(case @:82–87 shifts it 9…12 bits), wrapping back after four presses.
reset (KEY0) fans out to a single place — PI_antiWindup.rst — clearing
the integrator. Pin sources: _1Mhz_beta010.qsf:55–56, 146, 157, 165–170.
Operating recipe: to run target detection with clutter cancellation, set
SW9 = 0 and SW8 = 0 (mode 0/0 of the uin3
mux table) — the PI loop then locks to Rx_cc_Amped_DB, the Schmitt-triggered,
debounced output of the Rx_Amped − Tx_Q_Delay subtraction (Q24.8). If residual clutter
remains (imperfect amplitude match), pick a trim style with SW4 and step SW3–SW0 up from 0x0
until the residual nulls — but note: in the current build the trim mux is
commented out (_1Mhz_beta010.v:550), so SW4/SW3–SW0 are inert until it is restored;
see the RTL chain below.
The switch states are mirrored on the red LEDs above them via the
r register (@_1Mhz_beta010.v:22), so you can
confirm the FPGA actually sees SW9/SW8 (plus Clutter_weak) at a glance.
Interactive — flip the virtual switch bank (click to collapse)
This is the C5G slide-switch row as the FPGA sees it. Click a switch to flip it
(blue = 1/up, dark = 0/down; greyed switches are
unconnected). The round push-buttons are momentary: click KEY1 to step the PI
set-point scale (it counts on release, like the real board), KEY0 to reset the
PI integrator, and KEY3 (CC) to commit the clutter
amplitude-match ratio. The readout below explains what your current setting does — same
information as the table above, but live.
From Switch to Residual — the Exact RTL Chain | 從開關到殘差——精確的 RTL 鏈
Fixed point since 2026-09-04. The listing below is the historical IEEE-754 chain (with the Amp_Sel trim, itself removed the same day). The current chain is the fixed-point one shown in §3 Relation to Clutter Cancellation: div_fx → Q8.16 ratio latched on the synchronised KEY3 edge → 15 × 25 multiply → delay_line → subtract → Rx_cc (Q24.8). The DIV_OPT_VALID stability gate mentioned below is unnecessary because div_fx only updates its result when a division completes.
Here is the complete path from the switch pins to the cancelled signal, condensed from
_1Mhz_beta010.v. The auto-computed amplitude ratio comes first, then the two
manual-trim candidates (driven by SW3–SW0), then the SW4 mux, then the actual subtraction:
// 1. Auto amplitude match — FP divider computes ratio = Tx/Rx @_1Mhz_beta010.v:529
DIV FP_Divider (.dataa(Tx_Q_Amp_FP), .datab(Rx_Amp_FP), .result(Amp_Multiplier_));
// 2. Manual trim candidates — both computed in parallel from the Amp_Sel LUTs (SW3–SW0)
Subtractor CC_Extra_eq_Adder_For_Tx (.dataa(Amp_Multiplier_), .datab(Amp_CC_Add), ...); // @:535 ratio − trim
MULT CC_Extra_eq_Multiplier_For_Tx (.dataa(Amp_Multiplier_), .datab(Amp_CC_Mult), ...); // @:542 ratio × trim// 3. SW4 (Mult_or_Add) picks which trim applies — ⚠ CURRENTLY BYPASSED @:549–551// 暫且把這個功能關掉 ("temporarily turn this feature off")// assign Amp_Multiplier_Extra = (Mult_or_Add) ? Amp_Multiplier_Extra_Mult : Amp_Multiplier_Extra_Add;assign Amp_Multiplier_Extra = Amp_Multiplier_; // ← trim disabled: raw ratio passes through// 4. Ratio applied to Rx, replica phase-aligned, then subtracted
MULT Multiplier (.dataa(Rx_FP), .datab(Amp_Multiplier), .result(Rx_Amped_FP)); // @:560
delay_line Variable_Delay_Line (clk10MHz2, 1'b1, Tx_Q_Delay, Tx_Q_FP, phase_diff); // @:580
Subtractor Subtract_Two_Signal_For_ClutterCancel (.dataa(Rx_Amped_FP), .datab(Tx_Q_Delay), .result(Rx_cc)); // @:582
Reading the chain switch-by-switch:
逐開關解讀這條鏈:
SW3–SW0 (Amp_Sel) index two lookup tables (@:452–504) that emit
IEEE 754 constants: an additive offset +0.00…+0.15 and a scale factor
×1.00…×0.85. Both trim candidates are always computed. Note the "add" trim
is actually applied by a Subtractor — both styles can only reduce the
auto ratio, i.e. they correct the case where Rx_Amped_FP comes out too strong.
SW4 (Mult_or_Add) selects between them — but in the current
build the mux line is commented out (@:550), so the raw divider ratio passes straight
through and SW4/SW3–SW0 have no effect on the hardware until line 550 is
restored and line 551 removed. The switches are still wired and the LEDs still mirror them.
Ratio update gate:Amp_Multiplier (the value actually multiplied
into Rx) only latches a new ratio when the divider output has been stable for two samples
(DIV_OPT_VALID, @:436–446) — a glitch filter on the FP division.
SW9/SW8 then decide whether the cancelled signal matters: only in mode 0/0
does Rx_cc — after ×128 amplification, Schmitt trigger and debounce
(Rx_cc_Amped_DB) — become the PI loop's feedback uin3.
In any other mode the canceller still runs, but its output only reaches the UART/DAC
monitoring taps.
Interactive — switch panel: dial the trim, null the residual (click to collapse)
Set a channel-imbalance error (how far the auto ratio is off), then use the board switches to
null it: SW4 picks multiply/add trim, SW3–SW0 set the amount. The
clutter is Rx after the ratio is applied, the
replica is Tx_Q_Delay, the
residual is Rx_cc. SW9/SW8 show whether the
PI loop actually listens to the result. The "trim mux enabled" box models un-commenting
_1Mhz_beta010.v:550 — leave it off to see today's build, where Amp_Sel does nothing.
Amp_Sel — Amplitude Fine-Trim Control | Amp_Sel——振幅微調控制
Removed 2026-09-04. The Amp_Sel/Mult_or_Add trim tables and their extra FP subtractor/multiplier are gone from clutter_cancel.v; Clutter_Amp_Ratio_Extra is the raw divider output, as it had effectively been since the bypass. This section is kept as history.
Amp_Sel is a 4-bit hardware input (switch/pin) that simultaneously drives
two combinational lookup tables, producing two IEEE 754 floating-point constants used
to fine-trim the clutter-cancellation amplitude matching ratio.
Clutter cancellation works by computing Rx_Amped_FP − Tx_Q_Delay. For the
subtraction to cancel well, Rx_Amped_FP must match Tx_Q_Delay
in amplitude. The automatic amplitude ratio is:
In practice, this raw ratio may have a small residual error due to ADC channel imbalance,
hardware asymmetry, or rounding. Amp_Sel provides a manual ±trim on top of
the auto-computed ratio so the operator can null out the residual clutter.
Two Lookup Tables Driven by Amp_Sel | 由 Amp_Sel 驅動的兩個查找表
Amp_Sel
Amp_CC_Add (additive offset) | Amp_CC_Add(加法偏移)
Amp_CC_Mult (scale factor) | Amp_CC_Mult(縮放因子)
0x0
+0.00
×1.00
0x1
+0.01
×0.99
0x2
+0.02
×0.98
0x3
+0.03
×0.97
0x4
+0.04
×0.96
0x5
+0.05
×0.95
0x6
+0.06
×0.94
0x7
+0.07
×0.93
0x8
+0.08
×0.92
0x9
+0.09
×0.91
0xA
+0.10
×0.90
0xB
+0.11
×0.89
0xC
+0.12
×0.88
0xD
+0.13
×0.87
0xE
+0.14
×0.86
0xF
+0.15
×0.85
Note:
The two tables move in opposite directions by design. Amp_CC_Add
increases from 0→+0.15 (subtract a positive offset from the ratio → reduce Rx gain).
Amp_CC_Mult decreases from 1.0→0.85 (multiply ratio down → also reduce Rx gain).
Both trims nudge in the same physical direction: reducing the Rx amplitude scale to
improve cancellation depth.
Scales the auto ratio by a factor slightly below 1 — multiplicative fine-trim mode | 把自動比值乘上略小於 1 的因子——乘法式微調模式
Bypassed
Amp_Multiplier (latched on negedge CC)
Amp_Multiplier_Extra → latched
Holds the stable amplitude ratio used downstream; latches only when CC button pressed and value is stable (DIV_OPT_VALID) | 保存下游使用的穩定振幅比;只在 CC 按下且數值穩定(DIV_OPT_VALID)時鎖存
Active
MULT Multiplier
Rx_FP × Amp_Multiplier → Rx_Amped_FP
Scales the raw Rx signal to match Tx amplitude before the clutter-cancel subtraction | 把原始 Rx 訊號縮放到與 Tx 振幅匹配,供雜波消除減法使用
Active
FP_Comparator
Amp_Multiplier vs 32'h40A00000 (= 5.0)
Detects clutter strength: if ratio > 5 → Rx is much weaker than Tx → clutter is strong | 偵測雜波強度:比值 > 5 → Rx 遠弱於 Tx → 雜波很強
Active
Currently bypassed:
Line 556 forces assign Amp_Multiplier_Extra = Amp_Multiplier_, so the
trim logic is disabled and the raw divider output is used directly. The comment says
暫且把這個功能關掉 ("temporarily disable this feature"). The trim hardware
remains wired and ready to re-enable.
Why Only a Q Replica — Single-Tone Cancellation | 為什麼只需要 Q 複本——單音消除
The clutter canceller subtracts a single replica, Tx_Q_Delay (a delayed copy of the transmit Q
signal) — there is no separate I-axis canceller, and none is needed. The Tx is quadrature
(I = v, Q = v_62d5), but both DACs emit the same 40 kHz
frequency. Whatever leaks into Rx — from the I path, the Q path, direct coupling, or static reflections —
arrives as one 40 kHz sinusoid with some net amplitude and phase.
雜波消除器只減去單一複本 Tx_Q_Delay(發射 Q 訊號的延遲副本)——沒有獨立的 I 軸消除器,也不需要。Tx 是正交的(I=v、Q=v_62d5),但兩個 DAC 發出的是同一個 40 kHz 頻率。無論洩漏進 Rx 的來自 I 路徑、Q 路徑、直接耦合還是靜態反射——到達時都是一個 40 kHz 弦波,帶著某個淨振幅與相位。
A single tone lives in a 2-D space (amplitude + phase), and you can reach any point in it with
one reference tone if you can adjust its phase and amplitude: phase via the
Variable_Delay_Line (delaying Tx_Q_FP sweeps its phase through the full 0–360°),
amplitude via the upstream Rx_Amped (ratio) scaling. So the delayed Q replica reproduces the
combined I- and Q-origin leakage, and one subtraction nulls it. Using Tx_I instead of
Tx_Q would make no difference — same tone, and the delay supplies any 90° offset.
A full complex (I & Q) canceller — what the unused IQ_demod.v would set up — is only needed to
cancel before down-conversion or across many frequencies at once. Here everything is a single
40 kHz IF tone, so scalar delay-and-subtract with just Tx_Q_Delay suffices (which is why
IQ_demod is left commented out).
Interactive — one replica nulls any clutter phase (click to expand)
The clutter is the received 40 kHz tone; move its phase to simulate
leakage arriving more from the I or Q axis. The replica is
Tx_Q delayed (phase) and scaled (amplitude); the residual is
their difference. Match the replica's phase & amplitude to the clutter and the residual flattens to zero —
for any clutter phase, so no separate I canceller is required.
The Q24.8 residual fans out into two measurements (the former ×128 amplifier is now the SH=7 parameter of the trigger and a shift on the 16-bit output):
Timing / threshold:smittch_trigger #(.W(32),.FRAC(8),.SH(7),.TH(200)) slices the waveform into a clean 1-bit signal → Rx_Debounce → Rx_cc_out → CC_Saver.
A comparator with hysteresis (two thresholds): once it goes high it won't drop until the
signal falls well below a lower threshold. This rejects noise in the amplitude domain
near a crossing — but not fast chatter, which is where the debounce comes in.
It only accepts a transition after the input has held its new value for 16 consecutive
clocks; any flicker resets the counter, so disturbances shorter than 16 clocks are rejected. Core
logic (Rx_Debounce.v, HIGH branch — the LOW branch mirrors it):
At ~10 MHz, 16 clocks ≈ 1.6 µs. A genuine 40 kHz level lasts ~12.5 µs
(~125 clocks), so it passes untouched; sub-µs noise chatter (< 16 clocks) is filtered out. Schmitt
hysteresis (amplitude) and debounce (time) are complementary; the clean Rx_cc_out
is then latched by CC_Saver.
Interactive — glitch in → debounce → clean out (click to expand)
Top = input from the Schmitt trigger (a slow real level with injected
glitches); bottom = debounced output.
A transition is accepted only after the input holds for N clocks, so glitches narrower than N
vanish. Raise the glitch width above N to watch one leak through.
How the Canceller Works — and an Assessment | 消除器如何運作——以及評估
What the code does. Clutter here means the direct Tx-to-Rx leakage, modelled as a scaled and
delayed copy of the transmit reference. On a KEY3 press (falling edge of CC, synchronised to
clk10MHz4) clutter_cancel.v freezes two calibration values: an amplitude
ratio from the fixed-point divider div_fx, Tx_Q_Amp / Rx_Amp in Q8.16 built from
the two peak-to-peak detectors (amplitude_calc), and a phase offset from phase_detector, which counts 10 MHz samples
between the zero crossings of the transmit reference Tx_Q and the received sum
Rx_IQ_Sum_B. The receive signal is then multiplied by that ratio, the reference is pushed through
delay_line (Variable_Delay_Line) by that many samples, and Rx_cc (Q24.8) is the
subtraction of the two. The small residual is Schmitt-triggered with the former 128× gain folded in
(smittch_trigger, SH=7, threshold 200 LSB) and debounced
(Rx_Debounce) into the 1-bit edge Rx_cc_Amped_DB that can drive the PI injection
instead of the image-reject path. Two guards sit around it: Clutter_weak (ratio > 5.0, i.e.
> 327680 in Q8.16) flags a ratio above 5 as “too little
clutter to bother”, and CC_Saver watches the duty cycle of the result and falls back to the
image-reject path when it looks broken. (Until 2026-09-04 this ran in IEEE-754: DIV/MULT/
Subtractor IP on a 400 MHz PLL clock, an asynchronous always @(negedge CC) latch and the
×128 Amplifier_For_CC_Resolve.)
Judgement. It is a minimal single-tap canceller, calibrated once by hand, and that is both its
merit and its limit.
評估。這是一個最簡的單抽頭消除器,靠人手一次校正——這既是它的優點,也是它的極限。
It scales the whole received signal rather than the clutter estimate, so the wanted
echo is rescaled too and cancellation is exact only when leakage dominates.
它縮放的是整個接收訊號而不是雜波估計,所以想要的回波也一起被縮放;只有在漏波佔主導時消除才精確。
Both calibration values are derived from the total received signal, so the target itself
biases the ratio and the phase.
兩個校正值都由總接收訊號推得,因此目標本身會使比值與相位產生偏差。
Phase alignment is one 10 MHz sample, 1.44°, with no fractional step, and it is a
real-valued delay rather than a complex I/Q weight, so only one leakage path can be
nulled.
The calibration is static. Leakage drifts with temperature and gain, so the residual grows
until someone presses KEY3 again.
校正是靜態的。漏波會隨溫度與增益漂移,殘差因此越來越大,直到有人再按一次 KEY3。
Subtracting two nearly equal signals and then amplifying 128× before slicing to one bit
magnifies noise. The source comments admit the glitches and the instability this injects, and
CC_Saver exists as the band-aid.
As a proof of concept that leakage can be subtracted before the phase-lock loop, it works. For a robust
instrument the natural next steps are to scale the reference rather than the receive path, replace the one-shot
ratio and delay with an adaptive complex weight such as LMS on I/Q, and synchronise the KEY3 capture.
7. UART Serial Data Output — UART_A | UART 序列資料輸出 — UART_A(click to collapse)
Theory ref — Yu & Horng eq. (5):
The readout path. Yu's radar output is the delay value, x̂(t) = −½·c·[d(t) − a] — "the
delay value itself is the output." UART_A serializes that demodulated quantity
(PI output / amplitude) to the host PC. Implementation glue; both designs are FPGA-based.
[Yu guide]
Instance U6_COPY is the only UART in _1Mhz_beta010.v.
It is transmit-only (no rx pin). It alternates
between two 40-bit packets at 2.5 kHz, sending either PI
controller data or peak amplitude, identified by a 1-byte suffix.
2.5 kHz — triggers one packet transmission per cycle (was port clk_1250) | 2.5 kHz——每個週期觸發一次封包傳送(原埠名 clk_1250)
clk1d25kHz
clk1d25kHz
1.25 kHz — selects packet type (PI vs amplitude) inside UART_A (was port clk_625) | 1.25 kHz——在 UART_A 內選擇封包類型(PI vs 振幅)(原埠名 clk_625)
data[31:0]
data_switch
32-bit payload, alternates between data_all and amplitude_info | 32 位元酬載,在 data_all 與 amplitude_info 之間輪替
tx
FPGA pin
Serial bitstream output — no rx | 序列位元流輸出——沒有 rx
Two Data Sources — Switched by clk1d25kHz | 兩個資料來源——由 clk1d25kHz 切換
clk1d25kHz
data_switch
Source signal | 來源訊號
Meaning | 意義
Packet ID byte | 封包識別位元組
1(high half) | (高半週)
data_all = {data[23:0], 8'b0}
PI_antiWindup.data (24-bit signed)
PI controller output — phase error / correction value | PI 控制器輸出——相位誤差/修正值
0xC5
0(low half) | (低半週)
amplitude_info = {Rx_peak, 16'b0}
Rx_peak_cc or Rx_peak_ir (16-bit, selected by amplitude_select_cc_ir)
Peak amplitude of CC or IR receive path | CC 或 IR 接收路徑的峰值振幅
0xBA
40-bit Packet Format | 40 位元封包格式
Bits | 位元
Field | 欄位
PI / phase packet | PI/相位封包
Amplitude packet | 振幅封包
[39:32]
start_iden_code (prefix) | start_iden_code(前綴)
0xD4
0xEE
[31:8]
Payload (24 bits) | 酬載(24 位元)
data_switch[31:8] = PI[23:0]
data_switch[31:8] = {Rx_peak[15:0], 8'b0}
[7:0]
Type marker (postfix) | 類型標記(後綴)
0xC5
0xBA
Note:
40 bits = 5 bytes = 1 prefix (start_iden_code) + 3 payload + 1 marker.
Only data[31:8] (the top 24 bits) is sent; the low byte data[7:0] is dropped to make room.
tx_16bits U3 slices the 40 bits into five 8-bit bytes, which my_uart_tx U1 sends at
230400 baud. test_phase U2 and test_A U4 build the two variants
(prefix = start_iden_code, marker = low byte of .in); clk1d25kHz
selects between them (high = PI/phase, low = amplitude).
clken — the baud tick (advances one bit) | clken——鮑率節拍(前進一位元)
clken is the 230400-baud enable from speed_select
(txclk_en). The state machine runs on the 50 MHz clk_50M, but every step is
gated by if (clken), so it only moves forward on a baud tick:
STATE_DATA: if (clken) begin
tx <= data[bitpos]; // drive the current bit
bitpos <= (bitpos==7) ? ... : bitpos+1; // step to the next bit
end
Each clken pulse drives the next bit onto tx and increments
bitpos — that is what "advances one bit" means. clken is high
only 1 of every ~217clk_50M cycles (50 MHz / 230400 ≈ 217),
so each bit is held on the line for exactly one bit period:
trigger — the byte-done handshake (steps bytes) | trigger——位元組完成交握(步進位元組)
trigger is an output: my_uart_tx pulses it in the STOP state when a byte
finishes, then clears it in IDLE. It feeds tx_16bits.trigger_16, telling the serializer to
present the next of the five bytes in the 40-bit packet. So clken paces bits
within a byte; trigger steps between bytes.
DE25 2×20 GPIO Expansion Header — Where tx Comes Out | DE25 2×20 GPIO 擴充排針——tx 從哪裡出來
On the DE25 port (branch de25), the UART tx bitstream leaves the board
on the 40-pin GPIO expansion header. The header has 36 user pins
connected directly to the Agilex 5 SoC FPGA, plus DC +5 V (VCC5),
DC +3.3 V (VCC3P3), and two GND pins
(DE25 User Manual §3.8.3, Table 3-15/3-16, Figure 3-23).
This design uses four of them (de25_top.v:71–75,
_1Mhz_beta010.qsf:232–241):
Each of the 36 data pins passes through a series resistor and two clamp diodes
on its way to header JP1. The diodes short any voltage excursion above
VCC3P3 + one diode drop, or below GND − one diode drop, protecting the FPGA
I/O bank from over/under-voltage. This is why every GPIO_D assignment in the qsf
uses 3.3-V LVCMOS — the bank and the clamps are referenced to
VCC3P3, so never drive the header from 5 V logic.
36 個資料腳的每一個在通往排針 JP1 的路上都經過一個串聯電阻與兩個箝位二極體。二極體把任何高於 VCC3P3 +一個二極體壓降、或低於 GND −一個二極體壓降的電壓短路,保護 FPGA I/O bank 免受過壓/欠壓。這就是為什麼 qsf 中每個 GPIO_D 指定都用 3.3 V LVCMOS——bank 與箝位都以 VCC3P3 為參考,所以絕不要用 5 V 邏輯驅動排針。
Redrawn from DE25 User Manual Figure 3-23 — one series resistor + two clamp diodes per data pin.
The protection R adds source impedance: fine at 230400 baud, but it slows edges if you hang a
long unterminated cable on the pin.
Image rejection needs the I and Q paths equal in amplitude and exactly 90° apart; any
gain mismatch between the two DAC channels (and their analog buffers/filters) leaks a residual image
sideband that caps the achievable rejection. The correction is a small scalar trim of ≈2.2% on one
channel. How that trim is applied — scaling one channel up vs. the other
down — is functionally equivalent for balance but not for overflow.
(Related: the 90° split itself is covered in §4 Hilbert Transform & Quadrature.)
Two ways to apply the same 1.022 balance | 施加同一個 1.022 平衡的兩種方式
Resolved 2026-09-04. The design now uses the fixed-point scheme on the Q channel: v_L * 64125 >> 16 (0.978470, −0.0004 % from 1/1.022), rounded. The floating-point ×1.022 on I described below is history; it could wrap the 14-bit DAC code at full scale once the KEY2 gain moved to the source.
Current code: balance by ÷1.022 (gain < 1), not ×1.022
The present _1Mhz_beta010.v no longer boosts the I path by ×1.022. Instead the delayed
(I/DAC_DA) channel runs at unity (MULT constant
32'h3f800000 = ×1.0), and the other channel (DAC_DB) is scaled
down by 1/1.022 ≈ 0.9785. The relative balance is identical, but because
the only active multiplier is < 1 no sample can ever exceed the 14-bit full scale —
precisely the overflow/fold failure the ×1.5 plots in §4 illustrate.
K = round(2¹⁶ / 1.022) = 64125, so the realised gain is 64125/65536 = 0.978470
(≈ −0.0004% off the ideal 1/1.022). The signed sample v is multiplied, arithmetic-shifted
back to 14 bits, then re-encoded offset-binary for the DAC (v_Tx_bal).
K = round(2¹⁶ / 1.022) = 64125,所以實現的增益是 64125/65536 = 0.978470(與理想的 1/1.022 相差約 −0.0004%)。有號取樣 v 先相乘、算術右移回 14 位元,再重新編碼成偏移二進位送 DAC(v_Tx_bal)。
Trade-off: Scheme B's >>>16 is a floor-truncation (≤ 1 LSB downward
bias) versus the FP path's rounding — negligible for image balance, and worth it to guarantee no fold on
peaks. Source: _1Mhz_beta010.v:191–196 (the Q16 balance), :235 (I-path unity
MULT), :246 (Q feed). The Rx side keeps an analogous balance trim
32'h3f83d70a ≈ ×1.03 in the floating-point domain.
9. How the 40 kHz Sine Is Generated | 40 kHz 弦波如何產生
There is no sine lookup table and no NCO. The transmit sine v is a
self-sustained limit cycle produced by a relay-feedback (describing-function)
oscillator in relay_feedback2.v: a high-Q digital resonator whose own output is
fed back through a relay (sign comparator). This is the signal-level / waveform view; the
transfer-function derivation lives in §2 Relay Feedback Oscillator.
這裡沒有弦波查找表、也沒有 NCO。發射弦波 v 是 relay_feedback2.v 中繼電回授(描述函數)振盪器產生的自持極限環:一個高 Q 數位諧振器,其自身輸出經繼電器(符號比較器)回授。這是訊號位準/波形的視角;轉移函數推導在 §2 繼電回授振盪器。
Relay-Feedback Loop | 繼電回授迴路
Stage | 階段
Code | 程式碼
Role | 角色
Relay
res_in <= ±256·sign(f1024) + y_in (:45)
takes only the sign of the resonator output → a ±256 square wave (the nonlinearity that keeps the loop alive) | 只取諧振器輸出的符號 → ±256 方波(維持迴路運作的非線性元件)
pole at 1023/1024 (just inside the unit circle) → very high Q, narrow band at 40 kHz; passes the square's fundamental, rejects harmonics | 極點在 1023/1024(單位圓內側一點點)→ 非常高的 Q、40 kHz 窄帶;讓方波的基頻通過、排除諧波
Integrator G(z) + error
g1024 += temp; e = res_in − g1024/1024 (:44, :21)
second loop state that sets the exact centre frequency and closes the feedback | 第二個迴路狀態,設定精確的中心頻率並閉合回授
Why it oscillates — and why it's a sine:Frequency: the loop phase returns to 0° only at the resonator's centre, so it locks to
40 kHz. Amplitude: the relay's describing-function gain drops as amplitude
grows, so the limit cycle self-limits at a stable level (driving f1024 toward its ±2²³ rail).
Shape: the relay emits a square wave, but the high-Q bandpass keeps only the
40 kHz fundamental, so the state f1024 traces a sine.
The small ±256 relay square wave (blue), in phase with the sign of the output, drives the high-Q
resonator; the resonator builds it up into a large, clean 40 kHz sine (amber). Square in → sine out is
exactly what the narrow bandpass does.
Kick-start: a zero initial state would stay at zero forever, so g1024 is
seeded non-zero ({4'b0001,24'b0}, relay_feedback2.v:41) to perturb the loop and
let the oscillation build. Output taps: v = f1024[23:10] (14-bit, to the Tx path),
u = f1024[23:11] (13-bit, half amplitude). f1024/e update on
negedge clk10MHz and g1024/res_in on posedge —
the two half-loops interleave to form the resonator at 10 MHz.
10. Integrator Gain, F(z), and Clock Scaling | 積分器增益、F(z) 與時脈縮放(click to collapse)
These notes capture two related questions about the relay-feedback oscillator in
relay_feedback2.v, the block that generates the 40 kHz carrier for the radar
IQ pipeline. The first question asks what changes when the integrator gain — the
0.5 × 332/1024 coefficient — is increased. The second asks whether the lowpass
filter F(z) inside the loop is a bandpass. The two answers share one core idea:
the bandpass/resonator behaviour belongs to the closed loop, not to any single block,
and the integrator gain is the knob that sets its center frequency.
10.1 The Integrator Gain and What It Controls | 積分器增益控制什麼
10.1.1 Where the expression lives in the RTL | 這個式子在 RTL 的哪裡
The transfer function in question,
$G(z) = \dfrac{0.5 \times (332/1024)}{1 - z^{-1}}$,
is the integrator accumulated in register g1024 at every 10 MHz clock edge.
The gain is not a multiplier — it is a shift-add sum:
wire signed [23:0] temp;
assign temp = {{3{f1024[23]}},f1024[23:3]}+{{5{f1024[23]}},f1024[23:5]}+{{8{f1024[23]}},f1024[23:8]}+{{9{f1024[23]}},f1024[23:9]};
always @(posedge clk10MHz) begin
g1024 <= g1024+{{4{temp[23]}},temp}; // G(z)=0.5*332/(1024-1024z^-1)
res_in <= {{9{f1024[23]}},1'b1,8'b0}+y_in;
end
The integrator output is subtracted from the relay drive at line 26, and the resulting error
feeds the lowpass F(z) at line 30:
積分器輸出在第 26 行從繼電器驅動訊號中被減掉,得到的誤差在第 30 行送進低通 F(z):
wire signed [17:0] e;
assign e = res_in-g1024[27:10];
...
always @(negedge clk10MHz) begin
u_past <= f1024[23:11]; // u_past is cut in half
f1024 <= f1024-{{10{f1024[23]}},f1024[23:10]}+{{6{e[17]}},e}+{{5{e_1[17]}},e_1,1'b0}+{{6{e_2[17]}},e_2}; // F(z)=(z^-0.5+z^-1.5)/(1024-1023z^-1)
e_1 <= e;
e_2 <= e_1;
end
10.1.2 The loop is a lightly damped resonator | 迴路是一個低阻尼諧振器
An integrator wrapped in feedback around a first-order lag makes a second-order, lightly
damped resonator. In a continuous-time approximation, the closed-loop linear block from the
relay output to f1024 is:
$$H(s) \;\approx\; \frac{F_{dc}\,s}{\tau s^{2} + s + 2g},
\qquad
\omega_0 \;=\; \sqrt{\frac{2g}{\tau}}$$
Closed-loop resonator & center frequency | 閉迴路諧振器與中心頻率
where $g$ is the integrator gain per unit time (proportional to the 332 coefficient) and
$\tau$ is the time constant of $F(z)$ (pole at $1023/1024$, about $102\ \mu\text{s}$ at
10 MHz). The relay — res_in = ±256·sign(f1024) plus the injection
u_inj — locks the oscillation onto this resonance; that is where the loop phase
condition is met, and that is what sets the 40 kHz.
Loop topology of relay_feedback2.v: the relay drive $\pm 256$ plus injection
$u_{inj}$ forms res_in; the integrator output $G(z)\,f_{1024}$ is subtracted at
the summing node ($e = \texttt{res\_in} - \texttt{g1024}[27{:}10]$); $F(z)$ filters $e$ into
$f_{1024}$, whose sign drives the relay again. The linear part seen by the relay is
$H = F/(1+FG)$.
INTUITION — why integrator feedback makes a resonator | 直覺——為什麼積分器回授會變成諧振器
Seen alone, $F(z)$ is just a first-order lowpass (an energy-storage element) and $G(z)$ just
an integrator. But subtracting the integrator output at $F$'s input adds a restoring force:
the longer $f_{1024}$ stays away from zero, the more $g_{1024}$ accumulates and the harder it
pulls back — exactly the spring-mass structure, two first-order stages cascading into a
second-order oscillation cavity. The damping is set by $F$'s leakage (how far its pole sits
from $z=1$) and is very small, so the cavity is a high-Q resonator. The relay acts like a
clock's escapement, replenishing energy every half cycle so the oscillation never decays.
10.1.3 Four effects of increasing the integrator gain | 提高積分器增益的四個影響
(a) Oscillation frequency rises as √gain — the tuning knob | 振盪頻率以 √增益 上升——調頻旋鈕
Since $\omega_0 = \sqrt{2g/\tau}$, doubling the 332 coefficient raises the frequency by
$\sqrt{2} \approx 1.41\times$. To retune from 40 kHz to a target $f$, scale the
coefficient by $(f/40\,\text{kHz})^2$. This coefficient is the primary frequency-tuning knob
of the oscillator.
The damping ratio $\zeta = 1/(2\omega_0\tau)$ shrinks as $\omega_0$ rises. Practically:
better rejection of harmonics and injection noise, but a slower-settling startup envelope
and a more sluggish response to phase steps from u_inj.
(c) Fundamental amplitude roughly unchanged | 基頻振幅大致不變
At resonance the $-\tau\omega_0^2$ and $+2g$ terms cancel, leaving
$|H(j\omega_0)| = F$'s passband gain, independent of the integrator coefficient. So the
describing-function amplitude of u/v is, to first order,
unchanged.
The integrator's own swing at the oscillation frequency is
$|G(j\omega_0)| \propto g/\omega_0 \propto \sqrt{g}$, so a larger coefficient makes
g1024 (and temp) swing wider.
The header comment of relay_feedback2.v records that overflow already forced a
register-widening once: “the overflow happens for f1024 (u) and g1024, so this version …
gives one more bit to f1024 and e and e_1.” If you raise the gain much, re-check the
28-bit headroom of g1024 and re-derive the g1024[27:10] slice used
for e.
relay_feedback2.v 檔頭註解記載曾因溢位而把暫存器加寬過一次:
“the overflow happens for f1024 (u) and g1024, so this version … gives one more bit to
f1024 and e and e_1.” 若要大幅提高增益,請重新檢查 g1024 的 28-bit 餘裕,
並重新推導 e 所用的 g1024[27:10] 切片。
Effect of increasing gain | 提高增益的影響
English | 英文說明
Chinese | 中文說明
Frequency | 頻率
Rises as $\propto\sqrt{g}$; retune factor $(f_{new}/f_{old})^2$ on the coefficient
The gain is realized as the shift-add sum at line 40:
$2^{-3}+2^{-5}+2^{-8}+2^{-9} = 166/1024$ — not a real multiplier. Changing the gain means
picking a new sum of powers of two — e.g. $0.5\times 512/1024 = 0.25$ is just
f1024[23:2]. Values that are not a short sum of powers of two cost extra adder
terms.
No — F(z) alone is a first-order lowpass; the bandpass character belongs to the
closed loop.
不是——$F(z)$ 本身是一階低通;帶通特性屬於整個閉迴路。
10.2.1 F(z) by itself | 單獨看 F(z)
From the f1024 update (line 30):
由 f1024 的更新式(第 30 行):
The denominator pole sits at $z = 1023/1024$ — a leaky integrator. At 10 MHz sampling
this is a cutoff frequency around
$\frac{1}{1024}\cdot\frac{10\,\text{MHz}}{2\pi} \approx 1.55\ \text{kHz}$, rolling off at
$-20$ dB/dec above it. The 40 kHz oscillation therefore sits outside F's
own passband, in its integrator-like region.
The numerator $e + 2e_1 + e_2 = (1+z^{-1})^2$ places zeros at Nyquist (5 MHz) — it only
adds extra high-frequency attenuation and provides the half-sample delay ($z^{-0.5}$
equivalent) noted in the source comment. It does not create a bandpass.
DC in, DC out: viewed alone, $F(z)$ is a first-order lowpass with no ability to reject low
frequencies.
DC 進來、DC 出去:單獨看,$F(z)$ 就是一階低通,沒有抑制低頻的能力。
10.2.2 Where the bandpass comes from | 帶通從哪裡來
The bandpass appears in the closed loop $H = F/(1+FG)$. The integrator feedback has
near-infinite gain at low frequency, which inflates the denominator there — equivalent to a
zero at DC in the overall transfer (DC is completely removed). The high-frequency end rolls
off through F's pole. Squeezed between the two:
Zero gain at DC, rolloff at high frequency, peak at $\omega_0$ — a second-order
bandpass/resonator. This is also why the integrator gain (the 332 coefficient) moves the
center frequency: it does not reshape $F$, it moves the resonance of the whole
loop.
DC 增益為零、高頻滾降、峰值在 $\omega_0$——一個二階帶通/諧振器。這也是為什麼積分器增益
(係數 332)動的是中心頻率:它不是改 $F$ 的形狀,而是移動整個迴路的諧振點。
Left: $F(z)$ alone — first-order lowpass, cutoff frequency $\approx 1.55$ kHz, so
40 kHz lies on its $-20$ dB/dec slope. Right: the closed loop $H = F/(1+FG)$ —
integrator feedback kills DC, F's pole kills high frequency, leaving a sharp resonance at
$\omega_0 = \sqrt{2g/\tau}$ (40 kHz).
$F(z)$ is a lowpass; $H = F/(1+FG)$ is the bandpass. The relay locks onto the peak of $H$,
and the integrator gain slides that peak along the frequency axis.
$F$ is the energy-storage element (a capacitor, or a mass); $G$'s integral feedback plays
the restoring force (the spring); the two first-order stages cascade into a second-order
oscillation cavity, and the relay replenishes the losses so the cavity keeps ringing at its
own center frequency. Asking whether $F(z)$ is a bandpass is like asking whether a spring is
an oscillator — a single element is not, the combination is. DC passes straight through $F$;
it is the integrator feedback's infinite gain at DC that removes it completely and carves
out the bandpass shape.
$F$ 是「儲能元件」(像電容/質量),$G$ 的積分回授扮演「回復力」(像彈簧),
兩個一階級聯成一個二階振盪腔;繼電器則負責補償損耗,讓這個腔在自己的中心頻率上持續振盪。
所以問「$F(z)$ 是不是帶通」就像問「彈簧是不是振盪器」——單一元件不是,組合起來才是。
DC 進來、DC 出去,$F(z)$ 攔不住低頻;是積分器回授在 DC 的無限增益把它完全消掉,
才夾出帶通的形狀。
10.3 Scaling the Clock Period T | 縮放時脈週期 T
10.3.1 Clock scaling moves frequency linearly | 時脈縮放使頻率線性移動
Shortening the clock period — i.e. raising clk10MHz — scales the oscillation
frequency linearly: $f_{osc} \propto f_{clk}$. Double the clock and the 40 kHz
oscillation becomes 80 kHz.
Why linear, rather than the $\sqrt{\cdot}$ law of the gain knob? All loop coefficients are
dimensionless per-sample numbers, so a discrete-time design has no absolute notion of Hz —
every frequency in it is a fixed fraction of the sample rate (a normalized frequency). In
the continuous-time equivalents both parameters carry $T$: the integrator gain is
$g = k_i/T$ and the lowpass time constant is $\tau = 1024\,T$, hence:
$$\omega_0=\sqrt{\frac{2g}{\tau}}
=\frac{1}{T}\sqrt{\frac{2k_i}{1024}}
\;\propto\; f_{clk}$$
Center frequency scales with the clock | 中心頻率隨時脈線性縮放
The key contrast with raising the gain: clock scaling changes nothing else. In the
damping ratio, $T$ cancels out entirely:
與提高增益的關鍵對比:時脈縮放不改變其他任何東西。在阻尼比裡,$T$ 完全消掉:
$$\zeta=\frac{1}{2\omega_0\tau}
=\frac{1}{2\cdot 1024}\sqrt{\frac{1024}{2k_i}}$$
Damping ratio is independent of T | 阻尼比與 T 無關
Q, the startup settling time measured in oscillation cycles, the amplitude, and the
fixed-point swings in f1024/g1024 are all exactly preserved — the
waveform is the same waveform, played faster. There is no new overflow risk. This is the
"clean" retuning knob, whereas the 332 coefficient moves the frequency as $\sqrt{g}$
and drags Q and the overflow margin along with it.
The clock is the coarse band knob — everything scales together. The integrator coefficient
is the fine tuning within the band — with Q and headroom side effects.
10.3.2 Three cases of "decreasing T only" | 「只縮小 T」的三種情況
Case 1 — scale the whole clock tree | 情況一:整棵時脈樹一起縮放
Feed clock_generator.v with, say, 100 MHz instead of 50 MHz. The
digital system stays internally consistent: the oscillator runs at 80 kHz, all filter
corners double, and the 62.5-cycle delay is still exactly $T/4$ — the samples-per-period
count stays 250 because the 10 MHz / 40 kHz ratio is preserved. The
demodulator and PI ratios all survive. Breakage happens only at the boundaries: the UART's
230400 baud rate becomes 460800 (the PC side stops decoding); the PWM and ADC/DAC rates
double (the THDB-ADA has headroom, so that part is fine); and the analog world — the
physical resonance, matching, and filters the 40 kHz was chosen for — does not move,
so the retuned digital system now drives a detuned plant.
Case 2 — shrink T of only one domain | 情況二:只縮小單一時脈域的 T
Speed up only the clk10MHz divider and the system becomes internally
inconsistent. The oscillator and the $T/4$ delay move together (both live on
clk10MHz, so they stay coherent at 80 kHz), but the demodulator still
samples at 250 kHz and the PI controller at 50 kHz — every designed-in
cross-clock-domain ratio breaks: the 250 kHz / 40 kHz = 6.25 samples per
carrier period, the PI loop gain per second, the 1.25 kHz UART framing. The result is
not a cleanly shifted system but silent garbage.
Case 2 is not a retuning strategy — it is just a bug. Don't do it.
情況二不是調頻策略——它就只是一個 bug。不要這樣做。
Case 3 — shrink T but keep 40 kHz | 情況三:縮小 T 但保持 40 kHz
If the target frequency is pinned, the coefficients must absorb the change. From
$\omega_0 = \sqrt{2k_i/1024}\,/\,T$: keeping $\omega_0$ fixed when $T$ halves requires
$k_i \times 1/4$ (332 → 83) — the integrator gain scales as $T^2$. Keeping $\tau$
fixed requires $F$'s pole to move from $1023/1024$ to $2047/2048$ — poles crowd toward
$z=1$ as $1 - T/\tau$. This is the classic oversampling problem: the same physical dynamics
need tiny coefficients and poles jammed against the unit circle, so coefficient quantization
and accumulator rounding bite harder, and wider registers are needed (the
1024 shift-divides become 2048; small signals fall below the LSB of the shift-add terms
sooner). Nothing about a faster clock is free once the target frequency is pinned.
Works, but oversampling costs precision | 可行,但過取樣以精度為代價
10.4 Which Clock Sets the Displacement Resolution? | 哪個時脈決定位移解析度?
The design runs two fast clocks. The ADC, DAC, relay oscillator, Hilbert pair and the Rx
Schmitt/debounce chain run at clk10MHz; the tunable injection delay line
(delay_line2) and the PWM run at clk50MHz. A natural question is
which of the two sets the smallest displacement the radar can resolve, and whether either
sets its sensitivity. The short answer: the 10 MHz clock
sets the resolution, the 50 MHz clock only sets how finely the loop can respond,
and neither sets the sensitivity floor.
10.4.1 Where each clock touches the Rx edge | 每個時脈在哪裡碰到 Rx 邊緣
Displacement is read out as the PI delay word data, which tracks the arrival phase
of the received 40 kHz edge. Follow that edge through _1Mhz_beta010.v: it is
timed by three stages before it reaches the 50 MHz delay line, and all three run
at 10 MHz.
位移是以 PI 的延遲字 data 讀出,它追蹤接收 40 kHz 邊緣的到達相位。沿著
_1Mhz_beta010.v 追這個邊緣:它在到達 50 MHz 延遲線之前已經過三級定時,
而這三級全都跑在 10 MHz。
smittch_trigger (fixed point since 2026-09-04; was smittch_trigger_FP) on clk10MHz1 turns the Rx waveform into a 1-bit edge. | smittch_trigger_FP 在 clk10MHz1 上把 ADC 波形變成 1-bit 邊緣(405–417 行)。
Rx_Debounce on clk10MHz1 shifts it by whole 10 MHz cycles (lines 406–420). | Rx_Debounce 在 clk10MHz1 上把它推移整數個 10 MHz 週期(406–420 行)。
uin2/uin3 re-register it on clk10MHz (line 75). | uin2/uin3 在 clk10MHz 上再暫存一次(75 行)。
delay_line2 on clk50MHz then adds the PI-controlled delay d in 20 ns steps, and its 4-tap smoother turns the edge into a 5-level ramp 80 ns long. | 接著 delay_line2 在 clk50MHz 上以 20 ns 為步進加上 PI 控制的延遲 d,其 4-tap 平滑器把邊緣變成長 80 ns 的 5 階斜坡。
relay_feedback2 samples that ramp at 10 MHz; the sampled level encodes where the edge fell inside the 100 ns bin, so the actuator has 20 ns effective resolution. | relay_feedback2 以 10 MHz 取樣這個斜坡;取到的位準包含了邊緣落在 100 ns 格子內的位置,所以致動器具有 20 ns 的有效解析度。
The Rx edge is quantised to the 10 MHz grid by the Schmitt trigger, debounce and
uin2/uin3 registers before the 50 MHz delay line ever sees it.
The delay line can only add a finer constant offset; it cannot restore timing information the
10 MHz stages have discarded.
Rx 邊緣在 50 MHz 延遲線看到它之前,就已被施密特觸發器、去抖以及
uin2/uin3 暫存器量化到 10 MHz 格點。延遲線只能再加上更細的固定偏移,
無法找回 10 MHz 各級已經丟掉的時序資訊。
10.4.2 From clock bin to phase to displacement | 從時脈格子到相位、再到位移
The RF carrier is 5.8 GHz, so the speed of light enters through the RF wavelength, not
through time of flight of the 40 kHz tone. The Tx path emits an I/Q pair (the Hilbert
90° pair of §4) for single-sideband up-conversion, so the transmitted carrier sits at
$f_{RF} + 40$ kHz. After the receiver mixes with the $f_{RF}$ LO, the phase of the
40 kHz IF equals the round-trip RF phase. A clock bin $\Delta t$ on the IF edge therefore
maps to a displacement in three steps:
$$\Delta\varphi_{IF} = 360^\circ \cdot \Delta t \cdot f_{IF},
\qquad
\Delta\varphi_{IF} = 360^\circ \cdot f_{RF}\,\frac{2\Delta x}{c} = 360^\circ \cdot \frac{2\Delta x}{\lambda_{RF}},
\qquad
\Delta x = \frac{\Delta\varphi_{IF}}{360^\circ}\cdot\frac{\lambda_{RF}}{2}$$
IF edge time → IF phase → RF round-trip displacement | 中頻邊緣時間 → 中頻相位 → RF 往返位移
$$\lambda_{RF} = \frac{c}{5.8\ \text{GHz}} = 51.7\ \text{mm},
\qquad
\frac{\lambda_{RF}}{2} = 25.9\ \text{mm per } 360^\circ \text{ of IF phase}$$
Unambiguous range of the phase readout is one half-wavelength | 相位讀出的無模糊範圍是半個波長
Clock | 時脈
Time bin | 時間格子
IF phase at 40 kHz | 40 kHz 中頻相位
Displacement at 5.8 GHz (round trip) | 5.8 GHz 往返位移
≈ 21 µm (readout LSB only, not realised)(只是讀出 LSB,並未實現)
PI actuator step d; dead-zone width of the lock; the LSB of data. | PI 致動器步進 d;鎖定的死區寬度;data 的 LSB。
40 kHz envelope ToF | 40 kHz 包絡飛行時間
100 ns
—
≈ 15 m
Not used. This is why the system relies on carrier phase, not time of flight. | 未使用。這就是系統依賴載波相位而非飛行時間的原因。
So the finest displacement step the loop can actually respond to is about 103 µm.
The 20 ns LSB of data gives a readout granularity of 21 µm, but four
out of five of those codes carry no new information from the target.
WHY THE 100 ns BIN IS A DEAD ZONE, NOT NOISE | 為什麼 100 ns 格子是死區、而不是雜訊
The 40 kHz is exactly $10\ \text{MHz}/250$, and clk10MHz1 is PLL-locked to
the same crystal as clk10MHz. A static target's Rx edge therefore always lands at
the same position inside its 100 ns bin. The quantisation error is a fixed offset, not a
dithered random variable, so the 15.6 Hz low-pass on data cannot average it
away. A displacement smaller than one bin produces no change in data
unless receiver noise happens to push the edge across a bin boundary.
Neither fast clock sets the sensitivity floor, i.e. the smallest phase change that
rises above the noise. The oscillator state f1024 is a 24-bit accumulator, so its
phase is effectively continuous and not stuck on the 1.44° grid. The detectable change is
set instead by the noise budget of the slow signal processing:
Quantisation noise into the demodulator | 進入解調器的量化雜訊
ADC I/Q | ADC I/Q
14-bit @ 10 MHz
Receiver amplitude noise; sets where the Schmitt edge lands | 接收端振幅雜訊;決定施密特邊緣落點
Demodulator LPFs F1, F2 | 解調器低通 F1、F2
1 MHz, 250 kHz
In-loop noise bandwidth | 迴路內雜訊頻寬
PI update | PI 更新
50 kHz, $k_I = 3/2^8$, $k_P = 2/2^{16}$
Loop bandwidth | 迴路頻寬
Output LPF on data | data 的輸出低通
15.6 Hz
Averages hundreds of 40 kHz edges per output sample; this is what resolves below both clock steps when the input is dithered | 每個輸出樣本平均數百個 40 kHz 邊緣;當輸入有抖動時,這才是能解析到低於兩種時脈步進的原因
50 MHz crystal | 50 MHz 石英
—
All clocks and the 40 kHz itself share its drift and jitter; the long-term limit | 所有時脈與 40 kHz 本身共享其漂移與抖動;長期極限
KEY RESULT | 重點結論
Resolution (the smallest step the loop can respond to) is set by the
10 MHz clock: 100 ns, 1.44°, about 103 µm at 5.8 GHz.
Actuator fineness is set by the 50 MHz delay line: 20 ns, 0.29°,
about 21 µm. Sensitivity is set by word widths and the slow
filters, not by either clock. Raising the 50 MHz or 10 MHz clock alone does not
improve sensitivity; narrowing the output filter or adding ADC bits does.
1. Reach the 21 µm step. Move the Schmitt trigger, debounce and
uin2/uin3 registers onto clk50MHz, or feed the comparator
output straight into delay_line2. The edge is then quantised at 20 ns, and the
existing 4-tap smoother already lets the 10 MHz oscillator resolve that. The debounce count
threshold must be rescaled by 5 to keep the same time constant.
2. Go below the clock entirely. Estimate the IF phase from the 14-bit I/Q
samples ($\arctan(Q/I)$, or the FP phase_detector replaced by an I/Q product)
instead of from edge timing. That estimate is limited by ADC noise and averaging, not by any
clock, and it also removes the fixed dead zone described above.
10.5 Why the PI Delay Line Runs at 50 MHz While the Signal Path Runs at 10 MHz | 為什麼 PI 延遲線用 50 MHz,而訊號路徑用 10 MHz
A frequent question when reading _1Mhz_beta010.v: the relay oscillator, demodulator,
Hilbert pair and both image-reject delay lines run on clk10MHz, yet
PI_antiWindup and the delay line inside it take clk50MHz. Is that a
workaround for some timing problem, a real advantage, or historical accident? It is deliberate,
and the reason is the resolution of the actuator, not a timing fix. Section 10.4
looked at which clock limits the measurement; this section explains why the
control side was given the faster clock.
10.5.1 The PI controller itself is not a 50 MHz block | PI 控制器本身並不是 50 MHz 的區塊
Look at where clk50MHz is actually consumed. The integrator, the proportional sum
and the output low-pass in PI_antiWindup.v are clocked by the 50 kHz taps
clk50kHz_d2, clk50kHz_d3 and clk50kHz_d4 (lines 33, 41,
69). The 50 MHz input reaches only one thing: the tunable 1-bit delay line
delay_line2 U12 (line 48). In clock_generator.v the 50 MHz has one
more job, making those _d2/_d3/_d4 taps (lines 23–40). So the question is
really why does the injection delay line run at 50 MHz.
10.5.2 The actuator is a time delay, so the clock is its resolution | 致動器是一段時間延遲,所以時脈就是它的解析度
The PI output is not a voltage or a gain. Its low 17 bits become d, and
delay_line2 delays the 1-bit injection signal uin by exactly
d clock cycles through a 128K-word RAM circular buffer (write pointer
k, read pointer k−d). One clock period is therefore the smallest
phase step the loop can command at the 40 kHz carrier:
At 50 MHz the loop gets five times finer phase control for free: 50 MHz is the board
oscillator itself, so it costs no PLL, and a 1-bit RAM at 50 MHz is trivial. The output side
gains as well. The four-tap sum over out_d0..out_d3 at 50 MHz turns the hard
1-bit edge into a 5-level ramp (−2…+2) spread over 80 ns — the
“smoother transition of input injection” in the source comment. An abrupt injection
edge would put a glitch into the relay-feedback oscillator; the ramp does not.
10.5.3 The staggered 50 kHz taps: a pipeline built from clock skew | 錯開的 50 kHz 分接:用時脈偏斜搭出的管線
The second use of 50 MHz is subtler. clk50kHz_d2, _d3 and
_d4 are the same 50 kHz clock re-registered on alternating
negedge/posedge clk50MHz, so each tap lags the previous one by half a
50 MHz period, 10 ns. The integrator samples on _d2, the PI sum on
_d3, the output filter on _d4: every stage clocks after the one before
it has settled. It is a hand-built pipeline that uses deliberate clock skew instead of
clock-enable logic, and 50 MHz is simply the finest time base available for that stagger.
10.5.4 Why everything else stays at 10 MHz, and whether the crossing is safe | 為什麼其他模組維持 10 MHz,以及跨域是否安全
The rest of the design is the sample-rate domain. The DAC and ADC are clocked at 10 MHz, so
the relay oscillator, the demodulator, delay_62d5_10MHz and its
hilbert_90 replacement, and the 32-bit delay_line that trims the Tx Q
phase (Variable_Delay_Line, line 455) all operate on samples and naturally run at
the sample clock. The floating-point arithmetic runs on the 200 MHz PLL clock only so that a
multi-cycle IP core finishes inside one 10 MHz sample.
Is mixing the two domains safe? Yes, because clk10MHz is divided from
clk50MHz by divide_by_5_narrow in clock_generator.v, so the
two clocks are synchronous with a fixed phase relation. The 3-bit u_inj produced on
negedge clk50MHz and consumed by relay_feedback2 at 10 MHz is a
related-clock multicycle path, not a true asynchronous crossing, and the timing analyzer checks it
as such.
50 MHz is used exactly where time resolution is the product: the injection delay line
(20 ns = 0.29° steps, plus an 80 ns injection ramp) and the 10 ns stagger of
the PI stage clocks. 10 MHz is used where the data is: everything that handles DAC/ADC
samples. Neither choice is accidental, and the two domains are synchronous by construction.
THE PRICE, AND WHAT A REWRITE WOULD DO | 代價,以及重寫時會怎麼做
The cost is structural oddity rather than risk: several clocks, some negedge, and
phase-skewed copies of one 50 kHz clock that a newcomer must recognise as a pipeline. A
modern rewrite would run the whole controller on one clock with clock enables and keep only the
50 MHz delay line, since that is the one place where the extra 5× resolution buys
something real.
11. Verilog Code Trace | Verilog 程式碼追蹤(click to collapse)
Verilog Source Walkthrough · Cyclone V · 5CGXFC5C6F27C7
從 50 MHz 到天線:From 50 MHz to the antenna: _1Mhz_beta010.v程式碼追蹤Code Trace
This document traces the Verilog source of the 40 kHz delayed self-injection-locked (DSIL) radar line by line, following the actual signal flow — from clock generation all the way to DAC transmit, ADC receive, image rejection, clutter cancellation, and finally the UART/PWM outputs. Every stage comes with the source excerpt (with line numbers), a trace explanation, and the mapping from hardware to the two reference papers.
Top _1Mhz_beta010.v · 721 linesClock 50 MHz → 10 M / 2 M / 1 M / 250 k / 50 k / 1.25 k HzFP IEEE-754 single · 200/400 MHz PLLIF 40 kHz ultrasonic
§0.1如何閱讀本追蹤 · How to read
"Code Trace" is not a file-by-file API document — it follows one signal: you watch data move between registers, IP cores, and clock domains. Each stage uses a fixed three-part layout —
「Code Trace」不是逐檔案的 API 文件,而是跟著一個訊號跑:你會看到資料如何在暫存器、IP 核、時脈域之間流動。
每個階段的版面固定為三件套——
Source excerpt: quoted directly from _1Mhz_beta010.v or a submodule; highlighted rows = the key lines being traced, with real line numbers on the left.
This trace focuses on "how the code flows." For the theory derivations (Adler equation, IRR, circle-fit ranging) see DSIL_Radar_Study_Guide.html; for module-level specification tables (port lists, coefficients, packet formats) see dsil_studyguide.html. The three documents complement each other.
The parent of the FPGA implementation: Low-IF architecture, I/Q digitization, and how image/clutter cancellation map onto the FPGA. Block naming in this trace follows it. | FPGA 實現的母本:低中頻 (Low-IF) 架構、I/Q 數位化、影像消除與雜波消除在 FPGA 上的對應。本追蹤的方塊命名以此為準。
pdf/Highly Linear Phase-Canceling Self-Injection-Locked Ultrasonic Radar…pdf
IEEE journal paper | IEEE 期刊論文
Core theory: how phase-cancelling and delayed self-injection locking (DSIL) linearize the ultrasonic radar's phase response and measure respiration/heartbeat. Maps to the §2 oscillator and §4 injection loop. | 核心理論:相位消除 (phase-cancelling) 與 延遲+自我注入鎖定 (DSIL) 如何把超音波雷達的相位響應線性化、量測呼吸/心跳。對應 §2 振盪器與 §4 注入迴路。
Quick reference for each module's port list and numeric constants. | 各模組埠列與數值常數的速查。
◆ THEORY · 一句話背景One-line background
A DSIL radar injects its own transmitted signal, after a delay, back into the oscillator. The tiny phase change caused by the target (chest displacement) is amplified by the injection-locking loop into a measurable frequency/phase shift; I/Q Low-IF reception plus image rejection then extracts the single sideband, achieving highly linear contactless vital-sign detection. This FPGA is the all-digital realization of that theory.
The table below lists the submodules _1Mhz_beta010 instantiates directly (top-level instances), ordered by signal-chain position. Deeper IP cores (MULT/Adder/DIV…) are generated by the Quartus MegaWizard and explained in place at each stage.
下表是 _1Mhz_beta010 直接實例化的子模組(top-level instances),依訊號鏈順序排列。深層的 IP 核(MULT/Adder/DIV…)由 Quartus MegaWizard 產生,於各階段就地說明。
Fixed point since 2026-09-04. The code listings in this trace (with their line numbers) are the original floating-point RTL. On the de25 branch the whole Rx chain is fixed point and the IEEE-754 IP is deleted — see the notes in §5 and §6 and CLAUDE.md for the current structure. The tree and file map below are updated; the per-stage listings are kept as history.
Everything starts from clk50MHz. clock_generator U1 uses no PLL — it slices 50 MHz into the whole clock chain with dividers and counters. Understanding this tree is the prerequisite for every always @(posedge clkXXX) that follows.
50M ÷5 = 10M (divide_by_5_narrow, 40% duty, also emitting mask) → 10M ÷5 = 2M → a 3-bit counter yields 1M (bit0) and 250k (bit2) → 250k is re-aligned as 250k_d1 → ÷5 = 50k, then the _d2/_d3/_d4/_d5 edge-aligned delayed copies → 50k_d5 ÷5 = 10k → a 4-bit counter yields 2.5k / 1.25k / 625 Hz. Each _dN suffix is a same-frequency, one-edge-shifted copy used to establish explicit causal ordering between always blocks.
Demodulation must compute u(k)·u(k−60). The Data_mult multiplier needs extra pulses squeezed into the low half of clk10MHz to finish its shift-add; clk_Mult=(~mask)&clk50MHz (L11) exists precisely for that.
All IEEE-754 IP cores (MULT/Adder/DIV/Convert_*) run at 200/400 MHz, far faster than the data rate (≤10 MHz), so FP results settle well within one 10 MHz sample. clk10MHz1…6 are six slightly phase-shifted 10 MHz copies used to pipeline ADC sampling → FP conversion → registering cleanly and avoid metastability.
State register f1024 (24-bit) and integrator g1024 form a second-order resonator. e (L23) is the "sign-relay reference − integral" error fed back into f1024 (L29) to sustain the constant-amplitude 40 kHz oscillation. The injection port uinj is scaled into y_in at L36 and summed into res_in at L51 — this is the physical entry point of injection locking. Outputs u (13-bit, to the demodulator) and v (14-bit, to Tx) are both slices of f1024.
Adding uinj into the oscillator input is exactly the papers' injection locking: when the injected frequency is near the free-running frequency, the oscillator phase is pulled toward the injection phase, with the locking range described by the Adler equation. In DSIL the injection source is the radar's own transmission, reflected and delayed by the target, so tiny target displacement (breathing/heartbeat) → reflected-phase change → amplified by injection into a measurable phase shift. See DSIL guide §2-1-1 and the IEEE paper's phase-cancelling analysis.
Demodulates the 40 kHz u into the baseband phase signal x256. The method: "multiply by a delayed copy of itself (phase detection) + two-stage lowpass."
L44–47 delay u_0d5 by 12.5 cycles of 2 MHz through 13 registers, producing the quadrature copy u12. Data_mult computes v = u·u_delay (the phase-detecting multiply, on clk_Mult). Two IIR lowpass filters with negative taps — F1@1 MHz and F2@250 kHz — remove the 2× component, leaving x256 (22-bit, 250 kHz), proportional to instantaneous phase. This is the quantity the loop locks.
"Signal × its own 90°-delayed copy + lowpass" is classic digital phase detection: the DC term ∝ sin(Δφ). In DSIL this Δφ carries the target displacement; x256 then feeds the PI to form the phase-locked loop, holding the operating point near the paper's high-sensitivity null-point.
x256 minus the set-point gives the error; after PI integration it (a) drives the tunable delay line that generates the injection u_inj, and (b) is lowpassed into data for UART output.
x256 與設定點相減得誤差,PI 積分後一方面驅動 可調延遲線 產生注入 u_inj,一方面低通成 data 供 UART 輸出。
The error is computed at top-level L81–87 (x256 − set-point, the set-point scaled from r via shift_bit). The PI integrates at 50 kHz (L34, kI=3/2⁸) and adds the proportional term (L41, kp=2/2¹⁶) to get PI_out. The key is L49–50: PI_out is used directly as the delay amount d for delay_line2; the delayed uin becomes the 3-bit injection u_inj returned to the oscillator (§2). Meanwhile PI_out goes through the 15.6 Hz lowpass (L76) to become data for the UART.
誤差由 top 的 L81–87 算(x256 − 設定點,設定點由 r 經 shift_bit 左移縮放)。
PI 在 50 kHz 積分(L34, kI=3/2⁸)並加比例項(L41, kp=2/2¹⁶)得 PI_out。關鍵在 L49–50:PI_out 直接當成延遲量 d 餵給 delay_line2,
延遲後的 uin 變成 3-bit 注入 u_inj 回送振盪器(§2)。同時 PI_out 經 15.6 Hz 低通(L76)成 data 走 UART。
◆ THEORY · 延遲+自我注入鎖定Delay + self-injection locking (DSIL)
The "D" of DSIL is here: a 128K RAM ring buffer where read/write pointer difference d realizes an arbitrary tunable time delay, with d controlled live by the PI. This is the digital counterpart of the paper's DSIL architecture — the delay sets the injection phase and the PI loop automatically holds the optimum operating point for "highly linear phase cancellation." See the delay_line2 section of the module guide.
「D」就在這裡:用一塊 128K RAM 當環形緩衝,讀寫指標差 d 即可實現任意可調的時間延遲,而 d 由 PI 即時控制。
這正是論文 DSIL 架構的數位對應——延遲量決定注入相位,PI 迴路自動把系統維持在最佳工作點,達到「高線性相位消除」。詳見
模組導讀 的 delay_line2 章節。
§5注入來源多工 · uin3 CONTROL
The injection-locking "source" is selected by two panel switches: self-feedback (board verification) or the receive-path signal (real measurement).
Inject the image-rejected receive signal | 影像消除後的接收訊號注入
0
0
Rx_cc_Amped_DB
Inject the clutter-cancelled receive signal | 雜波消除後的接收訊號注入
Real measurements use the bottom two rows: the receive signal (processed by §8/§9, digitized by Schmitt + debounce) is injected back into the oscillator, closing the DSIL loop.
The oscillator output v must become an I/Q pair (90° apart) to drive quadrature transmit. The Q path gets its 62.5-cycle 10 MHz delay, plus floating-point amplitude correction for channel imbalance.
delay_62d5_10MHz (L186) delays v by 62.5 cycles for the quadrature v_62d5. To fix DAC channel amplitude imbalance, the Q-delay path is converted to FP → multiplied by 1.022 (32'h3f82d0e5) → converted back to integer nv_62d5 (L220–231). Finally L205–206 put corrected I and latency-aligned Q onto DAC_DA/DB. Note {~v[13],v[12:0]} is the standard signed → offset-binary DAC conversion.
One 40 kHz period = 25 µs; one 10 MHz cycle = 0.1 µs. 90° = ¼ period = 6.25 µs = 62.5 cycles of 10 MHz. The half-integer delay is realized inside delay_62d5 by averaging adjacent samples (×0.5) — see the FP version in §8. I/Q quadrature is the prerequisite of Hartley image rejection.
Dual ADCs sample I and Q simultaneously; both are converted to signed, then to floating point, with a 1.03× channel-balance correction on the Q channel.
ADC_DA→Q, ADC_DB→I (note the crossover, L321–322), with the same offset-binary inversion. Both are converted to FP immediately (L330/335); the Q path is multiplied by 1.03 (32'h3f83d70a) to correct receive I/Q gain imbalance (L340), and finally latched into the Rx_I/Rx_Q FP registers on clk10MHz6, handing off to §8.
A Low-IF receiver picks up both the "target" and its "image." Hartley's method: delay Q by 90° and add it to I — the image cancels, the target reinforces.
低中頻接收會同時收到「目標」與「鏡像」訊號。Hartley 法:把 Q 路延遲 90° 後與 I 路相加,鏡像相消、目標相長。
▸ TRACE · 90° 延遲怎麼做出半週期How the 90° delay makes a half cycle
Integer part: a 62-stage register shift (d0…d61→data_out). Half-cycle part: L19/25 add "current + previous" then ×0.5 (32'h3f000000) — linear interpolation of the midpoint — together exactly 62.5 samples. The delayed Rx_Q_delay and aligned Rx_I_ctrl are summed in the 400 MHz Adder (L385), and the result latched into Rx (L400) — the image-rejected single-sideband receive signal.
In a Low-IF architecture the wanted signal and its image sit at ±f_IF. Phase-shifting Q by 90° and adding to I makes the image anti-phase (cancels) and the target in-phase (reinforces), raising the image-rejection ratio (IRR). Channel gain imbalance (§7's 1.03, §6's 1.022 trims) caps the IRR — which is why the code spends so much effort on FP amplitude fine-tuning. Theory: DSIL guide §2-1-2 (Hartley / IRR).
The static background (clutter) = reflections of the transmitted signal. Method: measure the amplitude ratio of Rx to Tx_Q, estimate the phase difference, align and scale Tx_Q, then subtract it from Rx — leaving only the moving target.
Four steps: ① amplitude_calculation_FP computes |Rx| and |Tx_Q| (L439/440); ② DIV yields the ratio k=|Tx_Q|/|Rx| (L534); ③ phase_detector counts the zero-crossing offset phase_diff between Tx_Q and Rx (L609) and delay_line delays Tx_Q into phase (L610); ④ the Subtractor computes Rx_cc = Rx·k − Tx_Q_delay (L612). The background clutter (= reflected transmission) is precisely cancelled and the moving target emerges.
Top-level comments at L302–303 state plainly: "the clutter-cancel function causes many glitches, and injection can destabilize frequency or amplitude." So §9's subtraction result is used for measurement, while re-injecting it (§5's Rx_cc_Amped_DB path) should be paired with §11's automatic switching for safety. Image rejection (§8) is marked stable and usable.
This is exactly the "Phase-Cancelling" of the IEEE paper's title: subtract the known transmit component (amplitude and phase aligned) from the receive signal; the residual is the target modulation. Combined with §2's injection locking, the system operates in the most linear part of the phase response, yielding output highly linear in respiration/heartbeat displacement.
smittch_trigger_FP (thresholds ±200 = 32'h43480000) binarizes the FP waveform and Rx_Debounce cleans it, producing the digital physiological pulse for re-injection (back to §5). In parallel, peak_extractor detects zero crossings with zero_crossing, tracks temp_max each positive half-cycle, and outputs the amplitudes Rx_peak_cc/ir for the UART.
Inside UART_A, test/test_A/… prepend an identifier to the 32-bit data, then tx_16bits slices it into 5×8-bit bytes sent by my_uart_tx. clk_625 (=clk1d25kHz) alternates between the two identifiers 0xC5/0xBA (UART_A L41/49, L71-72), forming the FSK marker so the host can tell the sources apart. Packet format details: module guide §7.
PI_antiWindup's 10-bit pwm_in is compared against a 50 MHz sawtooth; the borrow bit diff[10] is the PWM signal whose duty is proportional to the control value, framed by clk50kHz_d2/_d3.
When clutter is too weak, clutter cancellation fails. CC_Saver counts high/low states over the last 250 samples and automatically decides whether to use the cc or ir path.
雜波太弱時雜波消除會失效。CC_Saver 統計近 250 個週期內的高/低態次數,自動決定要用 cc 還是 ir 路徑。
Every 250 samples form one window (count_full). If the high count falls outside 110–130 (duty deviating from ~50%, meaning that path's signal quality is good), route_select=1 picks in2 (ir), else in1 (cc). route_select also feeds the §0 LEDs and the §5 injection decision (Clutter_weak comes from FP_Comparator L588 vs 40A00000=5.0).
Theory ref — Yu & Horng §6 (Design Example & Simulation):
Same intent — pre-silicon loop validation. Yu validates the PI / ω_BW design against
margin targets (PM ≥ 60°); this testbench validates the 40 kHz oscillator's behavior (period,
amplitude) at the RTL level in Icarus Verilog.
[Yu guide]
tb_relay_feedback2.v is a self-checking testbench that drives
relay_feedback2 in a PC-side logic simulator so the 40 kHz oscillation can be
observed as a waveform without programming the FPGA.
tb_relay_feedback2.v 是一個自我檢查的測試平台,在 PC 端邏輯模擬器中驅動 relay_feedback2,讓 40 kHz 振盪可以在不燒錄 FPGA 的情況下以波形觀察。
Toolchain | 工具鏈
Tool | 工具
Role | 角色
Notes | 備註
iverilog
Compiles RTL + testbench into a simulation binary | 把 RTL+測試平台編譯成模擬二進位檔
Behavioral/RTL simulation only — it does not synthesize for the FPGA (Quartus does that). | 僅供行為級/RTL 模擬——它不為 FPGA 合成(那是 Quartus 的事)。
vvp
Runs the compiled simulation | 執行編譯後的模擬
Produces console output and the .vcd waveform dump. | 產生主控台輸出與 .vcd 波形轉存。
gtkwave
Waveform viewer | 波形檢視器
Opens the .vcd and plots signals over time. | 開啟 .vcd,把訊號隨時間畫出來。
theonekevin.icarusext
VS Code "Verilog Testbench Runner" | VS Code「Verilog Testbench Runner」擴充
Scope limit:
Icarus can simulate the hand-written RTL modules
(relay_feedback2, PI_antiWindup, UART_A,
peak_detector, …) but not the full _1Mhz_beta010 top,
because ~22 files instantiate Altera megafunction IP (FP cores, PLLs) that Icarus cannot
elaborate without Altera simulation libraries.
範圍限制:
Icarus 可以模擬手寫的 RTL 模組(relay_feedback2、PI_antiWindup、UART_A、peak_detector…),但不能模擬完整的 _1Mhz_beta010 頂層,因為約 22 個檔案實例化了 Altera megafunction IP(浮點核、PLL),沒有 Altera 模擬程式庫 Icarus 無法展開。
The first line, `timescale 1ns/1ps, sets the time units for the whole simulation —
everything below depends on it:
第一行 `timescale 1ns/1ps 設定整個模擬的時間單位——下面的一切都依賴它:
Field | 欄位
Value | 數值
Meaning | 意義
Time unit
1 ns
What a bare delay means. #50 → 50 ns. (If the unit were 1 ps, the same #50 would be 50 ps.) | 裸延遲的意義。#50 → 50 ns。(若單位是 1 ps,同樣的 #50 就是 50 ps。)
Time precision
1 ps
Smallest step the simulator tracks; delays round to this. Time is resolved down to 0.001 ns. | 模擬器追蹤的最小步階;延遲捨入到此。時間解析到 0.001 ns。
The unit must be ≥ the precision. Only 1, 10, or 100 of
s/ms/us/ns/ps/fs are allowed (so 1ns/1ps is valid, 2ns is not).
Everything in this design is a multiple of 50 ns, so nothing is ever lost to rounding.
Why it matters here:
The clock always #50 clk10MHz = ~clk10MHz; only produces 10 MHz because
#50 is interpreted as 50 ns. Omitting `timescale makes delays
simulator-dependent and the clock timing wrong. The directive also drives %t
formatting (it defaults to the precision unit, ps) — which is why the console period
needed $timeformat(-9, 0, " ns", 1) to print in nanoseconds. Because the testbench
`includes the DUT after this line, relay_feedback2 inherits the
same 1ns/1ps scale.
The single line always #50 clk10MHz = ~clk10MHz; is the clock source. With the
`timescale 1ns/1ps directive, #50 waits 50 ns, then
toggles the clock. Two toggles = one period = 100 ns:
Critical Gotcha — Reset-less Registers Start as X | 關鍵陷阱——無重置的暫存器以 X 起始
Why the power-up block exists:relay_feedback2 has no reset port, and its state registers
(f1024, res_in, e_1, e_2, u_past)
have no initialiser. On a real Cyclone V these power up to 0, but in simulation they
start as X (unknown) and the feedback loop stays X forever — the output
is a flat undefined trace and never oscillates. Seeding them to 0 at t=0 via the
DUT hierarchy (dut.f1024 = 0; …) reproduces FPGA power-up behaviour without
modifying the DUT. Only g1024 keeps its own initialiser (the non-zero
0x1000000 seed that kick-starts the oscillation).
Use the toolbar button, NOT the Command Palette:
The extension's "Compile and run testbench" command is contributed only to the editor
title bar (editor/title) and expects a file-URI argument. Launched from
Ctrl+Shift+P it receives no argument and silently throws → the
"Icarus Output" channel appears empty. Click the run icon at the
top-right of the editor instead.
用工具列按鈕,不要用命令面板:
擴充的「Compile and run testbench」命令只註冊在編輯器標題列(editor/title),並期望一個檔案 URI 引數。從 Ctrl+Shift+P 啟動收不到引數、默默拋出例外 → 「Icarus Output」頻道空白。請改按編輯器右上角的執行圖示。
Step | 步驟
What happens | 發生什麼
1. Open tb_relay_feedback2.v | 1. 開啟 tb_relay_feedback2.v
Language mode must read Verilog (bottom-right status bar) for the toolbar button to appear. | 語言模式必須顯示 Verilog(右下角狀態列),工具列按鈕才會出現。
2. Click the run button (editor top-right) | 2. 按執行按鈕(編輯器右上角)
Compiles: iverilog -o build/….out tb_relay_feedback2.v, then runs vvp from build/. | 編譯:iverilog -o build/….out tb_relay_feedback2.v,然後從 build/ 執行 vvp。
3. Notification: "Output file was created" | 3. 通知:「Output file was created」
Click Open in GTKWave to view the build/relay_feedback2.vcd. | 按 Open in GTKWave 檢視 build/relay_feedback2.vcd。
The .vcd and compiled binary land in build/ (the extension runs
vvp from there). build/ is gitignored. Manual equivalent:
iverilog -o sim.out tb_relay_feedback2.v && vvp sim.out && gtkwave relay_feedback2.vcd.
Steady-state swing of the 14-bit DAC word after the startup transient. | 啟動暫態後 14 位元 DAC 字組的穩態擺幅。
Settling | 穩定時間
~100 µs
Startup ramp before the amplitude stabilises; 1 ms run captures ~40 full cycles. | 振幅穩定前的啟動爬升;1 ms 的執行捕捉約 40 個完整週期。
Built-in check:
The testbench measures the time between successive zero-crossings of v and prints it
to the console — e.g. period=25000 ns (~40000 Hz) — so the
40 kHz result is verified numerically, not only by eye.
The PC-side program that receives the radar output over the serial link is a LabVIEW VI.
It opens the COM port with VISA Configure Serial Port (230400 baud, 8 data bits, no parity,
1 stop bit — matching the frame the FPGA's my_uart_tx emits), reads the byte stream, and
de-frames it into the amplitude and phase values for display and logging.
透過序列連線接收雷達輸出的 PC 端程式是一個 LabVIEW VI。它用 VISA Configure Serial Port 開啟 COM 埠(230400 鮑、8 資料位元、無同位、1 停止位元——與 FPGA 的 my_uart_tx 發出的訊框一致),讀取位元組流,並解訊框成振幅與相位值以供顯示與記錄。
Frame Markers — how the VI de-frames the stream | 訊框標記——VI 如何解訊框
Because two different quantities share one UART, the FPGA wraps each value in delimiter bytes
so the VI can tell them apart (and re-align if a byte is dropped). Each field gets a distinct
header (prepended) and trailer (appended):
因為兩種不同的量共用一個 UART,FPGA 用分隔位元組包住每個值,讓 VI 能分辨它們(並在位元組遺失後重新對齊)。每個欄位有獨特的檔頭(前綴)與檔尾(後綴):
Field | 欄位
Header (prepend) | 檔頭(前綴)
Payload | 酬載
Trailer (append) | 檔尾(後綴)
Amplitude (Rx_peak → amplitude_info)
0xEE
value bytes
0xBA
PI / phase (data_all)
0xD4
value bytes
0xC5
So the VI scans for 0xEE … 0xBA to capture an amplitude frame and
0xD4 … 0xC5 for a PI / phase frame. These come straight from the RTL: the
amplitude value (Rx_peak → amplitude_info) is framed by test_A
as {8'hEE, …, 8'hBA}, and the PI/phase value (data_all) by test as
{8'hD4, …, 8'hC5} — the prefix is start_iden_code in each test*.v,
the trailer is the low byte of .in in UART_A.v. (Selection is by
clk1d25kHz: high → PI/data_all/test; low →
amplitude/amplitude_info/test_A.)
Practical tips at 230400 baud: match the port settings exactly, key the reads off the header/trailer markers
rather than a fixed byte count, and use a large enough input buffer / timeout so bytes are not dropped
mid-stream — dropped bytes are the usual cause of “occasionally shifted values”.
The design can be loaded into the FPGA two ways: volatile (SRAM configuration via
.sof, lost on power-off) or non-volatile (written to the board's serial
configuration flash, reloaded automatically on every power-up). This project targets device
5CGXFC5C6F27C7 (Cyclone V GX) with USE_CONFIGURATION_DEVICE ON; the config flash
is an EPCQ256 (256 Mbit = 32 MB serial NOR). All commands use Quartus 18.1
($QBIN = /c/intelFPGA/18.1/quartus/bin64) and the USB-Blaster (-c 1).
Two config paths:Volatile-o "p;_1Mhz_beta010.sof" configures the FPGA SRAM directly — fast, but
gone at power-off. Non-volatile programs the EPCQ256, and the FPGA boots from it on
power-up (requires the board's MSEL DIP switches set to Active Serial (AS)).
A serial flash is indirect: JTAG cannot reach it directly, so the programmer first loads a
flash-loader bitstream into the FPGA that bridges JTAG ↔ EPCQ.
"$QBIN/jtagconfig.exe" # lists cable + the 5CGXFC5C… FPGA
"$QBIN/quartus_pgm.exe" -c 1 -a # lists devices and their chain indices
"No JTAG hardware available" means the USB-Blaster / board is not connected or powered — nothing below
(except SOF→JIC conversion) can run until the chain enumerates. The FPGA is normally chain index 1
and the attached EPCQ256 is index 2 (@2); confirm and adjust @N if different.
Equivalently, fix the stale <sof_filename> path in output_file.cof (it points at
an old C:/Users/Chromeina/Desktop/… path) and run quartus_cpf -c output_file.cof.
The .cof records <eprom_name>EPCQ256</eprom_name>,
<flash_loader_device>5CGXFC5C6</flash_loader_device>, <mode>13</mode>
(JIC, Active-Serial ×1).
Step 3 — set MSEL = AS and power-cycle; the FPGA now boots the design from flash.
步驟 3——設 MSEL=AS 並重新上電;FPGA 現在從快閃開機執行設計。
Back up the flash (read EPCQ256 → file) | 備份快閃(讀出 EPCQ256 → 檔案)
To capture whatever is currently on the flash (which may differ from any project .jic),
use the programmer's Examine (E) operation. Syntax:
-o "E;<output_file>;<device_name>@<index>".
This loads the flash-loader, reads the EPCQ256 back into flash_backup.jic, which is directly
re-programmable later (-o "pvi;flash_backup.jic"). GUI alternative: quartus_pgmw
→ Auto Detect → right-click the FPGA → Attach Flash Device → EPCQ256 → tick
Examine → choose output file → Start.
Backup caveats: EPCQ256 = 32 MB, so a full examine takes minutes. Verify the result is
not blank/all-FF (check file size; optionally re-verify against the device with
-o "v;flash_backup.jic;EPCQ256@2"). The existing *_Final.jic files in the project
are previously built images, not a read-back of the live flash.