自動利得制御(AGC)の理論と設計 — 受信機ダイナミックレンジを守る

スマートフォンで通話しながら建物の陰に入ると、電波の強さは一瞬で 20 dB も 30 dB も落ちます。逆に基地局の真下を通れば、同じ端末に桁違いに強い電波が飛び込んできます。ところが受信機の中にある A/D 変換器(ADC)のフルスケールは、電源電圧で決まった固定値です。強すぎればサンプルは頭打ちになって波形が潰れ、弱すぎれば信号が最下位ビットの下に沈んで消えてしまいます。入力は数十 dB 動くのに、変換器の受け皿は動かない — この矛盾をどう埋めるのでしょうか。

答えが自動利得制御(Automatic Gain Control, AGC)です。可変利得増幅器(VGA)の利得を、受信レベルに応じて自動で上げ下げし、ADC の入り口では常にほぼ一定の振幅になるように保ちます。カメラの自動露出とまったく同じ発想で、明るい被写体では絞り、暗い被写体では開く。その「絞り」を電子回路と制御ループで実現したものが AGC です。

AGC の全体像を示すブロック図。VGA・ADC・復調器の順方向パスと、検波・対数変換・誤差生成・積分器からなる帰還パス、そして 80 dB の入力レンジを 40 dB の ADC 窓に押し込む概念図

図の上段が信号の通り道です。アンテナから入った $x[n]$ は VGA で $g[n]$ 倍され、ADC を経て復調器に渡ります。下段が制御の通り道で、出力振幅を検波して対数をとり、目標値 $\ln A_t$ との差(誤差 $e[n]$)を積分器に流し込み、その結果である対数利得 $G = \ln g$ で VGA を制御します。右側の帯が示すとおり、AGC の仕事は「80 dB も動く入力レンジ」を「40 dB しかない ADC の窓」に押し込むことに尽きます。この記事では、下段の帰還パスでなぜ対数をとるのかが最大の論点になります。

AGC は地味な脇役に見えますが、受信機の性能を決める急所にいます。

  • 携帯電話・無線LAN の受信機: 端末が基地局に近づいたり離れたりするたびに受信電力は 60〜80 dB 変動します。AGC がなければ ADC の有効ビット数(ENOB)が数ビット分も失われ、そのぶん BER が悪化します。バースト通信では、プリアンブルのわずか数マイクロ秒で利得を決めきる速さも要求されます。
  • 衛星通信・テレメトリ受信: 低軌道衛星は仰角の変化だけで自由空間損失が 10 dB 以上変わり、降雨減衰やフェージングが上乗せされます。地上局の受信機は、この緩やかな変動を AGC で吸収して復調器に一定振幅を渡します。
  • レーダー受信機: 近距離の強い反射と遠距離の微弱な反射が同じ受信機に入ってきます。AGC(や STC: Sensitivity Time Control)がなければ、近距離目標で受信機が飽和して遠距離目標が見えなくなります。
  • オーディオ・補聴器: 同じ理屈のコンプレッサ/リミッタが使われており、アタック・リリース時定数の設計思想は無線の AGC とほぼ共通です。

そして AGC には、初学者がまず驚く数学的な美しさがあります。「利得を掛ける」という乗算の操作は本質的に非線形なのに、対数(dB)の世界で誤差を測ると、ループは厳密な一次線形システムになるのです。しかも、その線形性は入力レベルによらず成り立ちます。この記事の中心は、この事実の導出と、そこから出てくる設計式です。

本記事の内容

  • 固定利得受信機がなぜ破綻するか — ダイナミックレンジと ADC フルスケールの衝突
  • 素朴な「リニア AGC」の失敗 — ループ極が入力振幅に依存してしまう
  • 対数領域での厳密な線形化 — $G[n+1] = (1-\mu)G[n] + \mu(\ln A_t – a[n])$ の導出
  • 時定数 $\tau = -T_s/\ln(1-\mu) \approx T_s/\mu$ と整定時間
  • AGC は「対数包絡線に対するハイパスフィルタ」であるという見方と、ループ帯域・等価雑音帯域の導出
  • 安定性 — ステップ幅の上限、ループ遅延 $D$ に対する閉形式 $\mu_{\max} = 2\sin\!\big(\tfrac{\pi}{2(2D+1)}\big)$、検波平滑化との干渉と設計則 $\alpha > 4\mu$
  • アタック/リリース非対称の理由と、それが生む平衡レベルのバイアス
  • AGC と ADC — 最適バックオフ、$\mathrm{SNR}_q = 6.02B + 4.77 – \mathrm{BO}$、ENOB の山
  • AGC 歪み — $\mathrm{EVM} \approx \sigma_a \sqrt{B_n / R_s}$ の導出と 16QAM での検証
  • Python による離散時間 AGC の実装と可視化(ステップ応答・周波数応答・ENOB・コンスタレーション崩壊)

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

AGC とは — 受信機の「自動露出」

暗い室内で写真を撮るとき、カメラは絞りを開き、明るい屋外では絞ります。どちらの場合も、イメージセンサに届く光量はだいたい同じ範囲に収まります。センサのダイナミックレンジは固定なので、入ってくる光量のほうを一定範囲に押し込むわけです。受信機の AGC がやっていることも、まったく同じです。アンテナに届く電力は環境まかせで動きますが、ADC の受け皿は固定なので、途中の増幅率を動かして辻褄を合わせます。

入力ダイナミックレンジはどれくらいか

具体的な数字で感覚をつかみましょう。2.4 GHz 帯の無線 LAN を考えます。自由空間伝搬損失は

$$ L_{\rm fs}\,[\mathrm{dB}] = 20\log_{10} d + 20\log_{10} f + 32.44 \quad (d\,[\mathrm{km}],\ f\,[\mathrm{MHz}]) $$

なので、距離が 1 m から 100 m に変わるだけで $20\log_{10}(100) = 40$ dB の差が生じます。ここに壁による遮蔽(5〜20 dB)とマルチパスフェージングの落ち込み(深いヌルでは 20〜30 dB)が重なります。結果として、同じ受信機が扱わなければならない入力範囲は 60〜90 dB に達します。電力比でいえば $10^6$ 倍から $10^9$ 倍です。

一方、ADC 側はどうでしょうか。12 ビット ADC の理想 SNR は $6.02 \times 12 + 1.76 = 74$ dB ですが、これは「フルスケール正弦波」に対する値です。実際の変調波はピーク対平均電力比(PAPR)を持つので、クリップを避けるには実効値をフルスケールから 10 dB ほど下げて使います。さらに復調に必要な SNR を 20 dB 確保しようとすると、ADC が単独で許容できる入力レベル変動幅は 40 dB 程度しかありません。60〜90 dB の入力変動を 40 dB の窓に押し込む — この差分を埋めるのが AGC の仕事です。

固定利得では何が起きるか

8 ビット ADC に固定利得で信号を入れたときの実効 SNDR。入力が強い側はクリップ歪みで急落し、弱い側は量子化雑音で 1 dB 対 1 dB で悪化する。必要 SNR 19 dB を満たす窓は 28 dB 幅しかない

この図が「2 枚の壁」を一枚で示しています。右端(入力が強い)では点線の量子化理論から急激に離れてクリップ歪みが SNDR を潰し、左端(入力が弱い)では点線に沿って 1 dB 対 1 dB で悪化していきます。16QAM で BER $10^{-4}$ を得るのに必要な 19 dB を満たす入力範囲は、8 ビット ADC の場合わずか 28 dB です。入力側は 60〜90 dB 動くのですから、固定利得ではこの窓に入っている時間のほうが例外だとわかります。

固定利得の受信機を、入力レベルの両端で観察してみましょう。

入力が強すぎるとき、ADC の出力サンプルはフルスケールで頭打ちになります。これは単なる振幅誤差ではありません。クリップは強い非線形歪みで、スペクトルを広げ、隣接チャネルに漏れ、コンスタレーションの外側の点を内側に折り返します。16QAM のように外周に情報を持つ変調では、外側のシンボルが集中的に壊れるので、BER は急激に悪化します。

入力が弱すぎるとき、信号振幅が量子化ステップ $\Delta$ に近づきます。量子化雑音電力は $\Delta^2/12$ で固定なので、信号が小さくなればなるほど量子化 SNR は 1 dB 対 1 dB で悪化します。信号が $\Delta$ を下回れば、サンプルはほとんど 0 か $\pm\Delta$ しか取らず、情報は完全に失われます。

つまり固定利得の受信機には、上はクリップ、下は量子化埋没という 2 枚の壁があり、その間の狭い窓でしか正しく動きません。AGC はこの窓を入力レベルに追従させて動かす機構だと理解できます。

AGC ループの構成要素

AGC は次の 4 つの要素からなる閉ループです。

  1. 可変利得増幅器(VGA) — 制御電圧 $v$ に応じて利得 $g(v)$ を変える増幅器。後述するように、$g$ が $v$ に対して指数関数的(dB が電圧に対して線形)であることが決定的に重要です。
  2. 振幅検波器 — 出力 $y[n]$ の振幅または電力を測る。ピーク検波、RMS 検波、対数検波(ログアンプ)などの種類があり、選択が特性を大きく変えます。
  3. 誤差生成 — 検波値と目標値 $A_t$ を比較する。ここを引き算(線形誤差)でやるか、割り算=対数の引き算(対数誤差)でやるかが本記事の主題です。
  4. ループフィルタ(積分器) — 誤差を積分して制御量を作る。定常誤差をゼロにするために積分動作が必須です。

数式で書けば、離散時間の AGC は次の 2 本の式に集約されます。

$$ \begin{equation} y[n] = g[n]\, x[n] \end{equation} $$

$$ \begin{equation} g[n+1] = \mathcal{F}\big(g[n],\, |y[n]|,\, A_t \big) \end{equation} $$

$x[n]$ が入力の複素ベースバンド信号、$y[n]$ が AGC 出力、$A_t$ が目標振幅です。問題は $\mathcal{F}$ をどう作るか。ここからは、まず素朴な作り方が失敗することを見て、その失敗理由から正しい形を導きます。

リニア AGC はなぜ破綻するのか

制御を少しかじった人がまず書くのは、誤差をそのまま積分する形でしょう。

$$ \begin{equation} g[n+1] = g[n] + \mu\big(A_t – |y[n]|\big) \end{equation} $$

目標より出力が大きければ利得を下げ、小さければ上げる。直感的には何の問題もなさそうです。しかし、これは実際にはほとんど使い物になりません。理由を誤差の漸化式から見ましょう。

入力振幅を $|x[n]| = A_x$(一定)とすると、$|y[n]| = g[n] A_x$ ですから

$$ g[n+1] = g[n] + \mu\big(A_t – g[n] A_x\big) = (1 – \mu A_x)\, g[n] + \mu A_t $$

平衡点は $g^\star = A_t / A_x$ で、そこからのずれ $\epsilon[n] = g[n] – g^\star$ は

$$ \begin{equation} \epsilon[n+1] = (1 – \mu A_x)\, \epsilon[n], \qquad \epsilon[n] = (1-\mu A_x)^n \epsilon[0] \end{equation} $$

に従います。ここにリニア AGC の致命傷が現れています。ループの極が $1 – \mu A_x$ であり、入力振幅 $A_x$ に比例して動くのです。安定条件は

$$ \begin{equation} |1 – \mu A_x| < 1 \iff 0 < \mu A_x < 2 \end{equation} $$

です。$\mu$ は設計時に固定する定数ですから、$A_x$ が 60 dB(振幅比で 1000 倍)動けば、実効的なループ利得も 1000 倍動きます。

数値で確かめます。入力振幅が $A_x = 10^{-3}$(弱い信号)から $A_x = 0.316$(強い信号)まで 50 dB 変動する状況を考えましょう。振幅比は $316$ 倍です。

  • 強い信号で安定になるように $\mu$ を選ぶと、$\mu A_x \le 1$ から $\mu \le 1/0.316 = 3.16$。このとき弱い信号では $\mu A_x = 3.16 \times 10^{-3}$ となり、時定数は約 $316$ サンプル。弱信号での収束は強信号の 316 倍遅くなります。
  • 弱い信号で速く収束するように $\mu$ を選ぶと、たとえば $\mu = 100$(弱信号で $\mu A_x = 0.1$、時定数 10 サンプル)。しかし強い信号では $\mu A_x = 31.6 \gg 2$ となり、ループは発散します。実装では利得が負に振り切れて 0 にクランプされ、受信機が「死ぬ」という最悪の挙動になります。

つまりリニア AGC は、扱うダイナミックレンジと同じ倍率だけ、応答速度と安定余裕がばらつくのです。60 dB の入力範囲を想定した受信機で、この設計は成立しません。

リニア AGC の極の絶対値と時定数が入力振幅にどう依存するかのグラフ。μ=0.3 では弱信号で時定数が 3000 サンプル超、μ=100 では -34 dB より強い入力で発散する。対数 AGC は入力によらず一定

