Outline
Sections 10+ reserved

DSIL Design Notes

Signal-level notes on _1Mhz_beta010, traced from the RTL and the git record.依 RTL 與 git 紀錄追出的 _1Mhz_beta010 訊號層筆記。

branch de25HEAD 33ad2d12026-09-12

1. Rx_ir_Amped_DB — the lock loop's over-the-air feedback pulseRx_ir_Amped_DB — 鎖定迴路的空中回授脈波

1.1 What it is它是什麼

Rx_ir_Amped_DB is the 1-bit pulse derived from the image-rejected receive signal and re-injected into the relay oscillator, so the oscillator phase-locks to what actually came back through the target. Whenever clutter cancel is off (cc_active = 0) it is the loop's only source — the default operating mode.

Rx_ir_Amped_DB 是由影像抑制後的接收訊號導出的 1 位元脈波,重新注入繼電振盪器,使振盪器鎖相到真正從目標回來的訊號。只要雜波消除關閉(cc_active = 0),它就是迴路唯一的來源——也就是預設工作模式。

Rx_IQ_Sum_BQ15.0, 10 MHz Rx_ir_x128{sum, 7'b0} schmitt_trigger±200 on x128 = ±1.56 LSB Rx_ir_Amped_Trig1 bit Rx_Debounce17 × 100 ns = 1.7 µs Rx_ir_Amped_DBalso a core output (open) fb_pulse muxcc_active ? cc : ir (KEY3) Rx_cc_Amped_DB (when cc_active = 1) fb_pulse_d1sync, 10 MHz PI_antiWindupinj_in → delay_line2 → inj_out osc_inj3 bit, delayed pi_delay relay_feedback2.uinj → oscillator delay = pi_delay cycles at 50 MHz; 1250 cycles = 360° at 40 kHz
From the image-rejected sum to the oscillator: amplify by 128, threshold, debounce, select, synchronise, delay by the PI's phase word, inject.從影像抑制和到振盪器:放大 128 倍、門檻、去彈跳、選路、同步、依 PI 相位字延遲、注入。

1.2 Name archaeology名稱考古

partmeaning today今日含義where it came from來源
Rx_irthe image-reject path (Rx_IQ_Sum_B), as opposed to Rx_cc影像抑制路徑(Rx_IQ_Sum_B),相對於 Rx_ccproject convention _ir / _cc專案慣例 _ir_cc
Ampedthe ×128 left shift feeding the fine trigger; input-referred threshold 200/128 = ±1.5625 LSB餵精細觸發器的 ×128 左移;換算到輸入為 200/128 = ±1.5625 LSBbc691d3: floating-point Amplifier_For_CC_Resolvesmittch_trigger SH=7 → explicit {…, 7'b0}; the module is gone, the name stayedbc691d3:浮點 Amplifier_For_CC_Resolvesmittch_trigger SH=7 → 明寫 {…, 7'b0};模組已移除,名字留下
DBdebounced by Rx_DebounceRx_Debounce 去彈跳17 consecutive agreeing samples on negedge clk10MHz1 (count >= 16)negedge clk10MHz1 上連續 17 個一致取樣(count >= 16

1.3 History (from git)歷史(依 git)

commitdatewhat changed for this signal對此訊號的改變
bc691d32026-03-31Initial commit. One of four selectable loop sources: SW9 Injection_Method_Switch (0 = receive path, 1 = local feedback) and SW8 ClutterCancel_OR_ImageReject_Switch (0 = Rx_cc_Amped_DB, 1 = Rx_ir_Amped_DB):
uin2 <= Injection_Method_Switch ? (CC_OR_IR ? uin : u_out) : (CC_OR_IR ? Rx_ir_Amped_DB : Rx_cc_Amped_DB)
初始提交。四個可選迴路來源之一:SW9 Injection_Method_Switch(0=接收路徑,1=本地回授)與 SW8 ClutterCancel_OR_ImageReject_Switch(0=Rx_cc_Amped_DB,1=Rx_ir_Amped_DB)。
6531b032026-07-19Study guide documents SW9/SW8 semantics.學習指南記錄 SW9/SW8 語意。
af56d622026-08-28DE25 port; chain unchanged.移植到 DE25;鏈路不變。
a4e23c42026-09-05Both switches removed "per explicit user direction". The image-reject pulse becomes the unconditional baseline; KEY3 (cc_active) only swaps in the clutter-cancelled pulse.兩個開關「依使用者明確指示」移除。影像抑制脈波成為無條件基線;KEY3(cc_active)只決定是否換成雜波消除脈波。
35edd6e2026-09-11Golden GHRD port (dsil_linux_top); the output port is left open there too.移植到 golden GHRD(dsil_linux_top);輸出埠同樣懸空。
33ad2d12026-09-12uin2/uin3 renamed fb_pulse/fb_pulse_d1; the pulse itself kept its name.uin2uin3 更名為 fb_pulsefb_pulse_d1;脈波本身名稱未改。

It is also a core output, but both tops — dsil_linux_top.v and the retired de25_top.v — leave .Rx_ir_Amped_DB() unconnected. The port exists for observation and nothing has ever consumed it.

它同時是核心的輸出,但兩個頂層——dsil_linux_top.v 與已退役的 de25_top.v——都讓 .Rx_ir_Amped_DB() 懸空。這個埠只為觀測而存在,從未被任何東西使用。

1.4 Numbers that matter關鍵數字

quantityvalueconsequence後果
Schmitt hysteresis, input-referred施密特遲滯(換算到輸入)±1.56 LSBat or below the AD9248 noise floor, so the trigger chatters on noise whenever the residual is weak; the debouncer does the real rejection在 AD9248 雜訊底附近或以下,殘差微弱時觸發器會因雜訊抖動;真正做雜訊抑制的是去彈跳器
Debounce delay去彈跳延遲17 cycles = 1.7 µs = 24.5° @ 40 kHza fixed phase lag inside the lock loop, absorbed silently by the PI位於鎖定迴路的固定相位落後,由 PI 默默吸收
Band-crossing time, strong signal強訊號穿越遲滯帶時間≈ 2 nspeak 5910 LSB: slope A·2πf = 1.49 × 10⁹ LSB/s crosses the 3.1-LSB band far inside one 100 ns sample — chatter is purely a weak-signal-plus-noise phenomenon峰值 5910 LSB:斜率 A·2πf = 1.49 × 10⁹ LSB/s,在一個 100 ns 取樣內就穿過 3.1 LSB 的帶——抖動純粹是弱訊號加雜訊的現象
Source swap on KEY3KEY3 切換來源cc ↔ irboth chains have the same structure and latency, so the loop sees a phase step only from the signals' own difference兩條鏈結構與延遲相同,迴路只會看到兩訊號本身差異造成的相位階躍

1.5 Advice, in priority order建議(依優先順序)

  1. Rename the pair to say what they are, not what they were. Rx_ir_Amped_Trig/_DBRx_ir_fine_trig/Rx_ir_fine_pulse (and cc likewise): "fine" = the ±1.56 LSB low-threshold path, versus the ±200 LSB coarse chain that feeds CC_Saver. Ripples only into the two tops' open connections.
    把這對名稱改成描述現況而非歷史。Rx_ir_Amped_Trig_DBRx_ir_fine_trigRx_ir_fine_pulsecc 亦同):「fine」=±1.56 LSB 低門檻路徑,相對於餵 CC_Saver 的 ±200 LSB coarse 鏈。只會波及兩個頂層的懸空連線。
  2. Size the threshold to measured noise and shorten the debounce. A threshold of 512–1024 on the ×128 path (≈ ±4–8 LSB input-referred) with a shorter debounce gives the same noise immunity with less of the 24.5° lag. Measure first: if the residual after clutter cancel really is a few LSB, today's numbers are deliberate.
    依量測到的雜訊設定門檻並縮短去彈跳。×128 路徑上門檻取 512–1024(換算輸入約 ±4–8 LSB)再配較短的去彈跳,可在相同抗雜訊下減少 24.5° 的落後。先量測:若雜波消除後殘差真的只有幾個 LSB,現在的數字就是刻意的。
  3. Use the open output or drop it. Ten open outputs were removed on 2026-09-12 on the same grounds. The useful option: edge counts (or the bits) of Rx_ir_Amped_DB/Rx_cc_Amped_DB into a spare jtag_regs stat word — a live "is the loop seeing a clean 40 kHz pulse" indicator on the panel.
    懸空的輸出要嘛用起來,要嘛移除。2026-09-12 已依同一理由移除十個懸空輸出。有用的做法:把 Rx_ir_Amped_DBRx_cc_Amped_DB 的邊緣計數(或位元)送進 jtag_regs 的空閒 stat 字,在面板上即時顯示「迴路是否看到乾淨的 40 kHz 脈波」。
  4. Add power-up initialisers to Rx_Debounce (data_out, count) as schmitt_trigger already has. Without them the whole fb_pulse path starts X in simulation, which is part of why no testbench observes it today.
    Rx_Debounce 加上上電初值data_outcount),如同 schmitt_trigger 已有的做法。否則整條 fb_pulse 路徑在模擬中從 X 開始,這也是目前沒有測試台觀測它的原因之一。
  5. Cover the path with a directed test. tb_rx_path measures peaks only. Check that fb_pulse is a clean 40 kHz square from the synthetic Rx — correct duty, the expected 17-cycle lag, no chatter with a few LSB of injected noise — exactly the part of the design the lock depends on.
    用定向測試覆蓋這條路徑。tb_rx_path 只量峰值。應驗證 fb_pulse 是由合成 Rx 得到的乾淨 40 kHz 方波——占空比正確、預期的 17 週期落後、注入幾個 LSB 雜訊時不抖動——這正是鎖相所依賴的部分。