左のグラフは極 $|1-\mu A_x|$ を入力振幅に対して描いたものです。リニア AGC(青・紫)の極は入力レベルとともに動き、$\mu=100$ では $A_x$ が $-34$ dB を超えたところで赤い破線($|z|=1$)を突き抜けて発散領域に入ります。一方、対数 AGC(緑)は完全な水平線で、入力が 60 dB 動いても極は $1-\mu = 0.95$ のまま微動だにしません。右のグラフはこれを時定数に直したもので、$\mu=0.3$ のリニア AGC は弱信号で 3000 サンプル超、強信号で 3 サンプル弱と3 桁も速さが変わります。同じ回路が入力レベルによって「遅すぎる」か「死ぬ」かに転ぶ、という破綻の姿がそのまま見えています。

失敗の原因ははっきりしています。制御対象である「利得の掛け算」が乗算的な操作なのに、誤差を加算的(引き算)に測ったせいで、ループ利得に入力振幅が掛かってしまいました。ならば、乗算を加算に変える魔法 — つまり対数を使えばよいはずです。次の節で、それが単なる近似ではなく厳密な線形化になることを示します。

対数領域でループが線形になる

左: ギルバートセル型 VGA の利得は制御電圧に対して指数関数(dB 値では直線 30 dB/V)。右: 対数をとると乗算 y=gx が加算 ln|y|=G+a に変わり、係数が定数の一次線形差分方程式になることを示す流れ図

この節の結論を先に一枚にまとめたのが上の図です。左は VGA の実物の特性で、制御電圧に対して利得の倍率(青)は指数関数のカーブを描きますが、dB 値(赤破線)はきれいな直線になります。ハードウェアがすでに「対数の世界」で動いているわけです。右はこれから行う操作の流れで、乗算 $y = gx$ の両辺に $\ln$ を掛けると加算 $\ln|y| = G + a$ になり、誤差も比の対数でとることで、最終的に係数が定数の一次線形差分方程式に落ちます。以下ではこの流れを一段ずつ導きます。

指数利得 VGA という物理

まず、ハードウェアの側に都合のよい事実があります。バイポーラトランジスタのコレクタ電流はベース・エミッタ電圧に対して指数関数的に増え($I_C \propto e^{qV_{BE}/kT}$)、これを利用したギルバートセル型の VGA は、制御電圧に対して利得の dB 値が直線的に変化する特性(linear-in-dB, たとえば 30 dB/V)を自然に実現します。デジタル制御の VGA でも、利得ステップを 0.5 dB 刻みのような dB 等間隔で並べるのが標準です。

これを式で書きます。制御量を $v$、利得を $g$ として

$$ \begin{equation} G_{\rm dB}(v) = k_v (v – v_0), \qquad g(v) = 10^{k_v(v-v_0)/20} = e^{\kappa (v – v_0)}, \quad \kappa = \frac{k_v \ln 10}{20} \end{equation} $$

つまり VGA は指数関数そのものです。そこで、制御ループの内部状態を利得そのもの $g$ ではなく、その対数

$$ \begin{equation} G[n] \equiv \ln g[n] \end{equation} $$

で持つことにします。$G$ は物理的には制御電圧に比例する量(=dB 表示の利得)であり、ハードウェアが自然に扱える変数でもあります。

対数誤差の定義

次に誤差の測り方を変えます。目標振幅 $A_t$ に対し、比の対数を誤差とします。

$$ \begin{equation} e[n] \equiv \ln\frac{A_t}{|y[n]|} = \ln A_t – \ln|y[n]| \end{equation} $$

これは「出力が目標の何 dB 上/下か」を測っているのと同じです($e_{\rm dB} = \frac{20}{\ln 10} e$)。ループフィルタは今までどおり単純な積分器にします。

$$ \begin{equation} G[n+1] = G[n] + \mu\, e[n] \end{equation} $$

厳密な線形化の導出

ここからが本題です。$y[n] = g[n] x[n] = e^{G[n]} x[n]$ ですから、出力の対数振幅は足し算に分解されます。

$$ \ln|y[n]| = \ln\big(e^{G[n]}|x[n]|\big) = G[n] + \ln|x[n]| $$

入力の対数振幅を $a[n] \equiv \ln|x[n]|$ と書くと、誤差は

$$ \begin{equation} e[n] = \ln A_t – G[n] – a[n] \end{equation} $$

となります。これを積分器の式に代入すると、

$$ G[n+1] = G[n] + \mu\big(\ln A_t – G[n] – a[n]\big) $$

$G[n]$ の項をまとめれば、

$$ \begin{equation} \boxed{\;G[n+1] = (1-\mu)\, G[n] + \mu\big(\ln A_t – a[n]\big)\;} \end{equation} $$

これが AGC の中心的な結果です。入力の対数振幅 $a[n]$ を入力、対数利得 $G[n]$ を状態とする、係数がすべて定数の一次線形差分方程式になりました。ここで強調しておきたいのは、これが小信号近似ではないことです。$a[n]$ がどれだけ大きく動こうと、上の式に近似は一切入っていません。乗算的な制御対象と乗算的な誤差測定(比)が、対数の中で加算に化けた結果、非線形性が完全に消えたのです。

リニア AGC の極が $1-\mu A_x$ で入力依存だったのに対し、対数 AGC の極は

$$ \begin{equation} z_p = 1 – \mu \end{equation} $$

で、入力レベルに一切依存しません。60 dB の入力変動があっても、収束の速さも安定余裕もまったく変わらない。これが対数領域 AGC が標準になっている理由です。

平衡点と定常誤差

入力が一定($a[n] = a$)のときの平衡点は、$G^\star = (1-\mu)G^\star + \mu(\ln A_t – a)$ より

$$ \begin{equation} G^\star = \ln A_t – a \quad\Longrightarrow\quad |y^\star| = e^{G^\star}|x| = \frac{A_t}{|x|}\cdot|x| = A_t \end{equation} $$

定常誤差はゼロです。積分器を含むループなので、これは当然の結果ですが、重要なのは入力レベルによらず出力が正確に $A_t$ に落ち着くという点です。

時定数と整定時間

平衡点からのずれ $\varepsilon[n] = G[n] – G^\star$ は

$$ \begin{equation} \varepsilon[n+1] = (1-\mu)\varepsilon[n], \qquad \varepsilon[n] = (1-\mu)^n \varepsilon[0] \end{equation} $$

と幾何級数で減衰します。連続時間の一次遅れ $e^{-t/\tau}$ と対応づけるため、$n$ サンプル目の時刻を $t = nT_s$($T_s = 1/f_s$ はサンプル周期)として

$$ (1-\mu)^n = e^{n \ln(1-\mu)} \stackrel{!}{=} e^{-nT_s/\tau} $$

指数の肩を比較すると $n\ln(1-\mu) = -nT_s/\tau$ なので、

$$ \begin{equation} \tau = \frac{-T_s}{\ln(1-\mu)} \;\approx\; \frac{T_s}{\mu} \quad (\mu \ll 1) \end{equation} $$

を得ます。近似では $\ln(1-\mu) \approx -\mu$ を使いました。ループ定数 $\mu$ の逆数がサンプル単位の時定数という、覚えやすい関係です。

実務でより直接使うのは整定時間です。$\Delta$ dB のステップ入力に対し、残留誤差が $\delta$ dB を下回るまでのサンプル数 $n_s$ は、$\varepsilon$ の dB 値も同じ漸化式に従うので

$$ \Delta (1-\mu)^{n_s} = \delta \;\Longrightarrow\; n_s = \frac{\ln(\delta/\Delta)}{\ln(1-\mu)} \approx \frac{1}{\mu}\ln\frac{\Delta}{\delta} $$

$$ \begin{equation} n_s = \frac{\ln(\Delta/\delta)}{-\ln(1-\mu)} \approx \tau_{\rm samp}\,\ln\frac{\Delta}{\delta} \end{equation} $$

具体例を計算しましょう。$\mu = 0.05$、$\Delta = 50$ dB のステップ($-60$ dBm から $-10$ dBm への急変)に対して $\delta = 1$ dB 以内に入るまでは

$$ n_s = \frac{\ln(50/1)}{-\ln 0.95} = \frac{3.912}{0.05129} = 76.3 \to 77\ \text{サンプル} $$

サンプリング周波数 20 MHz なら 3.9 µs です。$\delta = 0.5$ dB まで詰めるなら $n_s = 90$ サンプル(4.5 µs)。無線 LAN のショートプリアンブル(8 µs)の内側に収まる速さで、実際この程度の設計が使われます。

dB で書いても $\mu$ は同じ

現場では自然対数ではなく dB で計算するのが普通です。$G_{\rm dB} = \frac{20}{\ln 10} G$、$e_{\rm dB} = \frac{20}{\ln 10} e$ と置くと、積分器の式は

$$ \frac{\ln 10}{20} G_{\rm dB}[n+1] = \frac{\ln 10}{20} G_{\rm dB}[n] + \mu \cdot \frac{\ln 10}{20} e_{\rm dB}[n] $$

両辺の共通因子 $\frac{\ln 10}{20}$ が約せるので、

$$ \begin{equation} G_{\rm dB}[n+1] = G_{\rm dB}[n] + \mu\, e_{\rm dB}[n] \end{equation} $$

まったく同じ形、同じ $\mu$ になります。$\mu$ は無次元のスケール不変量なので、「ネーパで設計して dB で実装する」といった単位換算の事故が起きません。実装上ありがたい性質です。

ここまでで、AGC ループは入力レベルによらない一次系であり、$\mu$ ひとつで速さが決まることがわかりました。では $\mu$ は速ければ速いほど良いのでしょうか。答えは明確に「否」で、その理由を見るには時間領域ではなく周波数領域でループを眺めるのが近道です。

AGC は「対数包絡線のハイパスフィルタ」である

伝達関数を求める

先ほどの差分方程式を $z$ 変換します。$\ln A_t$ は定数なので、まず変動成分だけを見るために $\tilde{G} = G – \mathbb{E}[G]$、$\tilde{a} = a – \mathbb{E}[a]$ と中心化した量を考えます(定数項は DC 成分としてあとで戻せます)。

$$ z\tilde{G}(z) = (1-\mu)\tilde{G}(z) – \mu \tilde{a}(z) $$

$\tilde{G}(z)$ について解くと、入力対数振幅から対数利得への伝達関数は

$$ \begin{equation} H_{ag}(z) = \frac{\tilde{G}(z)}{\tilde{a}(z)} = \frac{-\mu}{z – (1-\mu)} \end{equation} $$

$z=1$(直流)を代入すると $H_{ag}(1) = -\mu/\mu = -1$。入力が 1 dB 上がれば利得を 1 dB 下げる、という当たり前の関係です。

より意味があるのは、出力の対数振幅への伝達関数です。$\ln|y| = G + a$ なので

$$ \begin{equation} H_{\rm out}(z) = 1 + H_{ag}(z) = 1 – \frac{\mu}{z-1+\mu} = \frac{z-1}{z-1+\mu} \end{equation} $$

分子に $z-1$ があります。これは微分器=ハイパスフィルタです。つまり、

$$ \begin{equation} \boxed{\;\text{AGC とは、対数包絡線 } \ln|x[n]| \text{ に作用する一次ハイパスフィルタである}\;} \end{equation} $$

この一文が AGC の設計論をほぼ全部説明します。

  • $z = 1$(直流・超低周波)で利得 0 → 経路損失や遅いフェージングのようなゆっくりした振幅変動は完全に除去される。これが AGC の目的そのものです。
  • $z = -1$(ナイキスト・高い周波数)で利得 $\frac{-2}{-2+\mu} \approx 1$ → 速い振幅変動は素通りする。変調が持つ包絡線の揺れは、そのまま出力に残ります。

そして、この 2 つの領域を分ける遮断周波数の位置こそが、設計上のすべてのトレードオフの中心になります。遮断周波数より下にある振幅変動は「消される」ので、もし変調信号の包絡線情報がそこに含まれていたら、それは破壊されます。これが後の節で扱う「AGC 歪み」です。

3 dB 帯域と等価雑音帯域

遮断周波数を求めます。$z = e^{j\omega}$ として

$$ |H_{\rm out}(e^{j\omega})|^2 = \frac{|e^{j\omega}-1|^2}{|e^{j\omega}-1+\mu|^2} = \frac{2 – 2\cos\omega}{1 – 2(1-\mu)\cos\omega + (1-\mu)^2} $$

分子は $|e^{j\omega}-1|^2 = (\cos\omega-1)^2 + \sin^2\omega = 2-2\cos\omega$ から得ました。$\mu \ll 1$、$\omega \ll 1$ の領域で $\cos\omega \approx 1 – \omega^2/2$ を代入して整理すると、分子は $\omega^2$、分母は $\omega^2 + \mu^2$ に漸近します。したがって

$$ |H_{\rm out}|^2 \approx \frac{\omega^2}{\omega^2 + \mu^2} $$

これが $1/2$ になるのは $\omega = \mu$ のときなので、3 dB 遮断周波数は

$$ \begin{equation} f_c = \frac{\mu}{2\pi} f_s = \frac{1}{2\pi\tau} \end{equation} $$

時定数 $\tau$ と一次系の関係そのものです。

ループ雑音の議論では等価雑音帯域 $B_n$ のほうを使います。$H_{ag}$ のインパルス応答は、$n \ge 1$ で $h[n] = -\mu(1-\mu)^{n-1}$($n=0$ で 0)なので、そのエネルギーは等比級数の和として

$$ \sum_{n\ge 1} h[n]^2 = \mu^2 \sum_{k\ge 0} (1-\mu)^{2k} = \frac{\mu^2}{1-(1-\mu)^2} = \frac{\mu^2}{2\mu – \mu^2} = \frac{\mu}{2-\mu} $$

分母の展開では $1-(1-\mu)^2 = 1 – (1 – 2\mu + \mu^2) = 2\mu – \mu^2$ を使いました。直流利得は $|H_{ag}(1)| = 1$ なので、片側等価雑音帯域は

$$ \begin{equation} B_n = \frac{f_s}{2}\cdot\frac{\sum h[n]^2}{|H_{ag}(1)|^2} = \frac{f_s}{2}\cdot\frac{\mu}{2-\mu} \approx \frac{\mu f_s}{4} \end{equation} $$

$f_c = \mu f_s / (2\pi) \approx 0.159\,\mu f_s$ に対し $B_n \approx 0.25\,\mu f_s$ ですから、$B_n \approx 1.57 f_c = (\pi/2) f_c$ という一次系でおなじみの関係になっています。

$\mu$ を大きくすれば速く収束するが、ループ帯域も広がって変調の包絡線を食い始める — トレードオフの形が見えてきました。ただしその前に、そもそも $\mu$ をどこまで大きくできるのか、安定性の限界を押さえておく必要があります。

安定性 — $\mu$ の上限、ループ遅延、検波器の平滑化

遅延のない理想ループ

極は $1-\mu$ なので、安定条件は $|1-\mu| < 1$、すなわち

$$ \begin{equation} 0 < \mu < 2 \end{equation} $$

領域ごとの挙動は次のとおりです。

  • $0 < \mu < 1$: 極が $(0,1)$ にあり、誤差は符号を変えずに単調に減衰します。過渡応答にオーバーシュートがありません。
  • $\mu = 1$: 極がちょうど 0。デッドビートで、1 サンプルで平衡点に到達します。誤差が完全に既知で遅延ゼロならこれが最速です。
  • $1 < \mu < 2$: 極が $(-1,0)$ で、誤差が毎サンプル符号を反転しながら減衰します。利得が「上げすぎ・下げすぎ」を繰り返すリンギングです。
  • $\mu \ge 2$: 発散。利得が振動しながら際限なく振れ、受信機は使い物になりません。

現実の設計では $\mu \ll 1$($10^{-4} \sim 10^{-2}$ 程度)を選ぶので、この上限に触れることはほとんどありません。しかし、ループに遅延が入ると事情が一変します

ループ遅延がある場合 — 美しい閉形式

実際の AGC ループには必ず遅延があります。RF/IF 段の群遅延、検波器の応答、ADC のパイプライン遅延、DSP の処理遅延、そして VGA 制御 DAC のセトリング。これらを合計 $D$ サンプルの遅延としてモデル化します。

$$ \begin{equation} G[n+1] = G[n] + \mu\, e[n-D] \end{equation} $$

誤差 $\varepsilon[n] = G[n]-G^\star$ の漸化式は $e[n-D] = -\varepsilon[n-D]$ より

$$ \varepsilon[n+1] = \varepsilon[n] – \mu\,\varepsilon[n-D] $$

$\varepsilon[n] = z^n$ を代入して $z^{n-D}$ で割ると、特性方程式は

$$ \begin{equation} z^{D+1} – z^{D} + \mu = 0 \end{equation} $$

安定限界では、根が単位円上にあります。$z = e^{j\theta}$ を代入すると

$$ e^{j(D+1)\theta} – e^{jD\theta} + \mu = 0 \;\Longrightarrow\; e^{jD\theta}\big(e^{j\theta}-1\big) = -\mu $$

ここで $e^{j\theta}-1 = 2j\sin(\theta/2)\,e^{j\theta/2}$ という恒等式を使います(オイラーの公式から直接出ます)。左辺は

$$ 2\sin(\theta/2)\, e^{j\left(D\theta + \theta/2 + \pi/2\right)} $$

これが負の実数 $-\mu$ に等しくなる条件は、絶対値と偏角をそれぞれ比較して

$$ 2\sin(\theta/2) = \mu, \qquad D\theta + \frac{\theta}{2} + \frac{\pi}{2} = \pi $$

偏角の式を $\theta$ について解くと $\left(D+\tfrac12\right)\theta = \tfrac{\pi}{2}$、すなわち $\theta = \dfrac{\pi}{2D+1}$。これを絶対値の式に代入して

$$ \begin{equation} \boxed{\;\mu_{\max} = 2\sin\!\left(\frac{\pi}{2(2D+1)}\right)\;} \end{equation} $$

驚くほどきれいな閉形式が出てきました。値を並べてみます。

ループ遅延 $D$ [サンプル] $\mu_{\max}$ 対応する最小時定数 $\tau$ [サンプル]
0 2.000
1 1.000 1.0
2 0.618 1.04
3 0.445 1.70
4 0.347 2.35
5 0.285 2.99
6 0.241 3.65

$D=2$ で $\mu_{\max} = 0.618$、これは黄金比の逆数 $(\sqrt5-1)/2$ です($2\sin(\pi/10)$ がちょうどその値になります)。$D$ が大きいところでは $\sin x \approx x$ より

$$ \mu_{\max} \approx \frac{\pi}{2D+1} \approx \frac{\pi}{2D} $$

となり、ループ帯域に直すと $f_c \le \dfrac{\mu_{\max} f_s}{2\pi} \approx \dfrac{f_s}{4D}$。ループ遅延時間 $DT_s$ の逆数の 1/4 程度がループ帯域の上限という、フィードバック制御でおなじみの結論です。安全のためには実際にはこの $1/5$〜$1/10$ に取ります。

検波器の平滑化との干渉 — 設計則 $\alpha > 4\mu$

もう一つ、実装で必ず遭遇する落とし穴があります。振幅検波は 1 サンプルの $|y[n]|$ をそのまま使うのではなく、電力を平滑化して推定するのが普通です。

$$ \begin{equation} \hat{P}[n] = (1-\alpha)\hat{P}[n-1] + \alpha\,|y[n]|^2 \end{equation} $$

こうすると推定分散が下がる代わりに、ループに 2 つ目の極が入り、系が二次になります。対数領域で見ると、この検波器はほぼ極 $1-\alpha$ の一次低域通過フィルタなので、誤差の漸化式は

$$ \varepsilon[n+1] = \varepsilon[n] – \mu\, f[n], \qquad f[n] = (1-\alpha)f[n-1] + \alpha\,\varepsilon[n] $$

となります。特性方程式は、$\varepsilon,f \propto z^n$ とおいて 2 式を連立し $f$ を消去すると

$$ (z-1)\big(z-(1-\alpha)\big) + \mu\alpha = 0 $$

展開すると

$$ \begin{equation} z^2 – (2-\alpha)z + \big[(1-\alpha) + \mu\alpha\big] = 0 \end{equation} $$

判別式は

$$ \begin{aligned} \mathcal{D} &= (2-\alpha)^2 – 4\big[(1-\alpha)+\mu\alpha\big] \\ &= 4 – 4\alpha + \alpha^2 – 4 + 4\alpha – 4\mu\alpha \\ &= \alpha^2 – 4\mu\alpha = \alpha(\alpha – 4\mu) \end{aligned} $$

2 行目では $(2-\alpha)^2 = 4-4\alpha+\alpha^2$ を展開し、3 行目で $4$ と $-4$、$-4\alpha$ と $+4\alpha$ が相殺しました。$\alpha > 0$ なので、判別式の符号は $\alpha – 4\mu$ で決まります。

$$ \begin{equation} \boxed{\;\alpha > 4\mu \;\Longrightarrow\; \text{2 実根(オーバーダンプ、オーバーシュートなし)}\;} \end{equation} $$

逆に $\alpha < 4\mu$ だと複素根になり、ステップ応答がリンギングします。設計則として

検波器の平滑化はループより 4 倍以上速くせよ($\alpha \ge 4\mu$、時定数でいえば検波器の時定数がループ時定数の $1/4$ 以下)

と覚えられます。これは「時間スケールを分離する」という制御設計の常道を、AGC ループの文脈で定量化したものです。また、定数項の絶対値が 1 未満という条件 $|(1-\alpha)+\mu\alpha| < 1$ から $\mu < 1$ という安定限界も出てきます(遅延 $D=1$ の場合と一致するのは、平滑化フィルタが実効的に 1 サンプル分の遅延として働くためです)。

なお、電力を対数に変換する際は $\ln P = 2\ln|y|$ なので、電力の対数誤差を使うと実効的なループ定数が 2 倍になる点に注意してください。実装では

$$ e[n] = \tfrac12\big(\ln A_t^2 – \ln \hat{P}[n]\big) $$

のように $1/2$ を掛けて振幅ベースの $\mu$ と揃えておくと、上の設計式がそのまま使えます。

安定性の枠組みが固まりました。しかしここまでの議論は「アタックもリリースも同じ $\mu$」を前提にしています。実際の AGC が対称でないのはなぜなのか、次に見ていきます。

アタックとリリース — なぜ時定数を非対称にするのか

2 つの方向、2 つの要求

AGC の利得変化には 2 方向あります。

  • アタック(attack): 入力が急に強くなり、利得を下げる動き。誤差 $e[n] = \ln A_t – \ln|y[n]| < 0$ の側。
  • リリース(release / decay): 入力が弱くなり、利得を上げる動き。$e[n] > 0$ の側。

この 2 方向には、まったく非対称な要求が課されます。

アタックは速くなければなりません。 理由は ADC の保護です。$\Delta$ dB のステップが入った瞬間、出力は $\Delta$ dB だけ跳ね上がります。ADC のヘッドルーム(目標実効値からフルスケールまでの余裕)を $H$ dB とすると、利得誤差が $H$ dB を下回るまでの間、サンプルはクリップし続けます。そのサンプル数は先ほどの整定時間の式そのままで

$$ \begin{equation} n_{\rm clip} = \frac{\ln(\Delta/H)}{-\ln(1-\mu_a)} \end{equation} $$

数値例です。$\Delta = 50$ dB のステップ、$H = 10$ dB のヘッドルームで、

  • 速いアタック $\mu_a = 0.2$: $n_{\rm clip} = \ln(5)/(-\ln 0.8) = 1.609/0.2231 = 7.2 \to 8$ サンプル。$f_s = 20$ MHz なら 0.4 µs。
  • 遅いアタック $\mu_a = 0.005$: $n_{\rm clip} = 1.609/0.005013 = 321$ サンプル、16 µs。

16 µs のクリップは、OFDM シンボル数個分がまるごと壊れることを意味します。バースト通信では致命的です。だからアタックは速くします。

リリースは遅くなければなりません。 理由は 3 つあります。

  1. 包絡線追従による歪み: QAM や OFDM の瞬時振幅は、変調そのものによって激しく揺れます。リリースが速いと、シンボル間の一時的な振幅の谷を「信号が弱くなった」と誤認して利得を持ち上げ、次の瞬間に戻す — つまり変調の包絡線を平坦化してしまう。振幅に情報を載せている変調では、これは情報の破壊そのものです。
  2. 雑音のポンピング: 送信が途切れた区間(バースト間のギャップ、無音区間)で利得を全開まで上げてしまうと、雑音フロアが持ち上がって「ザーッ」という耳障りな挙動(オーディオでいうポンピング/ブリージング)になります。無線では、その直後にバーストが来たときに大きなクリップを招きます。
  3. 深いフェージングヌルの無視: マルチパスの深いヌルは数十マイクロ秒で通り過ぎます。それに合わせて利得を上げると、ヌルを抜けた瞬間に飽和します。リリースを遅くしておけば、短いヌルは「無視」されます。

実装 — 符号でステップ幅を切り替える

実装は驚くほど単純で、誤差の符号でステップ幅を切り替えるだけです。

$$ \begin{equation} G[n+1] = G[n] + \begin{cases} \mu_a\, e[n] & (e[n] < 0,\ \text{アタック}) \\[2pt] \mu_r\, e[n] & (e[n] \ge 0,\ \text{リリース}) \end{cases} \end{equation} $$

典型的には $\mu_a/\mu_r = 10 \sim 100$ に取ります。この単純な切り替えによって、ループは全体としては非線形になりますが、各方向では依然として一次系なので、時定数はそれぞれ $\tau_a \approx T_s/\mu_a$、$\tau_r \approx T_s/\mu_r$ と別々に設計できます。

非対称がもたらす平衡レベルのバイアス

ここで、教科書にあまり書かれていない重要な副作用があります。非対称なループの平衡点は、包絡線分布の平均ではなく、上側にずれます。

平衡状態では、利得を下げる力と上げる力が釣り合っています。$m \equiv \ln A_t – G^\star$(平衡時に「目標」とされる対数入力レベル)と置くと $e = m – a$ なので、釣り合いの条件は期待値で書いて