1.6 Current RTL目前的 RTL

_1Mhz_beta010.vHEAD 33ad2d1
246	fb_pulse    <= cc_active ? Rx_cc_Amped_DB : Rx_ir_Amped_DB;   // KEY3 picks the loop source
247	fb_pulse_d1 <= fb_pulse;                                        // -> PI_antiWindup .inj_in

274	wire signed [22:0] Rx_ir_x128 = {Rx_IQ_Sum_B, 7'b0};            // x128: the former Amplifier_For_CC_Resolve
287	schmitt_trigger #(.WIDTH(23)) Trig_Rx_ir_Amped (clk10MHz1, Rx_ir_x128, Rx_ir_Amped_Trig);
289	Rx_Debounce DB_Rx_ir_Amped (clk10MHz1, Rx_ir_Amped_Trig, Rx_ir_Amped_DB);
schmitt_trigger.v · Rx_Debounce.vthe two decisions
25	trigger_out <= trigger_out ? (data_in >= -THRESH) : (data_in > THRESH);   // THRESH = 200
7	always @(negedge clk)                                                       // Rx_Debounce
13	    data_out <= (count >= 31'd16) ? ~data_out : data_out;                   // flips after 17 agreeing samples
Bottom line結論

Keep it — it is the default lock source. But its name describes a removed amplifier, its threshold sits below the noise floor so the debouncer carries the load and adds 24.5° of lag, and its exported copy goes nowhere. All three are cheap to fix.

保留它——它是預設的鎖定來源。但它的名字描述的是一個已移除的放大器、門檻低於雜訊底以致去彈跳器承擔負載並多出 24.5° 落後、匯出的副本無人使用。三者都容易修正。

2. Clutter_weak — the cancellation-not-worth-doing flagClutter_weak — 「不值得消除」旗標

2.1 What it is它是什麼

Clutter_weak is one comparison in clutter_cancel.v:91: Clutter_Amp_Ratio > 5.0, where the ratio is Tx_Q_Amp / Rx_Amp. It asserts when the received signal is more than five times weaker than the Tx reference — too little direct-path leakage for the subtraction to matter, and scaling Rx up by a large ratio would mostly amplify noise. It is a quality verdict on the KEY3 calibration, not part of the cancellation arithmetic.

Clutter_weak 只是 clutter_cancel.v:91 裡的一個比較:Clutter_Amp_Ratio > 5.0,其中比值為 Tx_Q_Amp / Rx_Amp。當接收訊號比 Tx 參考弱超過五倍時成立——直接路徑洩漏太少,減法沒有意義,而且把 Rx 乘上大比值主要只會放大雜訊。它是對 KEY3 校正品質的判定,不是消除運算本身的一部分。

era時期representation表示法the 5.0 threshold5.0 門檻
bc691d3 (2026-03-31)IEEE-754, FP_ComparatorIEEE-754,FP_Comparator32'h40A00000 = 5.0f
de25 today今日 de25unsigned Q4.12 (divider_q_fixed)無號 Q4.12(divider_q_fixed16'd20480 = 5 × 4096
master's cancellermaster 的消除器unsigned Q8.16 (div_fx)無號 Q8.16(div_fx327680 = 5 × 65536
Doc mismatch文件不一致

CLAUDE.md quotes master's constant (327680), not the 20480 that de25 actually builds. Harmless today; a trap if the Q8.16 canceller is ever ported without moving the constant.

CLAUDE.md 引用的是 master 的常數(327680),而不是 de25 實際編譯的 20480。今日無害;若日後移植 Q8.16 消除器卻沒改常數,就是陷阱。

2.2 The job it was designed for, and never got它原本的任務——從未上線

The initial commit carries the intent in a commented-out line of the feedback mux: if clutter is weak, feed the lock loop from the image-reject pulse instead of the clutter-cancelled one, regardless of the switch. That iteration was abandoned before the first commit; the study guide records it ("the abandoned mux iteration… auto-fall-back… the current code… only mirrors Clutter_weak on the LEDs"). For its entire committed life the flag's only consumer was one bit of the LED status word r.

初始提交在回授多工器裡以一行被註解掉的程式留下了原意:若雜波很弱,不論開關狀態,都改用影像抑制脈波餵鎖定迴路,而不是雜波消除後的脈波。這個版本在第一次提交前就被放棄;讀書指南有記載(「被放棄的多工器版本……自動退回……目前的程式……只把 Clutter_weak 映到 LED」)。在整個提交歷史中,這個旗標唯一的使用者就是 LED 狀態字 r 的一個位元。

clutter_cancel ratio = Tx_Q_Amp / Rx_Amp ratio > 5 ? Clutter_weak no (clutter present) yes (too weak) — proposed fallback Rx_cc_Amped_DB cancelled pulse usable Rx_ir_Amped_DB image-reject pulse fb_pulse
The decision the flag was built for. Today the dashed branch does not exist: fb_pulse follows cc_active alone.這個旗標原本要做的判斷。今日虛線分支並不存在:fb_pulse 只跟隨 cc_active

2.3 History (from git)歷史(依 git)

commitdatewhat changed for Clutter_weakClutter_weak 的變化
bc691d32026-03-31born as an FP_Comparator output; one bit of the LED word r; the auto-fallback mux already commented outFP_Comparator 輸出誕生;LED 字 r 的一位元;自動退回多工器已被註解掉
a2a87f52026-07-08the commented alternatives removed from the hand-written files手寫檔案中的註解替代方案被移除
6531b032026-07-19study guide documents the flag and the abandoned iteration讀書指南記錄此旗標與被放棄的版本
c5666282026-09-04canceller extracted into clutter_cancel.v; flag becomes a module output消除器抽出成 clutter_cancel.v;旗標成為模組輸出
76e2a11 / fb075182026-09-04/05fixed point: the compare becomes > 16'd20480 in Q4.12定點化:比較變為 Q4.12 的 > 16'd20480
33ad2d12026-09-12r / LED removed on de25 as dead → the flag now drives nothing in the corede25 上 rLED 因無用被移除 → 旗標在核心中不再驅動任何東西

State today: computed in clutter_cancel.v, wired to a core-level wire, and it stops there — the fitter prunes the comparator; only tb_clutter_cancel.v's cweak check still reads it. Its two debug siblings Clutter_Amp_Ratio and phase_diff are equally unconsumed, and the amplitude outputs Rx_Amp_pp/Tx_Q_Amp_pp are open ports at both tops.

現況:在 clutter_cancel.v 中計算、接到核心層的一條 wire,然後就停在那裡——fitter 會把比較器修剪掉;只剩 tb_clutter_cancel.vcweak 檢查還在讀它。它的兩個除錯同伴 Clutter_Amp_Ratiophase_diff 同樣無人使用,振幅輸出 Rx_Amp_ppTx_Q_Amp_pp 在兩個頂層都是懸空埠。

2.4 Advice, in priority order建議(依優先順序)

  1. Give it the job it was designed for — one term. fb_pulse <= (cc_active && !Clutter_weak) ? Rx_cc_Amped_DB : Rx_ir_Amped_DB; KEY3 still arms clutter-cancel, but the loop only follows the cancelled pulse when the calibration measured meaningful leakage. It guards against locking onto a noise-amplified residual after a KEY3 press in a clutter-free setup. A behaviour change, so it wants a bench check — but it is the smallest edit that makes the flag mean something.
    讓它做原本設計的工作——只加一項。fb_pulse <= (cc_active && !Clutter_weak) ? Rx_cc_Amped_DB : Rx_ir_Amped_DB; KEY3 仍會啟動雜波消除,但只有在校正量到有意義的洩漏時,迴路才跟隨消除後的脈波。這能避免在無雜波環境按下 KEY3 後鎖到被放大雜訊的殘差。這是行為改變,需要上板驗證——但它是讓旗標有意義的最小修改。
  2. Surface it to the operator. {phase_diff, Clutter_weak, Clutter_Amp_Ratio} fits one spare jtag_regs stat word — exactly the three numbers you want after pressing KEY3: did the calibration find leakage, how much, at what offset. One register.
    讓操作者看得到。{phase_diff, Clutter_weak, Clutter_Amp_Ratio} 剛好放進一個備用的 jtag_regs 狀態字——正是按下 KEY3 後想看的三個數字:校正有沒有找到洩漏、多少、偏移多少。只要一個暫存器。
  3. Remove it — output, core wire, and the cweak check — only after deciding against 1 and 2. The computation is sound and free; it is the consumer that is missing.
    移除它——輸出、核心 wire 與 cweak 檢查——但只在否決 1 與 2 之後。計算本身正確且不花成本;缺的是使用者。
Independent of the choice與選擇無關的提醒

The 20480 constant is tied to the Q4.12 ratio. Porting master's Q8.16 canceller means 327680, or the flag silently becomes "ratio > 0.08" and asserts almost always.

20480 這個常數綁定於 Q4.12 比值。若移植 master 的 Q8.16 消除器,必須改為 327680,否則旗標會悄悄變成「ratio > 0.08」而幾乎永遠成立。

2.5 Current RTL目前的 RTL

clutter_cancel.vHEAD 33ad2d1
14	// Clutter_weak flags a ratio > 5.0 (Q4.12: 5*4096 = 16'd20480): too little
15	// leakage to bother.
36	output            Clutter_weak,     // ratio > 5.0
90	// ---- "Clutter too weak" flag: ratio > 5.0 (Q4.12: 5*4096 = 16'd20480) --
91	assign Clutter_weak = (Clutter_Amp_Ratio > 16'd20480);
_1Mhz_beta010.vthe wire that goes nowhere
254	wire        Clutter_weak;
259		.Clutter_weak(Clutter_weak), .Rx_Amp_(Rx_Amp_pp), .Tx_Q_Amp_(Tx_Q_Amp_pp));
new40kHz4.v @ bc691d3the abandoned auto-fallback
97	//   uin2 <=(Injection_Method_Switch)?uin:((ClutterCancel_OR_ImageReject_Switch | Clutter_weak)?Rx_ir_out:Rx_cc_out);
Bottom line結論

Right that it is useless now — since this morning it drives nothing. Wrong to conclude it was pointless: it is a sound calibration-quality verdict that was never wired to the decision it was meant to guard. Wire it in (one term), show it (one register), or remove it — in that order of preference.

「現在沒用」是對的——從今早起它什麼都不驅動。但若因此認定它沒有意義就錯了:它是一個正確的校正品質判定,只是從未接到它該守護的那個決策。接進去(一項)、顯示出來(一個暫存器)、或移除——優先順序如此。

3. fb_pulse / fb_pulse_d1 — why the assignment order is not a bugfb_pulse/fb_pulse_d1 — 為何指派順序不是錯誤

3.1 The block in question討論的程式區塊

_1Mhz_beta010.vHEAD 33ad2d1, lines 245-248
245	always @(posedge clk10MHz) begin
246		fb_pulse    <= cc_active ? Rx_cc_Amped_DB : Rx_ir_Amped_DB;
247		fb_pulse_d1 <= fb_pulse;
248	end

At first sight line 247 looks like it copies the value line 246 has just written — which would make the second register a duplicate of the first. It does not.

乍看之下第 247 行像是複製第 246 行剛寫入的值——那會讓第二個暫存器變成第一個的複本。實際上不是。

3.2 Why the order is correct為何這個順序是正確的

Both lines use non-blocking assignment (<=). At the clock edge every right-hand side is sampled before any left-hand side updates, so fb_pulse_d1 <= fb_pulse copies the pre-edge value of fb_pulse. The two statements describe two flip-flops in series, whichever order they are written in.

兩行都用非阻塞指派(<=)。在時脈邊緣,所有右式都在任何左式更新之前取樣,所以 fb_pulse_d1 <= fb_pulse 複製的是 fb_pulse邊緣之前的值。無論寫作順序如何,這兩個敘述描述的都是串聯的兩個正反器。

clock edge時脈邊緣fb_pulsefb_pulse_d1
nmux(n)mux(n−1)
n+1mux(n+1)mux(n)
n+2mux(n+2)mux(n+1)

Swapping lines 246 and 247 synthesises to identical hardware.

把第 246 與 247 行對調,合成出的硬體完全相同

3.3 When order would matter順序何時才重要

With blocking assignment (=) the statements execute in sequence and the second one sees the value the first just wrote. The same two lines would then collapse the two flops into one and lose the second stage entirely. That trap is the real origin of the "write the delayed register first" convention — under <= it is a matter of readability, not correctness.

若用阻塞指派(=),敘述依序執行,第二句會看到第一句剛寫入的值。同樣兩行會把兩個正反器塌縮成一個,第二級完全消失。這個陷阱才是「先寫延遲暫存器」慣例的真正由來——在 <= 下它只關乎可讀性,不關乎正確性。

non-blocking vs blockingsame text, different circuit
// <=  two flops, order irrelevant        // =   one flop, second stage lost
fb_pulse    <= mux;                        fb_pulse    = mux;
fb_pulse_d1 <= fb_pulse;  // pre-edge value   fb_pulse_d1 = fb_pulse;  // NEW value

3.4 Why two stages exist為何需要兩級

Three independent reasons, all real:

三個各自成立的理由:

  1. Clock-domain crossing. Rx_*_Amped_DB is produced on the falling edge of clk10MHz1 inside Rx_Debounce; this block runs on the rising edge of clk10MHz. Those are different nets, not two phases of one clock: clk10MHz comes from clock_generator (the 40 %-duty divide_by_5_narrow), clk10MHz1 from a separate clockgen_10MHz divider. Their skew is bounded because both derive from the same 50 MHz oscillator, but it is a real crossing, and the pair is the two-flop synchroniser into the PI's clock domain — the study guide describes it as exactly that.
    跨時脈域。Rx_*_Amped_DBRx_Debounce 內於 clk10MHz1下降緣產生;此區塊則跑在 clk10MHz上升緣。這是兩條不同的網路,不是同一時脈的兩個相位:clk10MHz 來自 clock_generator(40 % 占空比的 divide_by_5_narrow),clk10MHz1 來自另一個 clockgen_10MHz 除頻器。兩者的偏斜有上限,因為都源自同一顆 50 MHz 振盪器,但這仍是真正的跨域;這一對就是進入 PI 時脈域的兩級同步器——讀書指南正是這麼描述的。
  2. Glitch isolation. cc_active toggles asynchronously on KEY3, so the mux select can change mid-cycle and fb_pulse can capture a glitch. The second flop keeps that from reaching PI_antiWindup .inj_in, which drives the delay line.
    隔離毛刺。cc_active 隨 KEY3 非同步翻轉,多工器的選擇可能在週期中途改變,fb_pulse 可能擷取到毛刺。第二個正反器讓它到不了驅動延遲線的 PI_antiWindup .inj_in
  3. Timing budget. Without the second flop the path runs from a falling-edge flop in Rx_Debounce, through the cc_active mux, straight into delay_line2's RAM write port inside PI_antiWindup — a half-cycle (50 ns) path into a memory. The extra register turns it into a clean full-cycle register-to-register path.
    時序預算。若沒有第二個正反器,這條路徑會從 Rx_Debounce 的下降緣正反器出發,經過 cc_active 多工器,直接進入 PI_antiWindupdelay_line2 的 RAM 寫入埠——一條進入記憶體的半週期(50 ns)路徑。多出的暫存器把它變成乾淨的整週期暫存器對暫存器路徑。

Cost: one 10 MHz cycle = 100 ns = 1.44° at 40 kHz, constant. The PI absorbs it as part of the loop delay it already steers (alongside the 24.5° from the debouncer), so it has no effect on the locked result.

代價:一個 10 MHz 週期 = 100 ns = 40 kHz 下的 1.44°,且為定值。PI 把它當成本來就在調整的迴路延遲的一部分吸收(連同去彈跳器的 24.5°),因此對鎖定結果沒有影響。

Strictly, if Rx_ir_Amped_DB were produced on the same clk10MHz edge and cc_active were synchronous, one flop would suffice. Neither is true — which is why both stages have been there since the first commit.

嚴格說,若 Rx_ir_Amped_DB 是在同一個 clk10MHz 邊緣產生、且 cc_active 是同步訊號,一個正反器就夠了。兩者都不成立——這正是兩級從第一次提交起就一直存在的原因。

3.5 Recommendation建議

Bottom line結論

Leave it as it is. Flipping the two lines is safe and behaviour-identical — a style choice, nothing more. The block is doing three jobs (synchronise, isolate, close timing) and all three need the second stage.

維持原狀。把兩行對調是安全且行為完全相同的——純屬風格。這個區塊做了三件事(同步、隔離、收斂時序),三者都需要第二級。

4. Suffix hygiene: keep _ir, retire _B字尾整理:保留 _ir,淘汰 _B

4.1 The question問題

Since a4e23c4 image rejection is the unconditional baseline — it is no longer one path among several. Should the _ir in names like Rx_ir_B_d1 become a participle (_ired), or go away entirely? Keep _ir.

a4e23c4 起,影像抑制是無條件的基線——不再是多條路徑之一。像 Rx_ir_B_d1 這類名字裡的 _ir,該改成分詞(_ired)還是直接拿掉?保留 _ir

4.2 Four reasons to keep _ir保留 _ir 的四個理由

  1. It is a path label, not a participle. In this codebase _cc means "the clutter-cancel path"; nobody writes _cced. _ired would also collide visually with "infrared".
    它是路徑標籤,不是分詞。在這個程式庫裡 _cc 的意思是「雜波消除路徑」,沒有人寫 _cced_ired 在視覺上還會和「infrared(紅外線)」混淆。
  2. ir and cc never appear alone. They always come as a pair — Rx_ir_B_d1/Rx_cc_B_d1, Rx_peak_ir/Rx_peak_cc, Rx_ir_x128/Rx_cc_x128, the two trigger chains, the fb_pulse mux. Symmetric tags make each pair read as "the same thing, two paths"; an unsuffixed baseline next to _cc hides the twin.
    ircc 從不單獨出現。它們總是成對——Rx_ir_B_d1Rx_cc_B_d1Rx_peak_irRx_peak_ccRx_ir_x128Rx_cc_x128、兩條觸發鏈、fb_pulse 多工器。對稱的標籤讓每一對讀起來是「同一個東西、兩條路徑」;沒有字尾的基線放在 _cc 旁邊會把孿生訊號藏起來。
  3. It still carries information. "Image-rejected but not cancelled" is exactly the distinction that matters when cc_active is on and both values exist at once. The tag lost an alternative, not its meaning.
    它仍帶有資訊。「已做影像抑制但未做消除」正是 cc_active 開啟、兩個值同時存在時最重要的區別。這個標籤失去的是一個替代選項,不是它的意義。
  4. Cost without benefit. Rx_peak_ir and Rx_ir_Amped_* are ports, jtag_regs stat words, fpga_panel.py labels and uart_decode vocabulary; CLAUDE.md and the study guide document _ir/_cc as a convention.
    只有成本沒有收益。Rx_peak_irRx_ir_Amped_* 是埠、jtag_regs 狀態字、fpga_panel.py 的標籤與 uart_decode 的詞彙;CLAUDE.md 與讀書指南都把 _ir_cc 記為慣例。

This morning's Rx_BRx_ir_B rename (33ad2d1) followed the same logic — parallel to Rx_cc_B — and is a one-line revert if the baseline should stay unsuffixed after all.

今早 Rx_BRx_ir_B 的更名(33ad2d1)依循同一邏輯——與 Rx_cc_B 平行——若最終決定基線不加字尾,一行即可還原。

4.3 The suffix that is stale: _B真正過時的字尾:_B

What it meant. "Binary" — the fixed-point twin of a signal that simultaneously existed as an IEEE-754 float: Rx_cc (float, into FP_Comparator) beside Rx_cc_B (16-bit, into peak_extractor); Rx_IQ_Sum beside Rx_IQ_Sum_B. It answered "which representation am I holding?".

它原本的意思。「Binary」——某個訊號同時以 IEEE-754 浮點存在時的定點孿生:Rx_cc(浮點,進 FP_Comparator)旁邊是 Rx_cc_B(16 位元,進 peak_extractor);Rx_IQ_Sum 旁邊是 Rx_IQ_Sum_B。它回答的是「我手上是哪一種表示法?」。

Why it is stale. On 2026-09-04 every floating-point block was deleted — Convert_*, MULT, Adder, the FP_Comparator triggers, the fp_*.ip. CLAUDE.md's own convention entry says "_FP no longer occurs since 2026-09-04". With the _FP side gone, _B distinguishes nothing: today assign Rx_cc_B = Rx_cc; is a plain alias. A tag that distinguishes nothing is misleading noise — a newcomer will go looking for the non-_B version.

為何過時。2026-09-04 所有浮點區塊都被刪除——Convert_*MULTAdderFP_Comparator 觸發器、fp_*.ipCLAUDE.md 自己的慣例條目就寫著「_FP 自 2026-09-04 起不再出現」。_FP 這一側消失後,_B 什麼也區分不了:今日 assign Rx_cc_B = Rx_cc; 只是單純的別名。一個什麼都不區分的標籤是誤導性的雜訊——新來的人會去找那個沒有 _B 的版本。

Principle原則

A name says what a signal is; where it came from belongs in git and in comments. The same rule already retired "Amped" (§1) and the "FP_" amplitude ports.

名字說的是訊號是什麼;它從哪裡來屬於 git 與註解。同一條規則已經淘汰了「Amped」(§1)與「FP_」振幅埠。

4.4 Proposed rename (advice, not applied)建議的更名(僅建議,未套用)

today現在proposed建議note備註
Rx_ir_BRx_irport; both tops and tb_rx_path埠;兩個頂層與 tb_rx_path
Rx_cc_BRx_ccRx_cc already exists as the canceller output feeding the alias, so the alias disappears — or becomes Rx_cc_out16 if the saturated 16-bit port needs a distinct nameRx_cc 已是餵這個別名的消除器輸出,所以別名直接消失——若飽和的 16 位元埠需要獨立名稱,則改為 Rx_cc_out16
Rx_IQ_Sum_BRx_IQ_Sumthe image-reject sum, Q15.0影像抑制和,Q15.0
Rx_ir_B_d1 / _d2Rx_ir_d1 / _d2peak re-time chain峰值重定時鏈
Rx_cc_B_d1 / _d2Rx_cc_d1 / _d2peak re-time chain峰值重定時鏈

Cost. The two ports ripple into both tops and tb_rx_path; CLAUDE.md's Rx section and its _B convention entry (retire the entry in the same commit); the study guide's vocabulary — and master still uses the old names, so cross-branch file sharing must account for it.

成本。兩個埠會波及兩個頂層與 tb_rx_pathCLAUDE.md 的 Rx 章節與它的 _B 慣例條目(同一個提交中一併淘汰該條目);讀書指南的詞彙——而且 master 仍使用舊名,跨分支共用檔案時必須考慮。

Verdict結論

Worth doing the next time the file is touched; not urgent. Use the same verified-rename procedure as the 2026-09-12 renames: pre/post cores side by side under the ADC model with every output compared per edge, then tb_rx_path.

值得在下次動到這個檔案時一併處理;不急。採用與 2026-09-12 更名相同的驗證流程:更名前後的核心在 ADC 模型下並排、逐邊比較所有輸出,再跑 tb_rx_path

5. Rx_ir_B_d1 / _d2 — the two-stage re-time into the peak extractorsRx_ir_B_d1/_d2 — 進入峰值擷取器的兩級重定時

5.1 The block程式區塊

_1Mhz_beta010.vHEAD 33ad2d1, lines 314-330
314	always @(posedge clk10MHz5)
315	begin
316		Rx_ir_B_d1 <= Rx_ir_B;
317		Rx_cc_B_d1 <= Rx_cc_B;
318	end
320	reg  signed [15:0] Rx_ir_B_d1, Rx_cc_B_d1;
321	always @(negedge clk10MHz5)
322	begin
323		Rx_ir_B_d2 <= Rx_ir_B_d1;
324		Rx_cc_B_d2 <= Rx_cc_B_d1;
325	end
326	reg  signed [15:0] Rx_cc_B_d2, Rx_ir_B_d2;
329	peak_extractor peaker_ir (clk10MHz5, Rx_ir_B_d2, Rx_peak_ir);
330	peak_extractor peaker_cc (clk10MHz5, Rx_cc_B_d2, Rx_peak_cc);

Two values, two stages each: a rising-edge register (_d1), then a falling-edge register (_d2), then the peak extractors, which sample on the rising edge of the same clk10MHz5.

兩個值,各兩級:先是上升緣暫存器(_d1),再是下降緣暫存器(_d2),然後是峰值擷取器——它們在同一個 clk10MHz5 的上升緣取樣。

5.2 Merge into one block?能合併成一個區塊嗎?

Not as written. One block is posedge, the other negedge; a single always cannot clock registers on opposite edges. Merging is only possible by making stage 2 a rising-edge stage too — and whether that is allowed depends on what stage 2 is for, which is 5.3.

照現在的寫法不行。一個區塊是 posedge,另一個是 negedge;單一 always 無法讓暫存器在相反的邊緣觸發。唯一的合併方式是把第二級也改成上升緣——而這是否可行,取決於第二級的用途,見 5.3。

5.3 Why two stages, per the record為何是兩級——依紀錄

The structure is unchanged since bc691d3 (Rx_B_Temp on posedgeRx_ir_B_Reg on negedgepeak_extractor). Its job is to move both Rx values into the peak extractor's clock domain. The sources differ in kind:

這個結構自 bc691d3 起就沒變(Rx_B_TempposedgeRx_ir_B_Regnegedgepeak_extractor)。它的任務是把兩個 Rx 值搬進峰值擷取器的時脈域。兩個來源的性質不同:

signal訊號what drives it驅動來源domain時脈域
Rx_ir_BRx_IQ_Sum_B, a registerRx_IQ_Sum_B,一個暫存器posedge clk10MHz6
Rx_cc_BRx_cc, a combinational subtraction (Rx_Amped − Tx_Q_Delay) off registers inside clutter_cancelRx_cc,一個組合邏輯減法(Rx_Amped − Tx_Q_Delay),源自 clutter_cancel 內的暫存器clk10MHz2
consumer使用者peak_extractor + zero_crossingpeak_extractor + zero_crossingposedge clk10MHz5

clk10MHz1clk10MHz6 are six separate clockgen_10MHz dividers — divide-by-5, count powers up at 0, clkA = count[1] OR'd with its own falling-edge copy for 50 % duty. Nominally identical in phase, but six different nets that static timing treats as unrelated unless an SDC relates them. Three run from clk50MHz and three from clk50MHz2 — a DE2-115 two-oscillator artefact; on the DE25 both are CLOCK2_50.

clk10MHz1clk10MHz6 是六個各自獨立的 clockgen_10MHz 除頻器——除以 5,count 上電為 0,clkA = count[1] 與自己的下降緣複本做 OR 得到 50 % 占空比。名義上相位相同,但卻是六條不同的網路,除非 SDC 建立關聯,否則靜態時序分析視為互不相關。三個由 clk50MHz 驅動,三個由 clk50MHz2 驅動——這是 DE2-115 雙振盪器時代的遺跡;在 DE25 上兩者都是 CLOCK2_50

instance實例source clock來源時脈used by (in this section)用途(本節相關)
ClkGen10MHz1clk50MHzADC encode, triggers, debounceADC 編碼、觸發、去彈跳
ClkGen10MHz2clk50MHz2clutter_cancel Tx reference / delay line → Rx_ccclutter_cancel 的 Tx 參考/延遲線 → Rx_cc
ClkGen10MHz3clk50MHz(FSK dividers, removed)(FSK 除頻器,已移除)
ClkGen10MHz4clk50MHz2Rx Hilbert pair, clutter_cancel amplitude/ratioRx Hilbert 對、clutter_cancel 振幅/比值
ClkGen10MHz5clk50MHzthis block and both peak_extractors本區塊與兩個 peak_extractor
ClkGen10MHz6clk50MHz2ADC capture (adc_iface), Rx_IQ_Sum_BADC 擷取(adc_iface)、Rx_IQ_Sum_B
  1. Stage 1 (posedge, _d1) does real work. It is the register boundary that lands a combinational subtractor from another 10 MHz phase cleanly in the clk10MHz5 domain.
    第一級(posedge_d1)有實際作用。它是把來自另一個 10 MHz 相位的組合減法器乾淨地落入 clk10MHz5 時脈域的暫存器邊界。
  2. Stage 2 (negedge, _d2) is a half-cycle phase trick — the same idiom as Rx_Debounce and the ADC capture: it re-presents the value 50 ns before the extractor's rising edge. Reasonable in the FP era, when Rx_cc_B came out of a 400 MHz converter at uncertain phase. Today it buys nothing: fed directly from _d1, the extractor samples the previous rising edge's value with a full 100 ns of setup — better timing — whereas a falling-edge register halves the static-timing budget on both of its sides.
    第二級(negedge_d2)是半週期相位技巧——與 Rx_Debounce 及 ADC 擷取相同的手法:在擷取器上升緣前 50 ns 重新呈現這個值。在浮點時代合理,當時 Rx_cc_B 來自相位不確定的 400 MHz 轉換器。今日它什麼都換不到:直接由 _d1 餵入時,擷取器取樣的是上一個上升緣的值,有整整 100 ns 的建立時間——時序更好——而下降緣暫存器則把兩側的靜態時序預算都砍半。
  3. Functionally invisible. peak_extractor reports the maximum between zero crossings; a constant half-cycle delay cannot change a maximum. Removing _d2 only shifts when Rx_peak_* updates, by at most one 10 MHz cycle within the 25 µs period; the values are identical.
    功能上不可見。peak_extractor 回報的是兩次過零之間的最大值;固定的半週期延遲不可能改變最大值。移除 _d2 只會讓 Rx_peak_* 更新的時間點在 25 µs 週期內最多移動一個 10 MHz 週期;數值完全相同。

5.4 Recommendation (advice, not applied)建議(僅建議,未套用)

Drop the falling-edge stage and feed both extractors from _d1: one always block, two registers instead of four, no half-cycle paths. Keep _d1 — it is the domain boundary, not decoration.

拿掉下降緣那一級,讓兩個擷取器直接由 _d1 餵入:一個 always 區塊、兩個暫存器取代四個、沒有半週期路徑。保留 _d1——它是時脈域邊界,不是裝飾。

proposedone block, rising edge only
	reg  signed [15:0] Rx_ir_B_d1, Rx_cc_B_d1;   // re-time into the clk10MHz5 domain
	always @(posedge clk10MHz5) begin
		Rx_ir_B_d1 <= Rx_ir_B;
		Rx_cc_B_d1 <= Rx_cc_B;
	end
	peak_extractor peaker_ir (clk10MHz5, Rx_ir_B_d1, Rx_peak_ir);
	peak_extractor peaker_cc (clk10MHz5, Rx_cc_B_d1, Rx_peak_cc);

Verification. tb_rx_path's only measurement is Rx_peak_ir: 5910/87 and 5879/57 must come out identical. Add a pre/post comparison of the peak values per 40 kHz period — a per-edge comparison would flag the harmless update-time shift and prove nothing.

驗證。tb_rx_path 唯一量的就是 Rx_peak_ir:5910/87 與 5879/57 必須完全相同。再加一個改動前後、以每個 40 kHz 週期為單位的峰值比較——逐邊比較會把無害的更新時間偏移標成差異,什麼也證明不了。

5.5 The real risk in this corner這個角落真正的風險

Six unrelated 10 MHz clocks六個互不相關的 10 MHz 時脈

The latent risk here is not the stage count. It is six nominally identical but formally unrelated 10 MHz clocks crossing into each other with no timing constraint between them. It works because the dividers start aligned at power-up and the paths are short — luck that happens to hold, not analysis. set_clock_groups or generated-clock relationships in the SDC would turn it into analysis. That is a separate, larger topic than this block.

這裡潛在的風險不在級數,而在六個名義相同、形式上卻互不相關的 10 MHz 時脈彼此交錯,卻沒有任何時序約束。它能運作是因為除頻器上電時對齊、路徑又短——是恰好成立的運氣,不是分析。SDC 裡的 set_clock_groups 或衍生時脈關聯會把它變成分析。這是比本區塊更大的另一個題目。

6. count_full / route_select — CC_Saver, the third auto-selection experimentcount_full/route_select — CC_Saver,第三個自動選路實驗

Naming note命名說明

The names quoted in this section — in1/in2/data_out/route_select/count_full — are the pre-2026-09-12 names. They have since been renamed by meaning; see Section 7.

本節引用的名稱——in1in2data_outroute_selectcount_full——是 2026-09-12 之前的名字。之後已依含義更名;見第 7 節

6.1 The line in question討論的那一行

_1Mhz_beta010.vHEAD 33ad2d1, lines 311-312
311	CC_Saver CC_Save (clk10MHz1, Saver_out, Rx_cc_pulse, Rx_ir_pulse, route_select, count_full);
312	wire   count_full, route_select;
CC_Saver.vHEAD 33ad2d1
1	module CC_Saver (clk, data_out, in1, in2, route_select, count_full);
3	output      data_out; assign data_out = (route_select)?in2:in1;
8	output reg  route_select;
9	wire        high_agb = (count_h >= 130)?1'b1:1'b0;
10	wire        high_alb = (count_h <= 110)?1'b1:1'b0;
12	output count_full; assign count_full = (total_count >= 250)?1'b1:1'b0;
18			count_h <= (count_full)?32'd1:count_h_;
19			count_l <= (count_full)?32'd0:count_l;
30		if(count_full)
31		begin
32			route_select <= (high_agb | high_alb)?1'b1:1'b0;

6.2 What CC_Saver doesCC_Saver 做什麼

It watches the two coarse (±200 LSB) trigger pulses. Over a 250-sample window — one 40 kHz period at 10 MHz — it counts how many samples the clutter-cancelled pulse is high. If that count falls outside 110–130 (a duty cycle far from 50 %, i.e. a distorted or missing pulse) it sets route_select = 1 and its data_out (Saver_out) switches to the image-reject pulse. count_full is the window-rollover strobe.

它監看兩個(±200 LSB)觸發脈波。在 250 個取樣的窗口內——10 MHz 下正好是一個 40 kHz 週期——計數雜波消除脈波為高的取樣數。若計數落在 110–130 之外(占空比遠離 50 %,亦即脈波失真或缺失),就把 route_select 設為 1,其 data_outSaver_out)改為輸出影像抑制脈波。count_full 是窗口翻轉的選通。

count_h over one 40 kHz period一個 40 kHz 週期內的 count_hverdict判定route_selectSaver_out
110 … 130duty near 50 % — cancelled pulse well-formed占空比接近 50 %——消除後脈波完好0in1 = Rx_cc_pulse
< 110 or > 130distorted or missing — fall back失真或缺失——退回1in2 = Rx_ir_pulse

6.3 Why the wires are declared為何要宣告這些 wire

Positional instantiation needs a name in every slot, so the author declared wires for all three outputs whether or not anything read them.

位置式實例化每個槽位都需要一個名字,所以作者為三個輸出都宣告了 wire——不論有沒有人讀它們。

output輸出who reads it誰在讀history歷史
count_fullnobody — not even in bc691d3沒有人——連 bc691d3 也沒有appears only in the commented-out tail of the old port list (//clk_50_1, clk_50_2, , count_full, …): once a debug output pin, then dropped from the ports, the wire left as a landing pad. A bare wire since the first commit.只出現在舊埠列表被註解掉的尾巴(//clk_50_1, clk_50_2, , count_full, …):曾是除錯輸出腳,後來從埠中拿掉,wire 留下來當落點。從第一次提交起就是一條裸 wire。
route_selectexactly one reader: bit 5 of the LED status word r恰好一個讀者:LED 狀態字 r 的第 5 位元assign r = {4'b0000, clk250Hz, clk1Hz, route_select, Clutter_weak, Injection_Method_Switch, ClutterCancel_OR_ImageReject_Switch} in bc691d3 — the board LEDs showed which pulse CC_Saver had picked, which is why it was "visible before". r/LED removed in 33ad2d1 (2026-09-12), so it now goes nowhere.bc691d3assign r = {4'b0000, clk250Hz, clk1Hz, route_select, Clutter_weak, Injection_Method_Switch, ClutterCancel_OR_ImageReject_Switch}——板上 LED 顯示 CC_Saver 選了哪個脈波,這就是它「以前看得到」的原因。rLED33ad2d1(2026-09-12)移除,如今無處可去。
Saver_outstill a core output port; both tops leave it open (.Saver_out())仍是核心輸出埠;兩個頂層都讓它懸空(.Saver_out()never connected to anything at either top在兩個頂層都從未接到任何東西
Net effect淨效果

All three CC_Saver outputs are unconnected. CC_Saver and the coarse trigger/debounce chain feeding it — Trig_Rx_cc/ir, DB_Rx_cc/ir, Rx_*_trig/_trig_DB/_pulse — are dead end to end; the fitter removes all of it.

CC_Saver 的三個輸出全部懸空。CC_Saver 與餵它的粗觸發/去彈跳鏈——Trig_Rx_ccirDB_Rx_ccirRx_*_trig_trig_DB_pulse——從頭到尾都是死的;fitter 會全部移除。

6.4 Why it existed它為何存在

The same story as Clutter_weak (Section 2): an auto-selection experiment that was never wired into the loop. The intended endpoint was almost certainly fb_pulse <= Saver_out — hardware choosing between the cancelled and image-reject pulses by measured pulse quality instead of a switch. That step was never taken: the loop always took its pulse from the _Amped_DB mux, and CC_Saver only ever drove one LED.

Clutter_weak(第 2 節)同一個故事:一個從未接進迴路的自動選路實驗。它預期的終點幾乎可以肯定是 fb_pulse <= Saver_out——由硬體依量測到的脈波品質在消除後脈波與影像抑制脈波之間選擇,而不是靠開關。這一步從未跨出:迴路的脈波一直來自 _Amped_DB 多工器,CC_Saver 只曾驅動過一顆 LED。

Taken together with Sections 1 and 2, that makes three abandoned auto-select mechanisms in this design: the commented-out Clutter_weak fallback, CC_Saver, and the SW9/SW8 switches removed in a4e23c4.

連同第 1、2 節來看,這個設計裡共有三個被放棄的自動選路機制:被註解掉的 Clutter_weak 退回、CC_Saver,以及在 a4e23c4 移除的 SW9/SW8 開關。

6.5 Advice建議

CC_Saver.v is being edited by its owner right now, which is why it was left alone in the 2026-09-12 rename passes. Three options:

CC_Saver.v 目前正由擁有者編輯中,這也是 2026-09-12 的更名作業刻意不動它的原因。三個選項:

  1. Finish the experiment. Route Saver_out into the loop. Mind the mismatch: CC_Saver judges the coarse pulses while the loop runs on the fine (±1.56 LSB) ones — so either feed CC_Saver the fine pulses, or accept the mismatch deliberately.
    完成這個實驗。Saver_out 接進迴路。注意不匹配:CC_Saver 判定的是脈波,而迴路跑的是精細(±1.56 LSB)脈波——所以要嘛改餵 CC_Saver 精細脈波,要嘛有意識地接受這個不匹配。
  2. Keep it as instrumentation. route_select (and count_h, if exported) into a spare jtag_regs stat word — a live "is the cancelled pulse well-formed" indicator, which is what the LED used to show.
    留作儀表。route_select(若匯出的話還有 count_h)放進一個備用的 jtag_regs 狀態字——即時顯示「消除後脈波是否完好」,也就是那顆 LED 以前顯示的東西。
  3. Remove it whole. CC_Saver, Saver_out and its two open top-level connections, Trig_Rx_cc/ir, DB_Rx_cc/ir, the Rx_*_trig/_trig_DB/_pulse chain and these two wires — verified by a pre/post equivalence run.
    整個移除。CC_Saver、Saver_out 與它在兩個頂層的懸空連線、Trig_Rx_ccirDB_Rx_ccirRx_*_trig_trig_DB_pulse 鏈以及這兩條 wire——以前後等價模擬驗證。
Until then在那之前

The two wires are correct as they stand. Deleting only them breaks the positional instantiation; they go when CC_Saver goes, or gain a reader when it gains a job.

這兩條 wire 現狀是正確的。只刪它們會弄壞位置式實例化;它們要嘛隨 CC_Saver 一起消失,要嘛在 CC_Saver 有了任務時獲得讀者。

7. CC_Saver after the clean-up: ports by meaning, and are its outputs used?整理後的 CC_Saver:依含義命名的埠,以及它的輸出有人用嗎?

7.1 Port order first先談埠的順序

The header was reordered inputs-first, and the core's positional instantiation moved Saver_out from slot 2 to slot 4 to match. Every port is 1 bit, so a wrong order would compile silently — the check was a directed simulation through the new positional order: 50 % and 48 % duty on the cancelled pulse gave use_ir = 0 with the output following that pulse; 20 % and 80 % gave use_ir = 1 with the output following the image-reject pulse (4/4).

模組標頭改為輸入在前,核心的位置式實例化也把 Saver_out 從第 2 槽移到第 4 槽以對應。每個埠都是 1 位元,順序錯了也會靜靜地編譯過——所以用了一個直接走位置順序的定向模擬:消除後脈波占空比 50 % 與 48 % 時 use_ir = 0、輸出跟隨該脈波;20 % 與 80 % 時 use_ir = 1、輸出跟隨影像抑制脈波(4/4)。

CC_Saver.v · _1Mhz_beta010.vworking tree, 2026-09-12
9	module CC_Saver (clk10MHz, cc_pulse, ir_pulse, pulse_out, use_ir, window_end);
10		input       clk10MHz, cc_pulse, ir_pulse;
11		output      pulse_out;
12		output reg  use_ir;

311	CC_Saver CC_Save (clk10MHz1, Rx_cc_pulse, Rx_ir_pulse, Saver_out, use_ir, window_end);
312	wire   use_ir, window_end;      // CC_Saver: 1 = image-reject pulse selected / window rollover strobe
Finding from that sim模擬中的發現

CC_Saver has no power-up initialisers (cc_high_cnt, cc_low_cnt, use_ir). From power-up in simulation window_end = (X >= 250) is X, so use_ir stays X forever; the harness had to set the three registers from outside, mirroring the FPGA's start-at-zero. Same gap as Rx_Debounce (Section 1). Three = 0 initialisers would fix it — recommended, not applied.

CC_Saver 沒有上電初值(cc_high_cntcc_low_cntuse_ir)。模擬從上電開始時 window_end = (X >= 250)X,因此 use_ir 永遠停在 X;測試台只好從外部設定這三個暫存器,模仿 FPGA 從零開始。與 Rx_Debounce(第 1 節)同樣的缺口。三個 = 0 初值即可修正——建議,未套用。

7.2 Rename by meaning依含義更名

old舊名new新名meaning含義
clkclk10MHzthe 250-sample window only means "one 40 kHz period" at 10 MHz250 個取樣的窗口只有在 10 MHz 下才等於「一個 40 kHz 週期」
in1cc_pulsethe clutter-cancelled coarse pulse — the one being judged雜波消除後的粗脈波——被判定的那一個
in2ir_pulsethe fallback退回用的脈波
data_outpulse_outthe selected pulse被選中的脈波
route_selectuse_ir1 = image-reject pulse routed1=選路到影像抑制脈波
count_fullwindow_endrollover strobe窗口翻轉選通
count_h / count_lcc_high_cnt / cc_low_cntsamples the pulse was high / low脈波為高/為低的取樣數
count_h_ / count_l_cc_high_next / cc_low_nextthe incremented values遞增後的值
total_countwindow_cntsamples elapsed in the window窗口內已過的取樣數
high_agb / high_albduty_too_high / duty_too_lowthe two out-of-band verdicts兩個超出範圍的判定
250 / 130 / 110WINDOW / DUTY_HI / DUTY_LOlocalparams instead of magic numberslocalparam 取代魔術數字

In the core the wires became use_ir/window_end; the Saver_out port kept its name, so neither top changed. A header comment now describes the module and records the old port names. Verification: the previous module and the renamed one under random pulses (duty re-drawn every 5000 cycles), all three outputs compared every edge — 299 999 cycles, 0 mismatches; whole-core tb_rx_path passed.

核心中的 wire 改為 use_irwindow_endSaver_out 埠保留原名,因此兩個頂層都不必改。標頭註解現在描述了模組並記錄舊埠名。驗證:更名前後的模組在隨機脈波下(每 5000 週期重抽占空比)逐邊比較三個輸出——299 999 個週期,0 個不一致;全核心 tb_rx_path 通過。

7.3 Two redundancies collapsed收掉的兩處冗餘

The else use_ir <= use_ir; branch. A clocked register holds its value when not assigned, so window_end simply acts as the flop's clock-enable and the fitter emits identical logic. The self-assign idiom is needed in combinational always @* blocks — without it a latch is inferred — and was carried into clocked blocks by habit; Rx_Debounce has the same data_out <= data_out;.

else use_ir <= use_ir; 分支。時脈式暫存器在沒有被指派時會保持原值,所以 window_end 就只是正反器的時脈致能,fitter 產生的邏輯完全相同。自我指派的寫法在組合邏輯的 always @* 區塊裡確實需要——否則會推論出閂鎖器——它是被習慣帶進時脈區塊的;Rx_Debounce 也有同樣的 data_out <= data_out;

Four (cond) ? 1'b1 : 1'b0 forms reduced to (cond). The ? 32'd1 : cc_high_next forms in the counters were kept — those are real two-value muxes.

四個 (cond) ? 1'b1 : 1'b0 化簡為 (cond)。計數器裡的 ? 32'd1 : cc_high_next 保留——那些是真正的二選一多工器。

CC_Saver.vbefore → after
// before
always @(posedge clk)
	if(count_full)
		route_select <= (high_agb | high_alb)?1'b1:1'b0;
	else
		route_select <= route_select;

// after
45	always @(posedge clk10MHz)
46		if (window_end)
47			use_ir <= duty_too_high | duty_too_low;

Equivalence before vs after: 299 999 cycles, 0 mismatches; behaviour sim 4/4.

改動前後等價:299 999 個週期,0 個不一致;行為模擬 4/4。

7.4 Are pulse_out and use_ir still used?pulse_outuse_ir 還有人用嗎?

No.

沒有。

output輸出lands on接到consumed by使用者
pulse_outSaver_out, a core output portSaver_out,核心輸出埠both tops leave .Saver_out() open兩個頂層都讓 .Saver_out() 懸空
use_irwire use_irnothing — its only reader was bit 5 of the LED word r, removed in 33ad2d1沒有——唯一的讀者是 LED 字 r 的第 5 位元,已在 33ad2d1 移除
window_endwire window_endnothing; never was沒有;從來沒有

Inside the module all three are live. But with every output dangling, the synthesiser prunes CC_Saver entirely, together with the coarse chain feeding it (Trig_Rx_cc/irDB_Rx_cc/irRx_cc_pulse/Rx_ir_pulse): the cleaned-up file currently synthesises to zero logic.

在模組內部三者都是活的。但既然每個輸出都懸空,合成器會把 CC_Saver 整個修剪掉,連同餵它的粗鏈(Trig_Rx_ccirDB_Rx_ccirRx_cc_pulseRx_ir_pulse):整理後的檔案目前合成出零邏輯。

What the clean-up is worth這次整理值多少

Exactly as much as the decision about what CC_Saver is for. The three options are in Section 6.5: use it in the loop (mindful of coarse-vs-fine pulses), instrument it via a jtag_regs stat word, or remove it whole with a pre/post equivalence run. Until then the two output declarations are correct and necessary — deleting them breaks the module while leaving the dead instantiation in place.

恰好等於「CC_Saver 是做什麼用」這個決定的價值。三個選項在第 6.5 節:接進迴路(注意粗/細脈波的差異)、透過 jtag_regs 狀態字當儀表、或以前後等價模擬整個移除。在那之前,這兩個輸出宣告是正確且必要的——刪掉它們只會弄壞模組,卻留下那個死的實例。

8. clutter_cancel — what it does, and how the record changed itclutter_cancel — 它做什麼,以及紀錄如何改變它

8.1 The idea (unchanged since bc691d3)基本想法(自 bc691d3 起未變)

The direct-path leakage in Rx is a scaled, delayed copy of Tx. On a KEY3 press, measure two numbers — the amplitude ratio Tx/Rx and the lag in samples — then continuously subtract that scaled, delayed Tx replica from Rx. What survives is the target.

Rx 中的直接路徑洩漏是 Tx 經過縮放與延遲的複本。按下 KEY3 時量測兩個數字——振幅比 Tx/Rx 與以取樣數計的落後——然後持續從 Rx 減去這個縮放、延遲後的 Tx 複本。留下來的就是目標。

8.2 Today's de25 module, step by step (Q4.12)今日 de25 模組的步驟(Q4.12)

Tx_Q1: v on clk10MHz2 Rx_IQ_Sum16-bit, Q0 amplitude_calc ×22: peaks, ~clk10MHz4 phase_detector7: lag in samples divider_q_fixed3: Tx/Rx, Q4.12, ~clk_400 DIV_OPT_VALID4: two results agree latch on negedge CC5: ratio + phase_diff Rx_Amped6: Rx × ratio >> 12 delay_line8: Tx_Q by phase_diff Rx_cc9: Rx_Amped − Tx_Q_Delay Clutter_weakratio > 5.0 (Section 2) dashed = frozen by the KEY3 edge; solid = continuous
Nine steps: two measurements (top row, right; bottom left), frozen on the KEY3 edge, then applied continuously to build and subtract the leakage replica.九個步驟:兩個量測(上排右側與下排左側)在 KEY3 邊緣凍結,然後持續用來建構並減去洩漏複本。
#step步驟where / clock位置/時脈
1Tx_Q <= v — the Tx reference sampleTx_Q <= v——Tx 參考取樣clk10MHz2
2peak amplitudes of Tx_Q (14-bit) and Rx_IQ_Sum (16-bit) via amplitude_calculation / amplitude_calculation_16bit, then absolute valueTx_Q(14 位元)與 Rx_IQ_Sum(16 位元)的峰值振幅,經 amplitude_calculationamplitude_calculation_16bit,再取絕對值~clk10MHz4
3ratio = Tx_Q_Amp / Rx_Amp as Q4.12 (max 15.99) from divider_q_fixed #(.AW(14),.BW(16),.FRAC(12),.RW(16))比值 = Tx_Q_Amp / Rx_Amp,Q4.12(最大 15.99),由 divider_q_fixed #(.AW(14),.BW(16),.FRAC(12),.RW(16)) 算出~clk_400
4DIV_OPT_VALID: accept a ratio only when two successive divider results agree, so a KEY3 press cannot latch a half-finished quotientDIV_OPT_VALID:只有連續兩次除法結果一致才接受比值,避免 KEY3 鎖到算到一半的商clk_400
5always @(negedge CC) latches Clutter_Amp_Ratio and phase_diff — the button edge used as a clockalways @(negedge CC) 鎖存 Clutter_Amp_Ratiophase_diff——把按鍵邊緣當時脈用KEY3 edgeKEY3 邊緣
6Rx_Amped = Rx_IQ_Sum × ratio >> 12, back to Q0Rx_Amped = Rx_IQ_Sum × ratio >> 12,回到 Q0combinational組合邏輯
7phase_detector: zero-crossing offset Tx_Q vs Rx_IQ_Sum, in 10 MHz samplesphase_detectorTx_QRx_IQ_Sum 的過零偏移,以 10 MHz 取樣數計~clk10MHz2
8delay_line delays Tx_Q by the latched phase_diff (0–255)delay_line 依鎖存的 phase_diff(0–255)延遲 Tx_Qclk10MHz2
9Rx_cc = Rx_Amped − Tx_Q_Delay, 16-bit Q0 — plus Clutter_weak = ratio > 5.0 (16'd20480, Section 2)Rx_cc = Rx_Amped − Tx_Q_Delay,16 位元 Q0——另有 Clutter_weak = ratio > 5.016'd20480第 2 節combinational組合邏輯
clutter_cancel.vworking tree, the key lines
43		Tx_Q <= v;
48	amplitude_calculation       AmpCal_Tx_Q (~clk10MHz4, 1'b1, Tx_Q_Amp, Tx_Q);
49	amplitude_calculation_16bit AmpCal_Rx   (~clk10MHz4, 1'b1, Rx_Amp,   Rx_IQ_Sum);
63	divider_q_fixed #(.AW(14), .BW(16), .FRAC(12), .RW(16)) FixedDivider (
64		.clock(~clk_400),
72	always @(posedge clk_400)
76			DIV_OPT_VALID <= (Clutter_Amp_Ratio_Temp == Clutter_Amp_Ratio_Extra);
80	always @(negedge CC)
82		Clutter_Amp_Ratio <= (DIV_OPT_VALID) ? Clutter_Amp_Ratio_Extra : Clutter_Amp_Ratio;
83		phase_diff <= phase_diff_;
87	wire signed [32:0] Rx_Amped_scaled = $signed(Rx_IQ_Sum) * $signed({1'b0, Clutter_Amp_Ratio});
88	wire signed [15:0] Rx_Amped = Rx_Amped_scaled[27:12];   // >>12 undoes the Q4.12 scale, back to Q0
91	assign Clutter_weak = (Clutter_Amp_Ratio > 16'd20480);
96	phase_detector Phase_Detector (~clk10MHz2, 1'b1, phase_diff_, ...
101	delay_line Variable_Delay_Line (clk10MHz2, 1'b1, Tx_Q_Delay_wide, ...
106	assign Rx_cc = Rx_Amped - {{2{Tx_Q_Delay[13]}}, Tx_Q_Delay};

8.3 The FP era (bc691d3 to 2026-09-04)浮點時代(bc691d3 至 2026-09-04)

The same nine steps, but every value round-tripped through IEEE-754: Convert_b2FP in, a 400 MHz DIV for the ratio, MULT for step 6, an FP Subtractor for step 9, FP_Comparator for Clutter_weak (against 32'h40A00000 = 5.0f), Convert_FP2b_16bit out, and the ×128 Amplifier_For_CC_Resolve for the fine triggers.

同樣九個步驟,但每個值都經過 IEEE-754 來回轉換:Convert_b2FP 進、400 MHz 的 DIV 算比值、MULT 做第 6 步、浮點 Subtractor 做第 9 步、FP_ComparatorClutter_weak(比較 32'h40A00000 = 5.0f)、Convert_FP2b_16bit 出,還有餵精細觸發器的 ×128 Amplifier_For_CC_Resolve

Plus an operator trim: four switches Amp_Sel chose +0.00…+0.15 (Amp_CC_Add) or ×1.00…×0.85 (Amp_CC_Mult) applied to the measured ratio, with Mult_or_Add choosing which — two 16-entry float tables. Removed in 29126a6 (2026-09-04, "as master did"). fb07518's rationale for the whole conversion: "pure format round-trips in every case except the divide, none of which needed float precision for values that fit exactly in 14/16-bit integers."

另有操作者微調:四個開關 Amp_Sel 選擇對量測比值加 +0.00…+0.15(Amp_CC_Add)或乘 ×1.00…×0.85(Amp_CC_Mult),由 Mult_or_Add 決定用哪個——兩張 16 筆的浮點表。已在 29126a6(2026-09-04,「如 master 所做」)移除。fb07518 對整個轉換的理由:「除了除法之外,每一處都只是純格式的來回轉換,而這些能精確放進 14/16 位元整數的值都不需要浮點精度。」

8.4 Master's version (c786cd6, Q8.16) vs de25 todaymaster 的版本(c786cd6,Q8.16)對照今日 de25

aspect面向de25 today今日 de25master c786cd6 (2026-09-10)master c786cd6(2026-09-10)
divider除法器divider_q_fixed on the 400 MHz PLL (~clk_400)divider_q_fixed,跑在 400 MHz PLL(~clk_400div_fx, free-running on ~clk10MHz4; no PLL clock in the modulediv_fx,自由運行於 ~clk10MHz4;模組內沒有 PLL 時脈
ratio range比值範圍Q4.12, saturates at 15.99Q4.12,在 15.99 飽和Q8.16, up to 255.99Q8.16,最高 255.99
KEY3 latchKEY3 鎖存asynchronous always @(negedge CC)非同步 always @(negedge CC)3-flop synchroniser + falling-edge detect on clk10MHz43 級同步器 + clk10MHz4 上的下降緣偵測
Rx_cctruncated 16-bit Q0 before the ×128 fine trigger在 ×128 精細觸發器之前就截成 16 位元 Q032-bit Q24.8, keeps sub-LSB resolution32 位元 Q24.8,保留次 LSB 解析度
also其他no power-up initialisers沒有上電初值initialisers; registered Rx_Amped; phase_detector on sign bits only; RAM delay_line; parameterised amplitude_calc有初值;Rx_Amped 有暫存;phase_detector 只看符號位元;RAM 式 delay_line;參數化的 amplitude_calc

8.5 Two consequences on hardware對硬體的兩個後果

What the de25 canceller does today今日 de25 消除器的實際行為

(a) Q4.12 saturation. Whenever the received signal is weaker than Tx/16 the ratio clamps at 15.99 and the subtraction is wrong — exactly the weak-return regime the fine triggers exist for.

(a)Q4.12 飽和。只要接收訊號弱於 Tx/16,比值就卡在 15.99,減法就是錯的——而這正是精細觸發器存在的理由:弱回波的情境。

(b) clk_400 is still consumed by de25's clutter_cancel. CLAUDE.md's "nothing uses clk_400" describes master, not de25; PLL400 cannot be removed on this branch until the divider moves off it.

(b)clk_400 仍被使用——de25 的 clutter_cancel 還在用它。CLAUDE.md 所寫的「沒有東西使用 clk_400」描述的是 master,不是 de25;在除法器搬離之前,這個分支不能移除 PLL400

These two are the concrete content of the still-open "port master's canceller" decision — together with Section 2's threshold caveat (20480327680).

這兩點就是仍懸而未決的「移植 master 消除器」決定的具體內容——連同第 2 節的門檻提醒(20480327680)。

8.6 History歷史

commitdatechange改動
c5666282026-09-04canceller extracted from the core into clutter_cancel.v消除器從核心抽出成 clutter_cancel.v
29126a62026-09-04the Amp_Sel / Mult_or_Add operator trim removed移除 Amp_SelMult_or_Add 操作者微調
76e2a11 / fb075182026-09-04/05fixed point, Q4.12; all FP IP dropped定點化,Q4.12;所有浮點 IP 移除
bae73362026-09-05amplifier + Schmitt triggers to fixed point放大器與施密特觸發器定點化
6de67932026-09-05comments trimmed整理註解
6fa49ce2026-09-11rename new40kHz4_1Mhz_beta010更名 new40kHz4_1Mhz_beta010
c786cd6 (master)2026-09-10refined Q8.16 canceller — not on de25精煉的 Q8.16 消除器——不在 de25 上

9. Rx_ir_B_d1 / _d2 — why both clock edges, from the recordRx_ir_B_d1/_d2 — 為何兩個時脈邊緣都用,依紀錄

This extends Section 5, which judged the two-stage chain on today's sources. The git record explains why it was built that way.

本節延伸第 5 節——該節以今日的來源評判這條兩級鏈。git 紀錄則解釋了它當初為何這樣蓋。

9.1 The source in bc691d3bc691d3 中的來源

In bc691d3 the two inputs of this chain were not slow register outputs. Rx_B and Rx_cc_B were the outputs of Convert_FP2b_16bit converters clocked on ~clk_400 — the falling edge of the 400 MHz PLL. Each input could change in any 2.5 ns slot inside a 100 ns clk10MHz5 period, in a PLL domain with no fixed phase relation to the divider-derived 10 MHz clocks.

bc691d3 裡,這條鏈的兩個輸入不是慢速的暫存器輸出。Rx_BRx_cc_BConvert_FP2b_16bit 轉換器的輸出,時脈是 ~clk_400——400 MHz PLL 的下降緣。每個輸入都可能在 100 ns 的 clk10MHz5 週期內任一個 2.5 ns 的時槽改變,而且位於一個與除頻器衍生的 10 MHz 時脈沒有固定相位關係的 PLL 時脈域。

chain @ bc691d3source → two stages → extractor
Convert_FP2b_16bit (~clk_400, 400 MHz PLL)      // fast, phase-unrelated source
  → Rx_B / Rx_cc_B
  → posedge clk10MHz5 → Rx_B_Temp               // stage 1: capture across the crossing
  → negedge clk10MHz5 → Rx_ir_B_Reg             // stage 2: re-present half a cycle early
  → peak_extractor (posedge clk10MHz5)

9.2 The two jobs兩個任務

  1. Stage 1 (posedge, _Temp, now _d1) — the capture across the 400 MHz → 10 MHz crossing.
    第一級(posedge_Temp,現為 _d1——跨越 400 MHz → 10 MHz 的擷取。
  2. Stage 2 (negedge, _Reg, now _d2) — re-present the captured value half a cycle before the extractor's own rising edge, so the extractor never samples an input changing on the very edge it uses. The author's habitual belt-and-braces idiom: the same falling-edge re-time appears in Rx_Debounce and in the ADC capture.
    第二級(negedge_Reg,現為 _d2——在擷取器自己的上升緣前半個週期重新呈現擷取到的值,讓擷取器永遠不會在它使用的那個邊緣取樣到正在變化的輸入。這是作者慣用的雙重保險手法:Rx_Debounce 與 ADC 擷取裡有同樣的下降緣重定時。

Against that source, defensible.

面對那樣的來源,這是站得住腳的。

9.3 Why it no longer applies為何不再適用

The converters were removed on 2026-09-04 (76e2a11 / fb07518). Today Rx_ir_B is a plain clk10MHz6 register and Rx_cc_B a combinational subtraction off clk10MHz2 registers — slow, and in the same divider family. Stage 1 remains the legitimate domain boundary; stage 2 only halves the timing budget on both sides, and a maximum between zero crossings cannot see a constant half-cycle delay.

轉換器已於 2026-09-04(76e2a11fb07518)移除。今日 Rx_ir_B 只是一個 clk10MHz6 暫存器,Rx_cc_B 是源自 clk10MHz2 暫存器的組合減法——速度慢,且屬同一除頻器家族。第一級仍是正當的時脈域邊界;第二級只會把兩側的時序預算砍半,而兩次過零之間的最大值看不見固定的半週期延遲。

The reason disappeared silently理由悄悄消失了

The FP removal deleted the fast source but left the two-stage chain built for it in place. Nothing was wrong at the time it was written; the premise was removed later without revisiting what depended on it. The proposed one-block form and its verification are in Section 5.4.

移除浮點時刪掉了快速來源,卻把為它而建的兩級鏈留了下來。寫下時沒有任何錯誤;是前提後來被移除了,而依賴它的部分沒有被重新檢視。建議的單一區塊寫法與驗證方式在第 5.4 節