$$ \mu_a\,\mathbb{E}\big[(a-m)^+\big] = \mu_r\,\mathbb{E}\big[(m-a)^+\big] $$

ここで $(u)^+ = \max(u,0)$ です。左辺は「入力が $m$ を超えた分」の平均(アタック方向の駆動力)、右辺は「$m$ を下回った分」の平均(リリース方向の駆動力)です。$\mu_a \gg \mu_r$ なら、左辺の $\mathbb{E}[(a-m)^+]$ が小さくないと釣り合わないので、$m$ は分布の上側に位置することになります。

$a$ が平均 0、標準偏差 $\sigma_a$ の正規分布に従うと仮定し、$t = m/\sigma_a$ と正規化すると、部分期待値の公式 $\mathbb{E}[(a-m)^+] = \sigma_a\{\phi(t) – t\,Q(t)\}$、$\mathbb{E}[(m-a)^+] = \sigma_a\{\phi(t) + t\,\Phi(t)\}$ を使って

$$ \begin{equation} \frac{\mu_a}{\mu_r} = \frac{\phi(t) + t\,\Phi(t)}{\phi(t) – t\,Q(t)} \end{equation} $$

を $t$ について解けばバイアスが求まります($\phi$ は標準正規密度、$\Phi$ は累積分布、$Q = 1-\Phi$)。数値解は $\mu_a/\mu_r = 10$ で $t = 0.90$、$= 40$ で $t = 1.41$、$= 100$ で $t = 1.72$ です。

これが意味するのは、アタック/リリース比を大きくするほど、AGC は「平均レベル」ではなく「ピークに近いレベル」を目標に据えるということです。事実上のピーク検波器として振る舞い始めるわけです。ADC 保護の観点ではむしろ好都合ですが、目標振幅 $A_t$ を設計するときに「実効値が $A_t$ になる」と思い込んでいると、実効値が想定より数 dB 低くなり、ENOB を損します。この事実を知っていれば、$A_t$ をあらかじめ補正できます。

なお実際の変調波の対数包絡線は正規分布ではなく、深いヌルのせいで左に裾を引きます。16QAM(ロールオフ 0.35)の実波形で測ると、$\mu_a/\mu_r = 40$ のときのバイアスは約 4.2 dB で、正規分布仮定の 6.5 dB より小さく出ます。理論式は「向きと桁」を与える目安として使うのが正しい姿勢です。

左: アタック/リリース比に対する平衡レベルの上方バイアス。正規分布仮定の理論曲線と 16QAM 実波形の実測点(3.1/4.2/4.8 dB)。右: 対数包絡線のヒストグラムと各比の平衡点の位置

左のグラフを見ると、バイアスは $\mu_a/\mu_r$ の対数に対してほぼ直線的に増え、比が 1(対称)ならゼロ、100 倍にすると正規分布仮定で 7.9 dB にも達します。ただし 16QAM 実波形の実測点(赤丸)は理論曲線より一貫して低く、$\mu_a/\mu_r = 40$ で 4.2 dB(理論 6.5 dB)です。理由は右のヒストグラムにあります。実波形の対数包絡線は $-10$ dB より下に長い裾(RRC の遷移で沈む区間)を持つ一方、上側は $+7$ dB あたりで切れており、同じ分散でも正規分布(灰色の曲線)ほど上側に伸びていません。上側の裾が短いぶん、釣り合いの点も上に行きにくいわけです。設計では「比を上げるほどピーク寄りに寄る」という向きだけを理論から取り、絶対値は実波形で測るのが安全です。

検波器の選び方

もう一点、非対称性と密接に関係するのが検波器の種類です。

  • ピーク検波: 包絡線の最大値に追従。クリップ保護には直結しますが、PAPR の大きい信号(OFDM)ではピークが稀にしか出ないので推定が暴れます。
  • RMS(平均電力)検波: $\hat{P}[n]$ を平滑化して使う方式。QAM/OFDM の目標レベル設定は実効値基準で行うのが自然なので、これが標準です。ただしピークは別途ヘッドルームで守る必要があります。
  • 対数検波器(ログアンプ): RF 領域で $\log$ を直接取るデバイス。80〜100 dB のダイナミックレンジを一発で扱えるので、対数領域 AGC と相性が抜群です。RSSI(受信信号強度表示)出力もここから取ります。

実装では、RMS 検波でループを回しつつ、別系統の高速ピーク検出でクリップを検知したら大きな利得ステップを即座に打つ(fast attack override)という二段構えがよく使われます。

さて、ここまで「目標振幅 $A_t$」を天下り的に扱ってきました。この値は何を基準に決めればよいのでしょうか。答えは ADC の側にあります。

AGC と ADC — 有効ビット数を最大化する目標レベル

量子化雑音とバックオフ

$B$ ビット、フルスケール $\pm V_{FS}$ の ADC を考えます。量子化ステップは

$$ \Delta = \frac{2V_{FS}}{2^B} $$

量子化誤差が $[-\Delta/2, \Delta/2]$ の一様分布とみなせるとき、その分散は

$$ \sigma_q^2 = \frac{1}{\Delta}\int_{-\Delta/2}^{\Delta/2} u^2\,du = \frac{\Delta^2}{12} $$

信号の実効値を $\sigma_y$ とし、バックオフ(フルスケールに対する実効値の余裕)を

$$ \begin{equation} \mathrm{BO}\,[\mathrm{dB}] = 20\log_{10}\frac{V_{FS}}{\sigma_y} \end{equation} $$

と定義します。すると量子化 SNR は

$$ \mathrm{SNR}_q = \frac{\sigma_y^2}{\Delta^2/12} = \frac{\sigma_y^2 \cdot 12 \cdot 2^{2B}}{4V_{FS}^2} = 3\cdot 2^{2B}\left(\frac{\sigma_y}{V_{FS}}\right)^2 $$

分母の $\Delta^2/12 = (2V_{FS}/2^B)^2/12 = V_{FS}^2/(3\cdot 2^{2B})$ を使いました。dB に直すと

$$ \begin{equation} \mathrm{SNR}_q\,[\mathrm{dB}] = 10\log_{10}3 + 20B\log_{10}2 – \mathrm{BO} = 4.77 + 6.02B – \mathrm{BO} \end{equation} $$

よく知られた「フルスケール正弦波で $6.02B + 1.76$ dB」は、正弦波の実効値がピークの $1/\sqrt2$($\mathrm{BO} = 3.01$ dB)である場合の特殊解であることが確認できます。

この式はバックオフ 1 dB につき SNR が 1 dB 悪化することを言っています。ならばバックオフはゼロが最良でしょうか。もちろん違います。バックオフを詰めればクリップが始まり、こんどはクリップ歪みが SNR を急激に潰します。

最適バックオフと ENOB の山

したがって、実効的な SNDR(信号対雑音+歪み比)は

$$ \begin{equation} \frac{1}{\mathrm{SNDR}} = \frac{1}{\mathrm{SNR}_q(\mathrm{BO})} + \frac{1}{\mathrm{SNR}_{\rm clip}(\mathrm{BO})} \end{equation} $$

の形になり、バックオフに対して山型の曲線を描きます。左(BO 小)ではクリップ、右(BO 大)では量子化が支配する、典型的なトレードオフです。有効ビット数は

$$ \begin{equation} \mathrm{ENOB} = \frac{\mathrm{SNDR}[\mathrm{dB}] – 1.76}{6.02} \end{equation} $$

で測ります。複素ガウス的な信号(帯域制限された QAM や OFDM は中心極限定理でこれに近づきます)を入力したときの数値実験結果は次のとおりです。

ADC ビット数 $B$ 最適バックオフ 最大 SNDR ENOB 理想値 $6.02B+1.76$
8 9.0 dB 40.5 dB 6.43 49.9 dB
10 10.5 dB 50.9 dB 8.16 62.0 dB
12 12.0 dB 62.0 dB 10.01 74.0 dB

読み取れることが 2 つあります。第一に、最適バックオフは 9〜12 dB 程度で、ビット数が増えるほどわずかに深くなります(分解能が上がると、相対的にクリップのほうが痛くなるため)。ただし山頂は左右対称ではありません。最適点からバックオフを 1 dB 深くする側では損失が 0.5〜1.0 dB にとどまるのに対し、1 dB 詰める側では 0.7〜1.8 dB、2 dB 詰めると 4.9〜5.9 dB と一気に落ちます。クリップ歪みの立ち上がりが急峻なためで、目標レベルは山頂ちょうどではなく、わずかに深めに置くのが実装の定石です。第二に、この最適点でも ENOB は公称ビット数より 約 2 ビット少ない。これは「PAPR のある信号を有限のフルスケールに詰め込む代償」であり、避けられません。

AGC の目標振幅 $A_t$ は、この最適バックオフを実現する値に設定します。 12 ビット ADC でフルスケール 1 V なら、$A_t = 1 \times 10^{-12/20} = 0.251$ V(実効値)です。AGC の役目は、入力が 80 dB 動いてもこの 0.251 V を維持し続けることだと言い換えられます。

バックオフを外すとどうなるか

最適点から外れたときの損失も見ておきましょう。8 ビット ADC の場合、バックオフ 6 dB(詰めすぎ)では SNDR 30.8 dB、バックオフ 18 dB(余らせすぎ)では 31.9 dB。どちらも最適点の 40.5 dB から 9〜10 dB の劣化です。16QAM の BER $10^{-4}$ に必要な SNR は約 19 dB なので、この 10 dB の損失はリンクマージンをまるごと食い潰します。

AGC の定常誤差が $+2$ dB(バックオフが深くなる側)にずれた場合、SNDR の損失は 1.4〜2.0 dB にとどまります。しかし $-2$ dB(詰まる側)にずれると 4.9〜5.9 dB も失います。AGC に小数点以下の精度は要らないが、詰めすぎ側への大外しは許されない — この非対称な要求が、目標レベルをやや深めに置いたうえで実装をシンプルに保つ根拠になります。

オーバーサンプリングの効き目

もう一つ、実務で効く事実を挙げておきます。量子化雑音は白色に近いので、ナイキスト帯域全体に広がっています。信号帯域がサンプリング周波数よりずっと狭ければ、整合フィルタ(またはデシメーションフィルタ)が帯域外の量子化雑音を捨ててくれます。オーバーサンプリング比を $\mathrm{OSR}$ とすると

$$ \begin{equation} \mathrm{SNR}_{q,\rm eff} = \mathrm{SNR}_q + 10\log_{10}\mathrm{OSR} \end{equation} $$

シンボルあたり 8 サンプル、ロールオフ 0.35 なら実効 OSR は $8/1.35 \approx 5.9$ で、約 7.7 dB の得です。ただしクリップ歪みは信号帯域内にも落ちるので、この処理利得はバックオフを詰めすぎた領域では効きません。オーバーサンプリングは「バックオフを深めに取る余裕をくれる」と理解するのが正確です。

ADC 側の要求が固まりました。では、ループを速くすることの代償 — AGC 歪み — を定量化しましょう。

AGC 歪み — ループ帯域が変調の包絡線を食うとき

何が壊れるのか

先に導いたとおり、AGC は対数包絡線に対するハイパスフィルタ $H_{\rm out}(z) = \frac{z-1}{z-1+\mu}$ です。遮断周波数 $f_c$ より低い周波数成分は除去されます。ここで問題になるのが、変調信号の包絡線もまた、低い周波数成分を持っていることです。

16QAM のシンボル点は 3 通りの振幅 $\sqrt2 d,\ \sqrt{10} d,\ \sqrt{18} d$ を取ります(平均電力 1 に正規化すると $0.447,\ 1.000,\ 1.342$)。対数振幅の標準偏差は 0.409 ネーパ、dB でいえば 3.55 dB です。さらにルートレイズドコサイン(RRC)でパルス整形すると、シンボル間の遷移で包絡線が沈む区間ができるため、オーバーサンプリングした波形の対数包絡線の標準偏差は $\sigma_a = 0.528$ ネーパ $= 4.59$ dB まで広がります(PAPR は 6.13 dB、クレストファクタ 2.02)。

この揺れを AGC が「除去すべき変動」と誤認して打ち消すと、シンボルの振幅情報が消えます。16QAM なら、内側リングと外側リングの区別が曖昧になり、コンスタレーションは円環状に潰れます。

残留 EVM の定量化 — $\sqrt{B_n}$ 則

どれくらい壊れるかを見積もりましょう。対数包絡線 $a[n]$ を、分散 $\sigma_a^2$ を持ち、帯域 $B_{\rm env}$ にわたっておおむね平坦なパワースペクトル密度 $S_a(f) = \sigma_a^2 / B_{\rm env}$ を持つ広義定常過程とモデル化します。AGC が追従してしまう成分(=利得の変動)の分散は、$H_{ag}$ を通した出力なので

$$ \mathrm{Var}[\Delta G] = \int_0^{B_{\rm env}} |H_{ag}(f)|^2 S_a(f)\,df \approx \frac{\sigma_a^2}{B_{\rm env}} \cdot B_n $$

ここで等価雑音帯域の定義 $\int |H_{ag}|^2 df = B_n |H_{ag}(0)|^2 = B_n$ を使いました。振幅の相対誤差は小さい変動に対して $e^{\Delta G} – 1 \approx \Delta G$ なので、EVM(誤差ベクトル振幅の実効値)は

$$ \begin{equation} \boxed{\;\mathrm{EVM} \approx \sigma_a\sqrt{\frac{B_n}{B_{\rm env}}}\;} \end{equation} $$

EVM がループ雑音帯域の平方根に比例する、というのが結論です。$\mu$ を 4 倍にすれば EVM は 2 倍になります。

数値実験(16QAM、ロールオフ 0.35、8 サンプル/シンボル)で係数を同定すると、$B_{\rm env} \approx 1.1 R_s$($R_s$ はシンボルレート)とすればよいことがわかりました。つまり対数包絡線の実効帯域はおおよそシンボルレートに等しいという、直感どおりの結果です。したがって実用式は

$$ \begin{equation} \mathrm{EVM} \approx \sigma_a\sqrt{\frac{B_n}{R_s}}, \qquad \sigma_a \approx 0.53\ (\text{16QAM, RRC } \beta=0.35) \end{equation} $$

検証結果を並べます(シミュレーション値 / 予測値)。

$\mu$ $B_n/R_s$ 実測 EVM 予測 EVM($B_{\rm env}=1.1R_s$)
$3\times10^{-4}$ $6.0\times10^{-4}$ 1.12 % 1.23 %
$1\times10^{-3}$ $2.0\times10^{-3}$ 2.20 % 2.25 %
$3\times10^{-3}$ $6.0\times10^{-3}$ 3.90 % 3.90 %
$1\times10^{-2}$ $2.0\times10^{-2}$ 7.16 % 7.14 %
$3\times10^{-2}$ $6.1\times10^{-2}$ 12.33 % 12.43 %

$B_n/R_s$ を 100 倍に振ってもずれは最大 10 %(中央付近では 1 % 未満)に収まっており、$\sqrt{B_n}$ 則が実用的な精度で成り立つことが確認できます。ラフな見積もりなら $B_{\rm env} = R_s$ として $\mathrm{EVM} \approx \sigma_a\sqrt{B_n/R_s}$ を使えば十分です(5 % ほど大きめに出る安全側の見積もりになります)。

設計則

EVM 目標から必要なループ帯域が逆算できます。$\mathrm{EVM} \le \eta$ を要求すると

$$ \begin{equation} B_n \le R_s \left(\frac{\eta}{\sigma_a}\right)^2 \end{equation} $$

$\eta = 1\%$、$\sigma_a = 0.53$ なら $B_n \le 3.6\times 10^{-4} R_s$、すなわち $B_n \lesssim R_s/2800$。$\eta = 3\%$ でも $B_n \lesssim R_s/310$ です。実務でよく言われる

AGC のループ帯域はシンボルレートの 1/1000 以下に取れ

という経験則が、ここから定量的に裏づけられます。シンボルレート 10 MHz なら $B_n \le 10$ kHz、時定数にして数十マイクロ秒のオーダーです。

「速さ」と「歪み」の両立 — 実務の答え

しかし先ほど、バーストのアタックは数マイクロ秒で決めたいと書きました。$R_s/1000$ の帯域では 100 マイクロ秒近くかかってしまいます。この矛盾を実務ではどう解くのでしょうか。

主に 3 つの手が使われます。

  1. 二段階 AGC(coarse / fine): バースト先頭のプリアンブル区間だけ $\mu$ を大きくして一気に合わせ、データ区間に入ったら $\mu$ を小さく切り替える(またはループをフリーズする)。無線 LAN の受信機はまさにこの方式で、ショートプリアンブルで粗調整、ロングプリアンブルで微調整し、以降はほぼ固定利得で 1 バーストを処理します。
  2. フィードフォワード推定: プリアンブルの受信電力を直接測って必要な利得を一発で計算する(閉ループを回さない)。収束時間の概念がなくなるので最速ですが、推定誤差がそのまま残ります。
  3. 後段でのデジタル正規化: 復調器側の適応等化器や搬送波再生ループが振幅を正規化してくれるので、AGC は「ADC を守る」ことだけに専念し、精度は $\pm 1\sim2$ dB でよい、と割り切る。実際、AGC に EVM 級の精度を求める必要はほとんどありません。

OFDM の場合の追加事情

OFDM では話がさらに厳しくなります。OFDM シンボルの時間波形は多数のサブキャリアの重ね合わせなので、振幅はほぼ複素ガウス、包絡線はレイリー分布に従い、PAPR は 10 dB を超えることも珍しくありません(詳しくは OFDMのPAPR問題と低減手法 を参照)。ここで AGC が1 OFDM シンボルの内側で利得を動かすと、時間領域の乗算はサブキャリアの畳み込みに相当するため、サブキャリア間干渉(ICI)が発生します。直交性そのものが壊れるので、被害は単なる EVM 劣化にとどまりません。

したがって OFDM 受信機では、

$$ \begin{equation} f_c \ll \frac{1}{T_{\rm OFDM}} = \Delta f \quad (\text{サブキャリア間隔}) \end{equation} $$

を守る、もしくはシンボル境界でのみ利得を更新する(シンボル内では完全にフリーズする)設計が採られます。後者は「利得は階段状にしか動かない」ことを保証するので、ICI の観点で安全です。

理論が一通り揃いました。ここからは Python で実際にループを回し、これまでの式が本当に成り立つかを一つずつ確かめます。

Python での実装

1. リニア AGC と対数 AGC のステップ応答

まず、この記事の出発点だった「リニア AGC の破綻」と「対数 AGC の入力レベル非依存性」を、$-60$ dBm 相当から $-10$ dBm 相当への 50 dB ステップで比較します。

import numpy as np
import matplotlib, matplotlib.pyplot as plt

for cand in ["Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", "IPAexGothic", "Meiryo"]:
    if any(cand == f.name for f in matplotlib.font_manager.fontManager.ttflist):
        plt.rcParams["font.family"] = cand
        break
plt.rcParams["axes.unicode_minus"] = False

N = 2400
x = np.full(N, 10 ** (-60 / 20.0))      # -60 dBFS 相当の一定振幅
x[N // 2:] = 10 ** (-10 / 20.0)         # 途中で 50 dB のステップ

def agc_log(x, mu, At=1.0, G0=None):
    """対数領域AGC: G[n+1] = G[n] + mu * (ln At - ln|y|)"""
    G = np.log(At / abs(x[0])) if G0 is None else G0
    y = np.empty(len(x)); Gs = np.empty(len(x))
    for n, v in enumerate(x):
        yn = np.exp(G) * v
        y[n], Gs[n] = yn, G
        G += mu * (np.log(At) - np.log(abs(yn) + 1e-300))
    return y, Gs

def agc_lin(x, mu, At=1.0, g0=1.0):
    """リニアAGC: g[n+1] = g[n] + mu * (At - |y|)"""
    g = g0
    y = np.empty(len(x)); gs = np.empty(len(x))
    for n, v in enumerate(x):
        yn = g * v
        y[n], gs[n] = yn, g
        g = max(g + mu * (At - abs(yn)), 1e-12)   # 負の利得は物理的に無いのでクランプ
    return y, gs

# 3方式とも「利得1」から起動して、収束の速さを同じ土俵で比べる
y_log, _ = agc_log(x, mu=0.05, G0=0.0)
y_l1, _ = agc_lin(x, mu=0.3)     # 強信号側で安定余裕を確保するように選んだ mu
y_l2, _ = agc_lin(x, mu=100.0)   # 弱信号側で速く収束するように選んだ mu

続けて、3 つの応答を dB で描画します。

def db(v):
    return 20 * np.log10(np.abs(v) + 1e-300)

t = np.arange(N)
fig, ax = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
ax[0].plot(t, db(x), lw=1.5, color="k")
ax[0].set_ylabel("入力振幅 [dB]")
ax[0].set_title("入力: 50 dB のステップ(弱信号 → 強信号)")
ax[0].grid(alpha=0.3)

ax[1].plot(t, db(y_log), label="対数AGC ($\\mu$=0.05)", lw=2)
ax[1].plot(t, db(y_l1), label="リニアAGC ($\\mu$=0.3, 強信号基準)", lw=1.5, ls="--")
ax[1].plot(t, db(y_l2), label="リニアAGC ($\\mu$=100, 弱信号基準)", lw=1.5, ls=":")
ax[1].axhline(0, color="r", lw=1, alpha=0.7)
ax[1].text(20, 4, "目標レベル 0 dB", color="r", fontsize=9)
ax[1].set_ylim(-80, 60)
ax[1].set_xlabel("サンプル番号 $n$")
ax[1].set_ylabel("AGC出力振幅 [dB]")
ax[1].set_title("AGC出力: 対数AGCだけが両方のレベルで正しく動く")
ax[1].legend(loc="lower right", fontsize=9)
ax[1].grid(alpha=0.3)
plt.tight_layout(); plt.show()

k = N // 2
print("対数AGC   : 起動から77サンプル後 %.2f dB / ステップ後77サンプル %.2f dB"
      % (db(y_log[77]), db(y_log[k + 77])))
print("リニアAGC(mu=0.3)  : ステップ直前(1200サンプル後)でまだ %.1f dB の誤差" % db(y_l1[k - 1]))
print("リニアAGC(mu=100)  : 起動から77サンプル後 %.2f dB / ステップ後の最終出力 %.1f dB"
      % (db(y_l2[77]), db(y_l2[-1])))

50 dB ステップ入力に対する 3 方式の応答。対数 AGC は弱信号側でも強信号側でも同じ速さで 0 dB に収束、μ=0.3 のリニア AGC は弱信号で極端に遅く、μ=100 のリニア AGC はステップ後に発散する

グラフを見ると、緑の対数 AGC だけが上下 2 つの入力レベルで同じ形の立ち上がりを描いていることが一目でわかります。青の破線(リニア $\mu=0.3$)は弱信号区間でだらだらと這い上がり、1200 サンプル経っても目標に届いていません。薄紫の点(リニア $\mu=100$)はステップ前こそ最速で合わせますが、ステップ後は $+30$ dB と $-250$ dB(軸外)の間を往復する発散状態に入ります。

出力される数値とグラフから、3 つのことが読み取れます。第一に、対数 AGC(実線)は弱信号側でも強信号側でもまったく同じ速さで 0 dB に収束しています。起動直後(入力 $-60$ dB、初期誤差 60 dB)でも 77 サンプル後に $-1.16$ dB、ステップ直後(誤差 50 dB)でも 77 サンプル後に $+0.96$ dB。$n_s = \ln(50)/(-\ln 0.95) = 76.3$ という予測どおりで、応答が入力レベルにまったく依存しません。第二に、強信号側で安定余裕を確保するように $\mu = 0.3$ を選んだリニア AGC(破線)は、弱信号区間では $\mu A_x = 3\times10^{-4}$ しか効かず時定数が 3333 サンプルにもなるため、1200 サンプル経っても $-10.4$ dB の誤差が残っています。第三に、弱信号側で速く収束するように $\mu = 100$ を選んだリニア AGC(点線)は、弱信号区間では 77 サンプルでぴたりと合わせる一方、ステップ直後に $\mu A_x = 31.6 \gg 2$ となって発散します。利得が負側に振り切れて $10^{-12}$ にクランプされ、出力は $-250$ dB — 受信機は完全に沈黙します。同じ回路が、入力レベルによって「遅すぎる」か「死ぬ」かのどちらかにしかならない。これが「リニア AGC を使ってはいけない」ことの実演です。

2. 時定数の理論式を検証する

次に、$\tau = -T_s/\ln(1-\mu)$ と整定時間の式が正しいかを、複数の $\mu$ で確かめます。

import numpy as np
import matplotlib.pyplot as plt

mus = [0.002, 0.005, 0.02, 0.05, 0.2]
N = 3000
x = np.full(N, 10 ** (-60 / 20.0)); x[100:] = 10 ** (-10 / 20.0)

plt.figure(figsize=(10, 6))
print(" mu     tau[理論]  n_s(50->1dB)[理論]  n_s[実測]")
for mu in mus:
    y, _ = agc_log(x, mu=mu)
    err_db = 20 * np.log10(np.abs(y[100:]) + 1e-300)      # 目標=1 なので出力dB=誤差dB
    tau = -1.0 / np.log(1 - mu)
    ns_th = np.log(50 / 1.0) / (-np.log(1 - mu))
    ns_meas = int(np.argmax(err_db < 1.0))
    plt.semilogy(np.arange(len(err_db)), np.abs(err_db) + 1e-3,
                 label="$\\mu$=%.3f ($\\tau$=%.0f サンプル)" % (mu, tau))
    print("%6.3f  %8.1f  %14.0f  %10d" % (mu, tau, ns_th, ns_meas))

plt.axhline(1.0, color="r", ls="--", lw=1, label="整定判定 1 dB")
plt.xlim(0, 800); plt.ylim(1e-2, 1e2)
plt.xlabel("ステップ後のサンプル数"); plt.ylabel("利得誤差の絶対値 [dB]")
plt.title("対数AGCの整定: 誤差は片対数で直線 = 幾何級数減衰")
plt.legend(fontsize=9); plt.grid(alpha=0.3, which="both")
plt.tight_layout(); plt.show()

左: 5 通りの μ に対する利得誤差の片対数プロットで、いずれも直線となり幾何級数減衰を示す。右: 50 dB から 1 dB への整定サンプル数の理論値と実測値の棒グラフ比較(1954/1955, 780/781, 194/194, 76/77, 18/18)

左のグラフでは 5 本の誤差曲線がすべて片対数軸上で直線になっており、傾きが $\ln(1-\mu)$ に一致しています。右の棒グラフは理論式と実測の整定サンプル数を並べたもので、$\mu = 0.002$ の 1954/1955 から $\mu = 0.2$ の 18/18 まで、4 桁近い範囲にわたって最大 1 サンプルしかずれていません

片対数プロットで誤差がきれいな直線になっていることが、$\varepsilon[n] = (1-\mu)^n\varepsilon[0]$ という幾何級数減衰の直接的な証拠です。傾きは $\ln(1-\mu)$ に一致し、$\mu$ を 10 倍にすると直線の傾きも約 10 倍急になります。表示された実測整定時間(1955, 781, 194, 77, 18 サンプル)は理論値(1954, 780, 194, 76, 18)と1 サンプル以内で一致しており、$n_s = \ln(\Delta/\delta)/(-\ln(1-\mu))$ がそのまま実用式として使えることが確認できます。$\mu = 0.002$ では 2000 サンプル近くかかるのに対し $\mu = 0.2$ なら 18 サンプル — 速度が 100 倍違うわけですが、この「速さ」の代償を次以降の節で見ていきます。

3. ハイパス特性を周波数領域で確認する

「AGC は対数包絡線のハイパスフィルタ」という主張を、伝達関数の実測で確かめます。

import numpy as np
import matplotlib.pyplot as plt

w = np.logspace(-5, np.log10(np.pi), 800)
plt.figure(figsize=(10, 6))
for mu in [0.001, 0.01, 0.1]:
    z = np.exp(1j * w)
    H_out = (z - 1) / (z - 1 + mu)          # 対数包絡線 → 出力対数振幅
    H_ag = -mu / (z - (1 - mu))             # 対数包絡線 → 対数利得
    fc = mu / (2 * np.pi)                   # 正規化3dB周波数
    Bn = mu / (2 * (2 - mu))                # 正規化等価雑音帯域
    plt.semilogx(w / (2 * np.pi), 20 * np.log10(np.abs(H_out)),
                 lw=2, label="出力 $H_{out}$ ($\\mu$=%.3f)" % mu)
    plt.semilogx(w / (2 * np.pi), 20 * np.log10(np.abs(H_ag)),
                 lw=1.2, ls="--", label="利得 $H_{ag}$ ($\\mu$=%.3f)" % mu)
    plt.axvline(fc, color="gray", lw=0.8, ls=":")
    print("mu=%.3f  fc/fs=%.3e  Bn/fs=%.3e  Bn/fc=%.3f" % (mu, fc, Bn, Bn / fc))

plt.axhline(-3, color="r", lw=1, alpha=0.6)
plt.text(2e-5, -2.5, "-3 dB", color="r", fontsize=9)
plt.ylim(-60, 6); plt.xlim(1e-5, 0.5)
plt.xlabel("正規化周波数 $f/f_s$"); plt.ylabel("利得 [dB]")
plt.title("AGCループの周波数応答: 出力はハイパス、利得はローパス")
plt.legend(fontsize=8, loc="lower right"); plt.grid(alpha=0.3, which="both")
plt.tight_layout(); plt.show()

AGC ループの周波数応答。μ=0.001, 0.01, 0.1 について、出力への伝達関数 H_out がハイパス、利得への伝達関数 H_ag がローパスとなり、遮断周波数 fc = μfs/2π で交差する

3 組の曲線が示すのは、$\mu$ を変えても形はまったく同じで、遮断周波数だけが平行移動するということです。$\mu$ を 10 倍にすれば $f_c$ も 10 倍の周波数に動きます。実線(出力への伝達)と破線(利得への伝達)が必ず $-3$ dB の線上で交差しているのは、$H_{\rm out}(z) = 1 + H_{ag}(z)$ という関係から両者が補フィルタの組(ハイパスとローパス)になっており、交点では電力がちょうど半分ずつになるためです。

実線(出力への伝達)は低域で $+20$ dB/dec の傾きを持ち、直流に向かって完全に落ちるハイパス特性です。破線(利得への伝達)はその鏡像でローパス、直流で 0 dB($=-1$ 倍)。両者は $f_c = \mu f_s/2\pi$ で交差します。表示された $B_n/f_c$ は $\mu = 0.001$ で $1.572$、$\mu = 0.01$ で $1.579$ と、一次系に共通の $\pi/2 = 1.571$ にきわめて近い値になりました($\mu = 0.1$ では $1.653$ とやや外れますが、これは $\mu \ll 1$ の近似から離れたためです)。この図が示すのは、AGC の設計とは遮断周波数 $f_c$ をどこに置くかという一点に尽きるということです。フェージングの変動周波数より上、変調の包絡線変動より下 — この隙間に $f_c$ を挿し込むのが仕事です。

4. ループ遅延と安定限界

閉形式 $\mu_{\max} = 2\sin(\pi/(2(2D+1)))$ を、特性方程式の根を直接求めて検証します。

import numpy as np
import matplotlib.pyplot as plt

def max_root(mu, D):
    """z^{D+1} - z^D + mu = 0 の最大根絶対値"""
    c = np.zeros(D + 2)
    c[0] += 1.0; c[1] += -1.0; c[D + 1] += mu     # D=0 では c[1] に両方が乗る
    return np.max(np.abs(np.roots(c)))

Ds = np.arange(0, 9)
mu_num = []
for D in Ds:
    lo, hi = 1e-9, 2.0
    for _ in range(60):                            # 二分法で安定限界を探す
        m = 0.5 * (lo + hi)
        lo, hi = (m, hi) if max_root(m, D) < 1 else (lo, m)
    mu_num.append(lo)
mu_th = 2 * np.sin(np.pi / (2 * (2 * Ds + 1)))

plt.figure(figsize=(9, 5.5))
plt.plot(Ds, mu_num, "o", ms=9, label="数値解(根の絶対値=1 の境界)")
plt.plot(Ds, mu_th, "-", lw=2, label="閉形式 $2\\sin(\\pi/(2(2D+1)))$")
plt.plot(Ds[1:], np.pi / (2 * Ds[1:] + 1), "--", lw=1.2, label="漸近形 $\\pi/(2D+1)$")
plt.xlabel("ループ遅延 $D$ [サンプル]"); plt.ylabel("安定限界 $\\mu_{max}$")
plt.title("ループ遅延が AGC の速度上限を決める")
plt.legend(); plt.grid(alpha=0.3)
plt.tight_layout(); plt.show()
for D, a, b in zip(Ds, mu_num, mu_th):
    print("D=%d  数値=%.4f  閉形式=%.4f" % (D, a, b))

左: ループ遅延 D に対する安定限界 μmax。二分法による数値解と閉形式 2sin(π/(2(2D+1))) が完全に重なり、D=2 で 0.618(黄金比の逆数)。右: D=3 のときの特性方程式の根が μ を上げると単位円の外へ出る様子

左のグラフでは、赤丸(数値解)が青い実線(閉形式)の上に完全に乗っており、緑の安定領域が $D$ の増加とともに急速に痩せていくことがわかります。$D = 0$ で $\mu < 2$ 使えたものが、$D = 4$ ではもう $0.347$ までしか使えません。右は $D = 3$ での根の配置で、$\mu$ を 0.15 → 0.445 → 0.80 と上げるにつれて根が外へ膨らみ、$\mu = 0.445$ でちょうど単位円上($|z| = 1.000$)、$\mu = 0.80$ では $|z| = 1.129$ となって発散します。閉形式が予言する境界と根の挙動が完全に対応しています。

数値解と閉形式が小数点以下 4 桁まで完全に一致しました。$D=2$ で $0.6180$(黄金比の逆数)という特徴的な値が出ているのも確認できます。実用上の含意は明快で、ループ遅延が 1 サンプル増えるごとに使える $\mu$ はおよそ $\pi/(2D)$ まで絞られる、つまり遅延の逆数がループ帯域の天井になります。AGC の制御信号をアナログ VGA に送る経路(DAC のセトリング、RC フィルタ)が遅いと、いくらデジタル側で速い $\mu$ を書いても意味がないどころか、発振します。

5. 検波器の平滑化とリンギング($\alpha > 4\mu$)

判別式から導いた設計則を、ステップ応答で確認します。

import numpy as np
import matplotlib.pyplot as plt

def step_2nd(mu, alpha, N=3000):
    """検波平滑化つきループの誤差ステップ応答"""
    eps, f = 1.0, 1.0
    out = np.empty(N)
    for n in range(N):
        f = (1 - alpha) * f + alpha * eps       # 検波器の一次平滑化
        eps = eps - mu * f                      # 積分器
        out[n] = eps
    return out

mu = 0.01
plt.figure(figsize=(10, 6))
print("  alpha    4mu     最小値    オーバーシュート")
for alpha in [0.5, 0.08, 0.04, 0.02, 0.01, 0.005]:
    o = step_2nd(mu, alpha)
    plt.plot(o, lw=1.8, label="$\\alpha$=%.3f%s" % (alpha, " (=4$\\mu$)" if abs(alpha - 4*mu) < 1e-9 else ""))
    print("  %.3f  %.3f  %8.4f  %10.1f %%" % (alpha, 4 * mu, o.min(), -100 * min(o.min(), 0)))

plt.axhline(0, color="k", lw=0.8)
plt.xlim(0, 1200); plt.ylim(-0.8, 1.05)
plt.xlabel("サンプル番号"); plt.ylabel("正規化利得誤差")
plt.title("検波器が遅いとループがリンギングする(境界は $\\alpha = 4\\mu$)")
plt.legend(fontsize=9); plt.grid(alpha=0.3)
plt.tight_layout(); plt.show()

左: μ=0.01 のループに検波平滑化 α を加えたときのステップ応答。α が 4μ=0.04 以上なら単調収束、下回るとリンギングする。右: α ごとのオーバーシュート量の棒グラフ(0/0/0/6.5/29.6/68.0 %)

左のグラフでは、$\alpha = 0.5, 0.08, 0.04$ の 3 本(青系)が一度もゼロ線を下回らずに収束するのに対し、$\alpha = 0.02$ 以下(赤系)は下に沈んでから戻る振動を始めます。右の棒グラフで境界の鋭さがはっきりします。$\alpha = 0.04$($= 4\mu$)までオーバーシュートは厳密に 0 % で、そこを一段下げた $\alpha = 0.02$ で 6.5 %、さらに下げると 29.6 %、68.0 % と急増します。判別式 $\mathcal{D} = \alpha(\alpha – 4\mu)$ の符号が変わる点と、シミュレーションの遷移点が一致していることの直接的な確認です。

出力を見ると、$\alpha \ge 4\mu = 0.04$ の曲線はゼロを一度も下回らず(オーバーシュート 0 %)、単調に収束します。$\alpha = 0.02$ で 6.5 %、$\alpha = 0.01$ で 29.6 %、$\alpha = 0.005$ で 68 % と、$\alpha$ を下げるほど激しくリンギングします。判別式 $\mathcal{D} = \alpha(\alpha – 4\mu)$ が予測した境界と、シミュレーションの遷移点がぴったり一致していることがわかります。実装で「AGC がなんとなく暴れる」ときは、まず検波器の平均化時定数がループ時定数に近づいていないかを疑ってください。

6. アタック/リリース非対称の効果

バースト状の入力に対して、対称ループと非対称ループを比較します。

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(0)
N = 6000
env = np.full(N, 10 ** (-40 / 20.0))
env[1500:2500] = 10 ** (-5 / 20.0)     # 強い干渉バースト
env[4000:4200] = 10 ** (-55 / 20.0)    # 一瞬の深いフェージングヌル
x = env * (1 + 0.15 * rng.standard_normal(N))   # 振幅の細かい揺らぎ

def agc_asym(x, mu_a, mu_r, At=1.0):
    G = np.log(At / abs(x[0])); y = np.empty(len(x)); Gs = np.empty(len(x))
    for n, v in enumerate(x):
        yn = np.exp(G) * v
        y[n], Gs[n] = yn, G
        e = np.log(At) - np.log(abs(yn) + 1e-300)
        G += (mu_a if e < 0 else mu_r) * e       # 符号でステップ幅を切替
    return y, Gs

y_sym, G_sym = agc_asym(x, 0.02, 0.02)
y_asym, G_asym = agc_asym(x, 0.20, 0.005)
db = lambda v: 20 * np.log10(np.abs(v) + 1e-300)

続けて可視化し、ADC のクリップ回数を数えます。

fig, ax = plt.subplots(2, 1, figsize=(10, 7.5), sharex=True)
ax[0].plot(db(x), color="k", lw=0.8, label="入力包絡線")
ax[0].set_ylabel("入力 [dB]"); ax[0].legend(fontsize=9); ax[0].grid(alpha=0.3)
ax[0].set_title("入力: 定常 → 強バースト → 深いヌル")

ax[1].plot(db(y_sym), lw=1.2, label="対称 ($\\mu_a=\\mu_r=0.02$)")
ax[1].plot(db(y_asym), lw=1.6, label="非対称 ($\\mu_a$=0.2, $\\mu_r$=0.005)")
ax[1].axhline(6, color="r", ls="--", lw=1)
ax[1].text(50, 7, "ADCクリップ閾値(+6 dB)", color="r", fontsize=9)
ax[1].axhline(0, color="g", lw=1, alpha=0.6)
ax[1].set_ylim(-25, 30); ax[1].set_xlabel("サンプル番号"); ax[1].set_ylabel("AGC出力 [dB]")
ax[1].legend(fontsize=9); ax[1].grid(alpha=0.3)
ax[1].set_title("非対称AGC: 速いアタックでクリップ区間を短縮、遅いリリースでヌルを無視")
plt.tight_layout(); plt.show()

for name, y, G in [("対称  ", y_sym, G_sym), ("非対称", y_asym, G_asym)]:
    n_clip = int(np.sum(db(y) > 6))
    rise = (G[4000:4200].mean() - G[3500:3900].mean()) * 20 / np.log(10)
    print("%s: クリップサンプル数 = %4d, ヌル区間の平均出力 = %6.1f dB, 利得上昇 = %.1f dB"
          % (name, n_clip, db(y[4000:4200]).mean(), rise))

上: 定常→強い干渉バースト→深いフェージングヌルという入力包絡線。下: 対称ループと非対称ループの AGC 出力比較。非対称ループはバースト先頭のクリップを 135→10 サンプルに短縮し、短いヌルでは利得をほとんど上げない

下段を見ると、1500 サンプル地点のバースト立ち上がりで、緑(非対称)のスパイクが青(対称)より明らかに細いことがわかります。赤い破線(クリップ閾値 +6 dB)を超えている時間が、それだけ短いということです。逆に 4000 サンプル付近の短いヌルでは、青の対称ループが利得を持ち上げて出力を 0 dB 近くまで戻し、ヌルを抜けた瞬間には上げすぎた 11.3 dB 分がそのまま過剰利得になって出力が $+16$ dB まで跳ね上がり、クリップ閾値を大きく超えているのに対し、緑の非対称ループは $-10$ dB 付近に留まったまま「無視」しています。同じ方向の遅さが、片方では欠点、もう片方では美点になるという非対称設計の本質が現れています。

数値が非対称化の効果をはっきり示しています。バースト立ち上がりでのクリップサンプル数は、対称ループの 135 に対し非対称ループはわずか 10。$\mu_a$ を 0.02 から 0.2 へ 10 倍にした分だけ、理論式 $n_{\rm clip} = \ln(\Delta/H)/(-\ln(1-\mu_a))$ どおりにクリップ区間が 1 桁縮んでいます。一方、4000 サンプル付近の短いフェージングヌル($-15$ dB、200 サンプル)では、対称ループが利得を 11.3 dB も持ち上げて出力を $-3.6$ dB まで戻してしまうのに対し、非対称ループの利得上昇は 6.0 dB にとどまり、出力は $-10.5$ dB のままです。つまり非対称ループは短いヌルを「無視」している。対称ループのほうは律儀に追従した結果、ヌルを抜けた瞬間に 11 dB のオーバーシュートを起こします。速いアタックと遅いリリースの組み合わせが、クリップ保護と過渡耐性を同時に達成していることが視覚的に確認できます。

7. ADC バックオフと有効ビット数

AGC の目標レベルを決めるための、SNDR 対バックオフの山を描きます。

import numpy as np
import matplotlib.pyplot as plt

def sndr_vs_backoff(B, bo_db, N=400000, seed=3):
    """複素ガウス信号をBビットADCに通したときのSNDR[dB]"""
    rng = np.random.default_rng(seed)
    s = (rng.standard_normal(N) + 1j * rng.standard_normal(N)) / np.sqrt(2)
    VFS = 1.0
    sig = s * VFS * 10 ** (-bo_db / 20)          # 実効値をバックオフ分だけ下げる
    D = 2 * VFS / 2 ** B
    q = lambda u: np.clip(np.round(u / D) * D, -VFS + D / 2, VFS - D / 2)
    r = q(sig.real) + 1j * q(sig.imag)
    g = np.vdot(sig, r) / np.vdot(sig, sig)      # 最良線形近似を除いた残差が歪み
    err = r - g * sig
    return 10 * np.log10(abs(g) ** 2 * np.mean(np.abs(sig) ** 2) / np.mean(np.abs(err) ** 2))

bos = np.arange(3, 30, 0.5)
plt.figure(figsize=(10, 6))
for B in [8, 10, 12]:
    v = np.array([sndr_vs_backoff(B, b) for b in bos])
    i = int(v.argmax())
    plt.plot(bos, v, lw=2, label="%d ビット (最適BO=%.1f dB, ENOB=%.2f)"
             % (B, bos[i], (v[i] - 1.76) / 6.02))
    plt.plot(bos[i], v[i], "o", ms=8, color="k")
    plt.plot(bos, 6.02 * B + 4.77 - bos, ls=":", lw=1, color="gray")

plt.text(22, 20, "点線: 量子化のみの理論\n$6.02B+4.77-BO$", fontsize=9, color="gray")
plt.xlabel("バックオフ BO [dB](フルスケールに対する実効値の余裕)")
plt.ylabel("SNDR [dB]")
plt.title("ADCの実効SNRはバックオフに対して山型 — AGCの目標レベルは山頂に置く")
plt.legend(fontsize=9); plt.grid(alpha=0.3); plt.ylim(0, 70)
plt.tight_layout(); plt.show()

左: 8/10/12 ビット ADC の SNDR がバックオフに対して山型になるグラフ。山頂は 9.0/10.5/12.0 dB。右: 最適点からのずれに対する損失曲線で、詰めすぎ側が急峻、余らせすぎ側は緩やかという非対称

左のグラフで、右半分は点線(量子化のみの理論)にぴったり重なり、左半分では急落する — この 2 つの効き方の切り替わりが山を作ります。右のグラフはその山頂を拡大して損失として描いたもので、谷が明らかに非対称です。バックオフを深くする側(緑)では 1 dB あたり約 1 dB の割合でゆっくり損をするのに対し、詰める側(赤)は $-2$ dB で 5 dB 前後、$-3$ dB で 10 dB 以上を一気に失います。AGC の目標レベルを山頂ちょうどではなく数 dB 深めに置くべき理由が、この 1 枚に集約されています。

3 本の曲線すべてが明確な山を持ちます。右側では点線(量子化のみの理論式 $6.02B+4.77-\mathrm{BO}$)にぴったり重なっており、バックオフ 1 dB につき SNDR が 1 dB 落ちる関係が確認できます。左側では理論式から急速に離れ、クリップ歪みが支配します。山頂は 8 ビットで 9.0 dB、10 ビットで 10.5 dB、12 ビットで 12.0 dB のバックオフに位置し、そのときの ENOB はそれぞれ 6.43、8.16、10.01 — 公称ビット数より約 2 ビット少ないという、PAPR を持つ信号に共通の代償が現れています。AGC の目標振幅は、この山頂に対応する実効値に設定します。ただし右の図が示すとおり山頂は左右対称ではなく、バックオフを 1 dB 深くしたときの損失が 0.5〜1.0 dB なのに対し、1 dB 詰めたときは 0.7〜1.8 dB、2 dB 詰めれば 4.9〜5.9 dB に跳ね上がります。AGC の誤差は必ず「深いバックオフ側」に倒しておくのが安全です。

8. 16QAM 信号での AGC 歪み — EVM とコンスタレーション

いよいよ本命です。RRC 整形した 16QAM に AGC を掛け、ループ帯域を変えたときの EVM とコンスタレーションを見ます。

import numpy as np
import matplotlib.pyplot as plt

def rrc(beta, sps, span):
    """ルートレイズドコサインフィルタ(単位エネルギー)"""
    t = np.arange(-span * sps, span * sps + 1) / sps
    h = np.zeros_like(t)
    for i, ti in enumerate(t):
        if abs(ti) < 1e-12:
            h[i] = 1 - beta + 4 * beta / np.pi
        elif beta > 0 and abs(abs(ti) - 1 / (4 * beta)) < 1e-9:
            h[i] = beta / np.sqrt(2) * ((1 + 2 / np.pi) * np.sin(np.pi / (4 * beta))
                                        + (1 - 2 / np.pi) * np.cos(np.pi / (4 * beta)))
        else:
            h[i] = (np.sin(np.pi * ti * (1 - beta))
                    + 4 * beta * ti * np.cos(np.pi * ti * (1 + beta))) \
                   / (np.pi * ti * (1 - (4 * beta * ti) ** 2))
    return h / np.sqrt(np.sum(h ** 2))

rng = np.random.default_rng(7)
lev = np.array([-3, -1, 1, 3.0])
C = np.array([complex(i, q) for i in lev for q in lev])
C /= np.sqrt(np.mean(np.abs(C) ** 2))          # 平均電力1に正規化
sps, Nsym = 8, 20000
h = rrc(0.35, sps, 8)
idx = rng.integers(0, 16, Nsym)
up = np.zeros(Nsym * sps, complex); up[::sps] = C[idx]
tx = np.convolve(up, h)

a = np.log(np.abs(tx[2000:-2000]) + 1e-30)
print("対数包絡線 sigma_a = %.4f ネーパ = %.2f dB" % (a.std(), a.std() * 20 / np.log(10)))
print("PAPR = %.2f dB" % (10 * np.log10(np.max(np.abs(tx) ** 2) / np.mean(np.abs(tx) ** 2))))

生成した波形の統計量が、理論節で使った $\sigma_a = 0.528$ ネーパ(4.59 dB)、PAPR 6.13 dB と一致することを確認できます。この $\sigma_a$ が、次の EVM 予測式に入る唯一の信号側パラメータです。

def agc_qam(x, mu, alpha=0.5, At=1.0, G0=0.0):
    """RMS検波つき対数AGC(電力誤差に1/2を掛けて振幅基準のmuに揃える)"""
    G, P = G0, At ** 2
    y = np.empty(len(x), complex)
    for n, v in enumerate(x):
        yn = np.exp(G) * v
        y[n] = yn
        P = (1 - alpha) * P + alpha * abs(yn) ** 2
        G += mu * 0.5 * (np.log(At ** 2) - np.log(P + 1e-300))
    return y

G0 = -np.log(np.sqrt(np.mean(np.abs(tx) ** 2)))    # 収束済みの利得から開始
sigma_a = a.std()
mus = [3e-4, 1e-3, 3e-3, 1e-2, 3e-2]
evms, preds, bns = [], [], []
for mu in mus:
    y = agc_qam(tx, mu, G0=G0)
    z = np.convolve(y, h)[len(h) - 1:][::sps][:Nsym][3000:]   # 整合フィルタ+シンボル抽出
    s = C[idx][3000:]
    g = np.vdot(s, z) / np.vdot(s, s)
    evm = np.sqrt(np.mean(np.abs(z - g * s) ** 2) / np.mean(np.abs(g * s) ** 2))
    Bn_over_Rs = mu / (2 * (2 - mu)) * sps          # B_n を シンボルレート で正規化
    evms.append(evm); bns.append(Bn_over_Rs)
    preds.append(sigma_a * np.sqrt(Bn_over_Rs / 1.1))
    print("mu=%.4f  Bn/Rs=%.2e  EVM実測=%.2f%%  EVM予測=%.2f%%"
          % (mu, Bn_over_Rs, 100 * evm, 100 * preds[-1]))

出力される実測 EVM と予測 EVM は、どの $\mu$ でも 10 % 以内(中央付近では 1 % 未満)で一致します。とくに $\mu$ を 100 倍($3\times10^{-4} \to 3\times10^{-2}$)にしたとき EVM は 1.12 % → 12.33 %、つまり 11 倍にしかなっていません。線形なら 100 倍になるはずですから、$\sqrt{B_n}$ 則($\sqrt{100} = 10$ 倍)が実際に効いていることが確かめられます。この関係のおかげで、設計時に EVM 目標からループ帯域を逆算できます。

fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].loglog(bns, np.array(evms) * 100, "o-", ms=8, lw=2, label="実測 EVM")
ax[0].loglog(bns, np.array(preds) * 100, "s--", lw=1.5, label="予測 $\\sigma_a\\sqrt{B_n/B_{env}}$")
ax[0].set_xlabel("正規化ループ雑音帯域 $B_n/R_s$"); ax[0].set_ylabel("EVM [%]")
ax[0].set_title("AGC歪み: EVMは $\\sqrt{B_n}$ に比例")
ax[0].legend(fontsize=9); ax[0].grid(alpha=0.3, which="both")

for k, mu in enumerate([3e-4, 3e-2]):
    y = agc_qam(tx, mu, G0=G0)
    z = np.convolve(y, h)[len(h) - 1:][::sps][:Nsym][3000:8000]
    s = C[idx][3000:8000]
    g = np.vdot(s, z) / np.vdot(s, s); z = z / g
    ax[k + 1].plot(z.real, z.imag, ".", ms=1.2, alpha=0.5)
    ax[k + 1].plot(C.real, C.imag, "r+", ms=12, mew=2)
    ax[k + 1].set_xlim(-2, 2); ax[k + 1].set_ylim(-2, 2); ax[k + 1].set_aspect("equal")
    ax[k + 1].set_title("16QAM コンスタレーション ($\\mu$=%.4f)" % mu)
    ax[k + 1].set_xlabel("同相成分 I"); ax[k + 1].set_ylabel("直交成分 Q")
    ax[k + 1].grid(alpha=0.3)
plt.tight_layout(); plt.show()

左: EVM 対正規化ループ雑音帯域の両対数プロットで実測と予測が重なり傾き 1/2 の直線になる。中央: μ=0.0003 の 16QAM コンスタレーション(16 点が明瞭)。右: μ=0.03 では点が半径方向に伸びて崩れる

中央と右のコンスタレーションを見比べると、AGC 歪みの「指紋」がよくわかります。$\mu = 3\times10^{-4}$(中央)では各点が赤い円の中に小さく収まっているのに対し、$\mu = 3\times10^{-2}$(右)では各点が原点方向に向かって細長く伸びた楔形になっています。角度方向にはほとんど広がっていません。振幅だけが誤って変調されている、つまり AGC が包絡線を食っている証拠です。左のグラフでは実測(青丸)と予測(赤四角)がほぼ重なり、$\sqrt{B_n}$ 則が 2 桁の範囲で成り立っています。

左のグラフは両対数軸で傾き $1/2$ の直線になり、$\mathrm{EVM} \propto \sqrt{B_n}$ が一目でわかります。中央($\mu = 3\times10^{-4}$、$B_n \approx R_s/1700$)のコンスタレーションは 16 点がくっきり分離していますが、右($\mu = 3\times10^{-2}$、$B_n \approx R_s/16$)では点が半径方向に大きく広がり、内側リングと外側リングの境界が曖昧になりかけています。とくに半径方向に伸びて角度方向にはあまり広がらないのが AGC 歪みの指紋です。位相雑音なら角度方向に、加法性雑音なら等方的に広がるので、コンスタレーションの崩れ方から原因を切り分けられます。

9. 総合 — 入力レベル急変時の実効 SNR とビット誤り率

最後に、AGC の有無で受信機の実効 SNR がどう変わるかをまとめます。ここでは ADC の効果だけを分離して見たいので、アナログ段の SNR は 20 dB で一定とし、ADC 入力レベル(バックオフ)だけを振ります。ADC は 8 ビットです。

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc

B, snr_analog_db = 8, 20.0
bos = np.arange(3, 45, 1.0)
snr_adc = np.array([sndr_vs_backoff(B, b, N=120000) for b in bos])

def combine(snr_a_db, snr_b_db):
    """独立な雑音源の合成: 1/SNR = 1/SNR_a + 1/SNR_b"""
    return -10 * np.log10(10 ** (-snr_a_db / 10) + 10 ** (-snr_b_db / 10))

snr_eff = combine(snr_analog_db, snr_adc)
ber = 0.375 * erfc(np.sqrt(10 ** (snr_eff / 10) / 10))   # 16QAM の近似式

fig, ax = plt.subplots(1, 2, figsize=(13, 5))
ax[0].plot(bos, snr_adc, lw=1.5, ls="--", label="ADC単独 SNDR")
ax[0].plot(bos, snr_eff, lw=2.5, label="実効SNR(アナログ+ADC)")
ax[0].axhline(snr_analog_db, color="g", lw=1, ls=":", label="アナログSNR 20 dB")
i_best = int(snr_eff.argmax())
ax[0].axvline(bos[i_best], color="r", lw=1)
ax[0].text(bos[i_best] + 0.7, 5, "AGCが保つ動作点\nBO=%.0f dB" % bos[i_best], color="r", fontsize=9)
ax[0].set_xlabel("ADC入力のバックオフ [dB]"); ax[0].set_ylabel("SNR [dB]")
ax[0].set_title("AGCがなければ動作点はここを自由に動いてしまう")
ax[0].legend(fontsize=9); ax[0].grid(alpha=0.3); ax[0].set_ylim(0, 45)

ax[1].semilogy(bos, np.maximum(ber, 1e-10), lw=2.5, label="AGCなし(入力レベル任せ)")
ax[1].semilogy(bos, np.full_like(bos, max(ber[i_best], 1e-10)), lw=2, ls="--",
               label="AGCあり(常に最適点)")
ax[1].axhline(1e-4, color="gray", lw=1, ls=":")
ax[1].text(4, 1.4e-4, "BER = $10^{-4}$", color="gray", fontsize=9)
ax[1].set_ylim(1e-10, 1); ax[1].set_xlabel("ADC入力のバックオフ [dB]")
ax[1].set_ylabel("ビット誤り率 (16QAM)")
ax[1].set_title("AGCはBERを動作点の変動から切り離す")
ax[1].legend(fontsize=9); ax[1].grid(alpha=0.3, which="both")
plt.tight_layout(); plt.show()

print("最適BO=%.0f dB: 実効SNR=%.1f dB, BER=%.2e" % (bos[i_best], snr_eff[i_best], ber[i_best]))
for b in [4, 10, 25, 35, 40]:
    j = int(np.argmin(abs(bos - b)))
    print("  BO=%2d dB -> 実効SNR=%.1f dB, BER=%.2e" % (b, snr_eff[j], ber[j]))

左: アナログ SNR 20 dB と 8 ビット ADC を合成した実効 SNR がバックオフ 6〜20 dB で台地を作るグラフ。右: 対応する 16QAM の BER。AGC ありでは水平な直線、AGC なしでは動作点により 4 桁変動する

左のグラフで緑に塗られた帯が「AGC が動作点を置くべき場所」です。この中では実効 SNR(青)がアナログ SNR の 20 dB(緑の点線)にぴったり張り付いており、ADC が実質的に何も劣化させていません。灰色の破線(ADC 単独の SNDR)は帯の中で 40 dB 前後もあり、アナログ側の 20 dB に対して十分な余裕があるためです。右のグラフでは、AGC なし(青)の BER が動作点次第で $3\times10^{-6}$ から $7\times10^{-2}$ まで 4 桁以上動くのに対し、AGC あり(緑の破線)は完全な水平線になります。AGC の価値は「BER を良くすること」ではなく「BER を入力レベルから切り離すこと」だと、この 2 本の線が語っています。

左のグラフでは、実効 SNR がバックオフに対して台地を作ることがわかります。バックオフ 6〜20 dB あたりでは実効 SNR がアナログ SNR の 20 dB で頭打ちになっており、ADC はほとんど劣化を与えていません。ここが AGC の狙うべき動作領域です。台地の外では話が一変します。バックオフ 4 dB(詰めすぎ)でクリップにより 18.2 dB、バックオフ 35 dB(余らせすぎ)で量子化により 13.7 dB、40 dB では 9.5 dB まで落ちます。

右のグラフでは、この差が BER に増幅されて現れます。最適点の $3.2\times10^{-6}$ に対し、バックオフ 4 dB では $1.1\times10^{-4}$(35 倍)、35 dB では $1.1\times10^{-2}$(3400 倍)、40 dB では $6.8\times10^{-2}$ で通信が成立しません。実効 SNR のわずか 6 dB の損失が、BER では 3〜4 桁の悪化になる — これが誤り率が SNR に対して指数的に効くことの帰結です。AGC がやっているのは、入力レベルが 80 dB 動いても動作点をこの台地の上に固定し続けることであり、図の破線(AGC あり)が水平な直線になっているのはまさにそれを表しています。逆に言えば、AGC の精度は台地の幅(十数 dB)に対して十分であればよく、$\pm 1\sim2$ dB の定常誤差はまったく問題になりません。

これで理論・設計式・実装の三つが揃いました。最後に、実際に AGC を設計するときの手順としてまとめておきます。

実務での設計手順

これまでの結果を、設計の順番に並べ直すと次のようになります。

  1. 入力ダイナミックレンジを決める — 最小受信感度(熱雑音+NF+所要 SNR)から最大入力(隣接局・近距離)まで。これが必要な利得可変範囲になります。80 dB を超えるなら、複数段に分けて配分します。
  2. 利得配分を決める — 弱信号時は前段の LNA 利得を確保して雑音指数を守り、後段(IF・ベースバンド)の利得を先に絞る。強信号時のみ前段を絞る。フリスの公式が示すとおり、前段の利得を下げると系全体の NF が悪化するので、順番が重要です。
  3. ADC の目標バックオフを決める — 第 7 節の山頂(9〜12 dB 程度)。これが AGC の目標振幅 $A_t$ になります。
  4. 許容 EVM からループ雑音帯域の上限を決める — $B_n \le R_s(\eta/\sigma_a)^2$。連続波なら $R_s/1000$ 前後が目安。
  5. 必要な整定時間からループ帯域の下限を決める — $n_s = \ln(\Delta/\delta)/(-\ln(1-\mu))$。バースト通信ならプリアンブル長が制約になります。
  6. 4 と 5 が両立しないなら二段階 AGC にする — プリアンブルで速い $\mu$、データ区間で遅い $\mu$ またはフリーズ。
  7. ループ遅延を測り、$\mu < \mu_{\max} = 2\sin(\pi/(2(2D+1)))$ を確認する — 実測では安全率を見て $\mu_{\max}/5$ 以下に。
  8. 検波器の平滑化を $\alpha \ge 4\mu$ に設定する — リンギングを避ける。
  9. アタック/リリース比を決める — $10\sim100$。平衡レベルのバイアスを見込んで $A_t$ を補正する。
  10. ホールド/フリーズ機構を入れる — 信号無し区間で利得が全開まで上がらないよう、レベル閾値やタイマでループを止める。

この 10 項目を順に埋めれば、AGC は「なんとなく動く回路」ではなく、数式で説明できる制御系になります。

まとめ

本記事では、自動利得制御(AGC)の理論と設計を、対数領域の線形化を軸に解説しました。

  • 固定利得の受信機には上下 2 枚の壁がある — クリップと量子化埋没に挟まれた狭い窓しか使えず、実際の入力変動 60〜90 dB には到底足りません。AGC はこの窓を入力に追従させる機構です。
  • リニア AGC は原理的に破綻する — 極が $1-\mu|x|$ で入力振幅に比例するため、扱うダイナミックレンジと同じ倍率だけ応答速度と安定余裕がばらつきます。
  • 対数領域では厳密に線形化される — $G[n+1] = (1-\mu)G[n] + \mu(\ln A_t – a[n])$。近似ではありません。極は $1-\mu$ で入力レベルに依存せず、時定数は $\tau = -T_s/\ln(1-\mu) \approx T_s/\mu$、整定時間は $n_s = \ln(\Delta/\delta)/(-\ln(1-\mu))$。dB で実装しても $\mu$ は同じ値です。
  • AGC は対数包絡線のハイパスフィルタ — $H_{\rm out}(z) = (z-1)/(z-1+\mu)$。遅い変動(フェージング・経路損失)を消し、速い変動(変調の包絡線)を通す。$f_c = \mu f_s/2\pi$、$B_n = \frac{f_s}{2}\frac{\mu}{2-\mu}$。
  • 安定性の上限は遅延で決まる — $\mu_{\max} = 2\sin\!\big(\frac{\pi}{2(2D+1)}\big)$。$D=2$ で黄金比の逆数 0.618、大きな $D$ では $\pi/(2D)$ に漸近します。検波器の平滑化とは $\alpha \ge 4\mu$ で時間スケールを分離します。
  • アタックとリリースは非対称にする — 速いアタックが ADC を守り($n_{\rm clip} = \ln(\Delta/H)/(-\ln(1-\mu_a))$)、遅いリリースが包絡線追従歪み・ポンピング・ヌル追従を防ぎます。副作用として平衡レベルが分布の上側にバイアスします。
  • AGC の目標値は ADC の最適バックオフから決まる — $\mathrm{SNR}_q = 6.02B + 4.77 – \mathrm{BO}$ とクリップ歪みのトレードオフから、最適バックオフは 9〜12 dB、ENOB は公称より約 2 ビット低くなります。
  • ループを速くしすぎると変調が壊れる — $\mathrm{EVM} \approx \sigma_a\sqrt{B_n/R_s}$。16QAM($\sigma_a = 0.53$)で EVM 1 % を守るには $B_n \lesssim R_s/2800$。OFDM ではシンボル内の利得変動が ICI を生むため、シンボル境界でのみ更新する設計が定石です。

AGC は「振幅を一定に保つだけの回路」ではなく、ダイナミックレンジ・応答速度・変調歪みという 3 つの要求が正面からぶつかる設計空間です。そのぶつかり方を対数領域の一次系として定量化できるようになれば、受信機のどこでマージンが失われているかを式で追えるようになります。

次のステップとして、以下の記事も参考にしてください。