A long-form walk through every line of the Lecture 7 reference list: what each data type, each operator, each statement and each loop form actually becomes in a standard-cell netlist, and where the area and the critical path really go.本頁逐行走過第 7 講的參考清單:每一種資料型態、每一個運算子、每一道敘述、每一種迴圈形式,在標準元件 netlist 裡究竟會變成什麼,以及面積與關鍵路徑真正花在哪裡。第 7 講の参照リストを一行ずつたどる長編解説。各データ型、各演算子、各文、各ループ形式がスタンダードセルのネットリスト上で実際に何になるのか、そして面積とクリティカルパスが本当にどこへ消えるのかを示す。
Lecture 7 is, on the slides, a list. Nets are retained or eliminated; integers default to thirty-two bits; x is allowed here and not there; this operator becomes combinational logic and that one becomes muxes. Read as a list it is impossible to remember and even harder to apply, because every entry is the conclusion of an argument whose premises are not on the slide. This page supplies the premises. Every rule in the lecture follows from one of three facts: a standard-cell library contains only a few hundred fixed gate shapes, a synthesiser is a compiler that must produce a finite netlist from a finite source text, and silicon carries only two voltages plus the absence of a driver. Once you have those three facts in mind, almost nothing in the lecture needs to be memorised; it can be re-derived.第 7 講在投影片上就是一張清單。net 有的保留、有的被消除;integer 預設三十二位元;x 這裡能用、那裡不能用;這個運算子變成組合邏輯、那個運算子變成多工器。當成清單來背,既記不住,更難應用,因為每一條都是某個論證的結論,而論證的前提並不在投影片上。本頁要補上那些前提。這一講的每一條規則都源自三項事實之一:標準元件庫裡只有數百種固定的閘形狀;合成器是一個編譯器,必須從有限的原始碼產生有限的 netlist;而矽晶片上只有兩種電壓,外加「沒有驅動源」這個狀態。只要把這三項事實放在心裡,這一講幾乎沒有什麼需要死記,都可以重新推導出來。第 7 講はスライド上ではひとつのリストである。net は残されるか除去されるか、integer は既定で 32 ビット、x はここでは許されるがあそこでは許されない、この演算子は組合せ論理になりあの演算子はマルチプレクサになる。リストとして読むと覚えることは不可能で、適用するのはさらに難しい。各項目は、前提がスライドに載っていない議論の結論だからである。本ページはその前提を補う。この講義の規則はすべて三つの事実のいずれかから導かれる。スタンダードセルライブラリには数百種の固定したゲート形状しかないこと、合成器は有限のソーステキストから有限のネットリストを生成しなければならないコンパイラであること、そしてシリコンは二つの電圧とドライバ不在しか運べないこと。この三つを念頭に置けば、この講義で暗記すべきものはほとんどなく、すべて再導出できる。
It helps enormously to stop thinking of synthesis as a single translation step. A cell-based synthesiser such as Design Compiler, Genus or Vivado's synthesis engine does three distinguishable things, and nearly every rule in this lecture belongs to exactly one of them.把合成想成單一的翻譯步驟,是很大的阻礙。像 Design Compiler、Genus 或 Vivado 合成引擎這類 cell-based 合成器,會做三件可以分開來看的事,而這一講幾乎每一條規則都正好屬於其中一件。合成を単一の変換ステップと考えるのをやめると理解が大きく進む。Design Compiler、Genus、Vivado の合成エンジンといったセルベース合成器は、区別できる三つの仕事を行い、この講義のほぼすべての規則はそのいずれか一つに属する。
The first pass is elaboration. The tool reads the text, applies compiler directives, resolves parameters, works out the width and signedness of every expression, builds the module hierarchy and expands anything that can be expanded statically: generate blocks, parameterised widths, tasks, functions and static loops. After elaboration there is no such thing as a for loop and no such thing as a task call. There is only a flat soup of assignments. This pass is why a loop whose trip count the compiler cannot compute is fatal, why a task has to be inlined, why `define works and why an integer starts out thirty-two bits wide.第一個階段是展開(elaboration)。工具讀入文字、套用編譯指令、解析參數、算出每個運算式的位寬與有號性、建立模組階層,並把所有能在靜態期展開的東西展開:generate 區塊、參數化位寬、task、function,以及靜態迴圈。展開之後,世界上不再有 for 迴圈,也不再有 task 呼叫,只剩下一鍋扁平的指定敘述。這個階段解釋了為什麼編譯器算不出迴圈次數就是致命錯誤、為什麼 task 必須被內聯、為什麼 `define 有效,以及為什麼 integer 一開始就是三十二位元寬。第一のパスはエラボレーションである。ツールはテキストを読み、コンパイラ指令を適用し、パラメータを解決し、すべての式のビット幅と符号属性を決定し、モジュール階層を構築し、静的に展開できるものをすべて展開する。generate ブロック、パラメータ化された幅、タスク、関数、そして静的ループである。エラボレーション後には for ループもタスク呼び出しも存在しない。あるのは代入のフラットなスープだけである。このパスがあるからこそ、コンパイラが反復回数を計算できないループは致命的であり、タスクはインライン展開されねばならず、`define が機能し、integer が 32 ビット幅から始まるのである。
The second pass is inference, sometimes called RTL synthesis or generic mapping. The tool looks at the elaborated assignments and decides which storage elements are implied — flip-flops, latches, tri-state drivers — and builds a technology-independent network of generic operators: a generic adder, a generic comparator, a generic multiplexer, a generic register. This pass is where the x/z rules, the event-control rules and the blocking/non-blocking distinction live. Lectures 5 and 6 of this course are almost entirely about this pass; Lecture 7 is about what it does to each individual language construct.第二個階段是推論(inference),有時稱為 RTL 合成或泛用對應。工具檢視展開後的指定敘述,判斷隱含了哪些儲存元件 — 正反器、閂鎖、三態驅動器 — 並建立一個與製程無關的泛用運算子網路:泛用加法器、泛用比較器、泛用多工器、泛用暫存器。x/z 規則、事件控制規則,以及阻塞式與非阻塞式的區別,都住在這個階段。本課程的第 5、6 講幾乎完全在講這個階段;第 7 講則是講它對每一個個別語言構件做了什麼。第二のパスは推論(inference)で、RTL 合成あるいはジェネリックマッピングとも呼ばれる。ツールはエラボレーション済みの代入を見て、どの記憶素子が暗示されているか — フリップフロップ、ラッチ、トライステートドライバ — を判断し、テクノロジ非依存のジェネリック演算子のネットワークを構築する。ジェネリック加算器、ジェネリック比較器、ジェネリックマルチプレクサ、ジェネリックレジスタである。x/z の規則、イベント制御の規則、ブロッキングとノンブロッキングの区別はすべてこのパスに属する。本科目の第 5 講と第 6 講はほぼ全体がこのパスの話であり、第 7 講はそれが個々の言語構文に対して何をするかの話である。
The third pass is optimisation and technology mapping. The generic network is flattened into Boolean equations where profitable, restructured against the timing constraints, and then covered with real library cells — a NAND2X1 here, a DFFRPQ there, a carry-select adder from the DesignWare library instead of the generic one. This pass is why internal nets disappear, why an integer that only ever holds values below eight shrinks to three bits, why a don't care is valuable, and why the same source text can produce two visibly different schematics on two different runs, as the lecture's two synthesis results for the same 4-to-1 multiplexer demonstrate.第三個階段是最佳化與製程對應。泛用網路在有利時被攤平成布林方程式,依時序限制重新結構化,然後用真實的庫元件覆蓋 — 這裡放一顆 NAND2X1、那裡放一顆 DFFRPQ,用 DesignWare 庫裡的 carry-select 加法器取代泛用加法器。這個階段解釋了為什麼內部 net 會消失、為什麼只存放小於八的值的 integer 會縮成三位元、為什麼 don't care 很有價值,以及為什麼同一份原始碼在兩次執行中可以產生兩張明顯不同的電路圖 — 正如這一講對同一個 4 對 1 多工器給出的兩種合成結果所示。第三のパスは最適化とテクノロジマッピングである。ジェネリックネットワークは有利な箇所でブール式に平坦化され、タイミング制約に対して再構成され、実在のライブラリセルで覆われる。ここに NAND2X1、あそこに DFFRPQ、ジェネリック加算器の代わりに DesignWare ライブラリのキャリーセレクト加算器、といった具合である。このパスがあるから内部 net は消え、8 未満の値しか保持しない integer は 3 ビットに縮み、ドントケアに価値が生まれ、同じソーステキストが二回の実行で見た目の異なる回路図を生む。講義が同じ 4 対 1 マルチプレクサについて二つの合成結果を示しているとおりである。
`define/`include, integer width, expression width and signedness, parameter resolution.展開:迴圈展開、task 與 function 內聯、`define/`include、integer 位寬、運算式位寬與有號性、參數解析。エラボレーション:ループ展開、タスクと関数のインライン化、`define/`include、integer の幅、式の幅と符号属性、パラメータ解決。z, asynchronous versus synchronous branches, blocking/non-blocking ordering, don't-care handling.推論:正反器、閂鎖或單純的 wire、由 z 推出的三態、非同步分支與同步分支、阻塞式與非阻塞式的順序、don't care 的處理。推論:フリップフロップかラッチか単なる wire か、z から導かれるトライステート、非同期分岐と同期分岐、ブロッキング/ノンブロッキングの順序、ドントケアの扱い。Verilog, as standardised in IEEE 1364, is a simulation language. It was designed to describe the behaviour of a digital system over time for an event-driven simulator, and a great deal of it describes things no circuit can do: wait an arbitrary number of nanoseconds, print a message, read a file, hold a real number, compare against an unknown. The synthesisable subset is the fragment of the language for which a sensible, deterministic, finite circuit exists. Everything Lecture 7 marks as unsupported is unsupported for one of exactly two reasons: either the construct has no finite hardware realisation at all, or it has one but the realisation would depend on information the tool does not have.Verilog 在 IEEE 1364 中標準化,本質上是一種模擬語言。它的設計目的是為事件驅動模擬器描述數位系統隨時間的行為,其中有很大一部分描述的是任何電路都做不到的事:等待任意奈秒數、印出訊息、讀檔、保存實數、拿未知值去比較。可合成子集就是語言中「存在一個合理、確定且有限的電路」的那一塊。第 7 講標示為不支援的每一項,不支援的理由恰好只有兩種:要嘛該構件根本沒有有限的硬體實現,要嘛有,但實現方式取決於工具手上沒有的資訊。IEEE 1364 で標準化された Verilog はシミュレーション言語である。イベント駆動シミュレータ向けにディジタルシステムの時間的ふるまいを記述するために設計されており、その大部分はどんな回路にもできないことを記述する。任意のナノ秒数だけ待つ、メッセージを印字する、ファイルを読む、実数を保持する、未知値と比較する、といったことである。合成可能サブセットとは、妥当で決定的かつ有限な回路が存在する言語の断片のことである。第 7 講が非対応と記すものはすべて、二つの理由のいずれかで非対応となる。その構文に有限のハードウェア実現がまったく存在しないか、存在するがその実現がツールの持たない情報に依存するかである。
real is in the first category. A floating-point variable would need a floating-point unit, and Verilog gives no indication of precision, rounding mode or exception behaviour, so no tool guesses. A non-static loop is in the first category too: the circuit size would depend on the data, and circuits have a fixed size. Delay controls are in the second category: #5 means five time units, and the tool has no idea how many gate delays that is in the final technology, so it either ignores the delay or — worse, and the lecture explicitly warns about this — reinterprets it as a clock boundary.real 屬於第一類。浮點變數需要一個浮點運算單元,而 Verilog 完全沒有指出精度、捨入模式或例外行為,所以沒有工具會去猜。非靜態迴圈也屬於第一類:電路大小會取決於資料,但電路的大小是固定的。延遲控制屬於第二類:#5 代表五個時間單位,工具根本不知道在最終製程裡那是幾個閘延遲,所以它要嘛忽略這個延遲,要嘛 — 更糟,而且這一講明確提出警告 — 把它重新解釋成一個時脈邊界。real は第一のカテゴリに属する。浮動小数点変数には浮動小数点ユニットが必要だが、Verilog は精度も丸めモードも例外動作も示さないため、どのツールも推測しない。非静的ループも第一のカテゴリである。回路規模がデータに依存してしまうが、回路の規模は固定だからである。遅延制御は第二のカテゴリに属する。#5 は 5 時間単位を意味するが、それが最終テクノロジで何ゲート遅延に当たるかツールには分からない。そのため遅延を無視するか、あるいは — さらに悪く、講義が明示的に警告しているとおり — それをクロック境界と読み替えてしまう。
A useful habit is to ask, for every construct you are tempted to write, "what finite collection of library cells has exactly this behaviour?" If you cannot answer, neither can the tool, and it will either error out or do something arbitrary. The painful case is the second one, because the design will then simulate correctly before synthesis and fail after it.一個很有用的習慣是,對每一個你想寫下的構件自問:「哪一組有限的庫元件,行為恰好就是這樣?」如果你答不出來,工具也答不出來,它要嘛報錯,要嘛做出某種任意的行為。麻煩的是後者,因為這樣的設計在合成前模擬正確,合成後卻會失效。有用な習慣は、書こうとするすべての構文について「ライブラリセルのどんな有限の集まりが、ちょうどこのふるまいを持つのか」と自問することである。答えられないならツールにも答えられず、エラーを出すか恣意的な何かをする。厄介なのは後者である。その設計は合成前には正しくシミュレーションされ、合成後に破綻するからである。
Throughout this page the cost of a construct is given in two currencies. Area is counted in two-input gate equivalents (GE), where one GE is roughly the area of a two-input NAND cell. On a typical standard-cell library a two-input NAND or NOR is 1 GE, an inverter about 0.7, a two-input XOR or XNOR about 2 to 2.5, a two-to-one multiplexer about 2 to 3, a full adder about 5 to 7, and a positive-edge D flip-flop with asynchronous reset about 5 to 6. Delay is counted in levels of logic, meaning the number of gates on the longest input-to-output path, because within one technology the delay of a path is roughly proportional to its depth once fanout is accounted for.本頁一律用兩種貨幣來標示構件的成本。面積以二輸入等效閘(GE)計算,一個 GE 約等於一顆二輸入 NAND 的面積。在典型的標準元件庫中,二輸入 NAND 或 NOR 約 1 GE,反相器約 0.7,二輸入 XOR 或 XNOR 約 2 到 2.5,二對一多工器約 2 到 3,全加器約 5 到 7,帶非同步重置的正緣觸發 D 型正反器約 5 到 6。延遲以邏輯層級數計算,也就是從輸入到輸出最長路徑上的閘數;因為在同一製程內,一旦把扇出納入考量,路徑延遲大致與其深度成正比。本ページでは構文のコストを常に二つの通貨で示す。面積は 2 入力ゲート等価(GE)で数え、1 GE はおよそ 2 入力 NAND セルの面積である。典型的なスタンダードセルライブラリでは、2 入力 NAND または NOR が 1 GE、インバータが約 0.7、2 入力 XOR または XNOR が約 2〜2.5、2 対 1 マルチプレクサが約 2〜3、全加算器が約 5〜7、非同期リセット付き立ち上がりエッジ D フリップフロップが約 5〜6 である。遅延は論理段数、すなわち入力から出力までの最長経路上のゲート数で数える。一つのテクノロジ内では、ファンアウトを考慮すれば経路遅延はおおむねその深さに比例するからである。
These numbers are deliberately coarse. The point is never the third significant figure; the point is the scaling law. A ripple-carry adder grows as $O(n)$ in both area and delay. A carry-look-ahead adder trades area for depth and grows as $O(n\log n)$ in area with $O(\log n)$ delay. An array multiplier is $O(n^2)$ in area. A barrel shifter is $O(n\log n)$. A restoring divider built combinationally is $O(n^2)$ in both. Knowing which exponent attaches to which operator is what lets you look at an RTL line and predict, without running the tool, whether it will cost you ten gates or ten thousand.這些數字刻意取得很粗略。重點從來不是第三位有效數字,而是尺度律。漣波進位加法器在面積與延遲上都是 $O(n)$ 成長。超前進位加法器拿面積換深度,面積 $O(n\log n)$、延遲 $O(\log n)$。陣列乘法器面積是 $O(n^2)$。桶式移位器是 $O(n\log n)$。用組合邏輯做的回復式除法器兩者皆為 $O(n^2)$。知道哪個指數配哪個運算子,才能讓你看著一行 RTL,不必跑工具就預測它要花你十個閘還是一萬個閘。これらの数値は意図的に粗い。要点は有効数字 3 桁目ではなく、スケーリング則である。リップルキャリー加算器は面積・遅延ともに $O(n)$ で増える。キャリー先見加算器は面積と深さを交換し、面積 $O(n\log n)$、遅延 $O(\log n)$ となる。アレイ乗算器は面積が $O(n^2)$。バレルシフタは $O(n\log n)$。組合せ回路として作った引き戻し除算器は両方とも $O(n^2)$ である。どの指数がどの演算子に付くかを知っていれば、RTL の一行を見ただけで、ツールを走らせずに 10 ゲートで済むのか 1 万ゲートかかるのかを予測できる。
Sections 2 and 3 cover data; sections 4 to 6 cover every operator class; sections 7 to 9 cover statements and timing; sections 10 to 12 cover loops, subprograms and directives. Each section ends with practice problems whose answers are collapsed. If you are revising the night before an exam, read the thirteen summary cards in section 13 first, then come back to whichever section they expose as weak.第 2、3 節講資料;第 4 到 6 節講每一類運算子;第 7 到 9 節講敘述與時序;第 10 到 12 節講迴圈、副程式與指令。每一節結尾都有練習題,答案是收合的。如果你是在考前一晚複習,先讀第 13 節的十三張總結卡,再回頭看它們暴露出你最弱的那一節。第 2 節と第 3 節はデータ、第 4〜6 節はすべての演算子クラス、第 7〜9 節は文とタイミング、第 10〜12 節はループ・サブプログラム・指令を扱う。各節の末尾には解答を折りたたんだ練習問題がある。試験前夜の復習なら、まず第 13 節の 13 枚のまとめカードを読み、そこで弱点と分かった節に戻るとよい。
Verilog's two families of data objects, nets and variables, exist because the language models two different physical ideas. A net is a connection — a piece of metal with one or more drivers — and its value is continuously resolved from whatever drives it. A variable (declared reg, integer, real and so on) is a piece of storage in the simulator that holds the last value procedurally written to it. Synthesis treats the two families almost identically, which surprises students, and the surprises are exactly what the first two slides of this lecture are about.Verilog 的兩大類資料物件 — net 與變數 — 之所以存在,是因為這個語言要模擬兩種不同的物理概念。net 是一條連線 — 一段帶有一個或多個驅動源的金屬 — 它的值由驅動它的東西持續解析而來。變數(宣告為 reg、integer、real 等)則是模擬器裡的一塊儲存空間,保存最後一次以程序方式寫入的值。合成對這兩類的處理幾乎完全相同,這常讓學生意外,而這些意外正是這一講前兩張投影片的主題。Verilog のデータオブジェクトの二つの系統、net と変数が存在するのは、この言語が二つの異なる物理的概念をモデル化しているからである。net は接続 — 一つ以上のドライバを持つ金属配線 — であり、その値は駆動するものから連続的に解決される。変数(reg、integer、real などで宣言)はシミュレータ内の記憶領域で、手続き的に最後に書き込まれた値を保持する。合成はこの二つをほぼ同一に扱い、それが学生を驚かせる。その驚きこそ、この講義の最初の 2 枚のスライドの主題である。
The lecture's first rule is that nets declared as input or output ports are retained, while internal nets may be eliminated by logic optimisation. Both halves of that statement follow from the mapping pass.這一講的第一條規則是:宣告為 input 或 output 埠的 net 會被保留,而內部 net 可能被邏輯最佳化消除。這句話的兩半都源自對應階段。講義の最初の規則は、input または output ポートとして宣言された net は残されるが、内部 net は論理最適化によって除去されうる、というものである。この言明の両半分とも、マッピングパスから導かれる。
A port is part of the module's interface, and the interface is a contract. Something outside the module — another module, a pad cell, the test bench, the place-and-route tool's floorplan — refers to that port by name. If the optimiser deleted a port because the logic inside happened not to use it, every netlist consumer downstream would break: the instantiating module's port connection list would no longer match, the gate-level test bench would fail to bind, the formal equivalence check would report a different interface. So ports are frozen. The lecture makes this point with the ALU example in which the input s2 ends up driving nothing at all after optimisation, yet still appears as a dangling port on the schematic. A dangling input port is not a bug in the tool; it is the tool obeying the contract.埠是模組介面的一部分,而介面是一份契約。模組外部的某樣東西 — 另一個模組、一顆 pad cell、測試平台、place-and-route 工具的 floorplan — 會用名字去指涉那個埠。如果最佳化器只因為模組內部剛好沒用到就刪掉一個埠,下游每一個 netlist 使用者都會壞掉:實例化模組的埠連接清單不再吻合、閘級測試平台綁不上去、形式等價檢查會回報介面不同。所以埠被凍結。這一講用 ALU 範例說明這點:輸入 s2 在最佳化後完全沒有驅動任何東西,卻仍以一個懸空的埠出現在電路圖上。懸空的輸入埠不是工具的錯誤,而是工具在遵守契約。ポートはモジュールのインタフェースの一部であり、インタフェースは契約である。モジュールの外側にある何か — 別のモジュール、パッドセル、テストベンチ、配置配線ツールのフロアプラン — がそのポートを名前で参照する。内部の論理がたまたま使っていないという理由で最適化器がポートを削除したら、下流のネットリスト利用者はすべて壊れる。インスタンス化側のポート接続リストが一致しなくなり、ゲートレベルのテストベンチはバインドできず、形式等価性検査は異なるインタフェースを報告する。だからポートは凍結される。講義はこの点を ALU の例で示す。入力 s2 は最適化後に何も駆動しなくなるが、それでも回路図上に宙に浮いたポートとして現れる。宙に浮いた入力ポートはツールのバグではなく、ツールが契約を守っている姿である。
An internal net has no such contract. If you write wire t = a & b; assign y = t | c; the net t exists only so that you could name the intermediate result. After the optimiser flattens the two equations into $y = ab + c$ and covers it with, say, a single AND-OR-INVERT cell plus an inverter, there is no wire in the netlist corresponding to t at all — the signal lives inside a cell, between two transistor stacks, where no probe can reach it. The net has been absorbed. This is normal and desirable: absorbing intermediate nets is precisely how multi-level logic optimisation reduces area.內部 net 沒有這樣的契約。如果你寫 wire t = a & b; assign y = t | c;,net t 存在的唯一理由只是讓你能替中間結果取名字。等最佳化器把兩條方程式攤平成 $y = ab + c$,再用比方說一顆 AND-OR-INVERT 元件加一顆反相器來覆蓋,netlist 裡就完全沒有對應 t 的連線了 — 那個訊號住在元件內部、兩組電晶體堆疊之間,任何探針都碰不到。這條 net 被吸收了。這是正常而且值得期待的:吸收中間 net 正是多層邏輯最佳化降低面積的手段。内部 net にはそのような契約がない。wire t = a & b; assign y = t | c; と書いたとき、net t は中間結果に名前を付けられるようにするためだけに存在する。最適化器が二つの式を $y = ab + c$ に平坦化し、たとえば AND-OR-INVERT セル一個とインバータ一個で覆ってしまえば、ネットリストに t に対応する配線はまったく存在しない。その信号はセルの内部、二つのトランジスタスタックの間にあり、どんなプローブも届かない。net は吸収されたのである。これは正常かつ望ましい。中間 net の吸収こそ、多段論理最適化が面積を削減する方法そのものだからである。
It becomes a problem only when you wanted to see that net — for debug, for a scan observation point, for a manual clock gate, or because you are hand-instantiating a cell and need the connection to survive. That is what the lecture's third bullet refers to: "the user may force a net to exist by special instructions provided in the synthesis tool". The mechanism is tool-specific and attribute-based. In Design Compiler one writes set_dont_touch on the net, or marks the signal /* synopsys keep_signal_name */; in Vivado the attribute is (* keep = "true" *) or (* dont_touch = "true" *); in Genus it is set_dont_touch as well. These are not Verilog. They are directives to a particular tool, and a design that depends on them is less portable than one that does not.只有當你本來就想看到那條 net 時,它才會變成問題 — 為了除錯、為了一個掃描觀測點、為了手動插時脈閘,或因為你正在手工實例化某顆元件而需要那個連接存活下來。這正是這一講第三個要點所指的:「使用者可以用合成工具提供的特殊指令強迫一條 net 存在」。這個機制因工具而異,而且是以屬性為基礎。在 Design Compiler 裡要對該 net 下 set_dont_touch,或把訊號標上 /* synopsys keep_signal_name */;在 Vivado 裡屬性是 (* keep = "true" *) 或 (* dont_touch = "true" *);在 Genus 裡同樣是 set_dont_touch。這些都不是 Verilog,而是給特定工具的指令;依賴它們的設計,可攜性比不依賴的差。問題になるのは、その net を見たかった場合だけである。デバッグのため、スキャンの観測点のため、手動クロックゲートのため、あるいはセルを手でインスタンス化していてその接続を残す必要があるため、といった場合である。これが講義の三つ目の項目が指すところで、「合成ツールが提供する特別な指示によって、ユーザは net を強制的に存在させられる」。この仕組みはツール固有で、属性に基づく。Design Compiler では net に set_dont_touch を書くか、信号に /* synopsys keep_signal_name */ を付ける。Vivado では属性が (* keep = "true" *) または (* dont_touch = "true" *)、Genus でも同じく set_dont_touch である。これらは Verilog ではなく、特定のツールへの指令である。これらに依存する設計は、依存しない設計より可搬性が低い。
Forcing a net to exist forbids the optimiser from absorbing the logic on either side of it, so the two cones must be covered separately. On a deep arithmetic path a single stray keep can add a full gate delay and a buffer. Use it for debug and remove it before the production run; never sprinkle it over a design as a style.強迫一條 net 存在,就是禁止最佳化器吸收它兩側的邏輯,於是兩個邏輯錐必須分開覆蓋。在很深的算術路徑上,一個隨手留下的 keep 就可能多出一整個閘延遲與一顆緩衝器。除錯時用它,正式執行前拿掉;絕不要把它當成風格到處灑。net を強制的に存在させることは、その両側の論理を最適化器が吸収するのを禁じることであり、二つのコーンは別々に覆わねばならない。深い算術パスでは、うっかり残した keep 一つでゲート遅延一段とバッファが増えうる。デバッグには使い、製品向けの実行前には外すこと。スタイルとして設計中に撒き散らしてはならない。
The net types wand and wor describe a net with several drivers whose values are resolved by a wired-AND or wired-OR rule rather than by the usual conflict rule. Historically this described a real electrical arrangement: several open-collector or open-drain transistors pulling a line down against a pull-up resistor gave a wired-AND for active-high data, or a wired-OR for active-low data. In a modern cell-based CMOS flow that arrangement does not exist. There is no pull-up resistor in a standard-cell library, and a net with two actively driving CMOS outputs is a short circuit.net 型態 wand 與 wor 描述的是一條有多個驅動源的 net,其值不是用一般的衝突規則,而是用 wired-AND 或 wired-OR 規則來解析。歷史上這描述的是真實的電氣配置:幾顆開集極或開汲極電晶體把一條線往下拉、對抗一顆提升電阻,對高態有效的資料就是 wired-AND,對低態有效的資料就是 wired-OR。在現代 cell-based CMOS 流程裡,這種配置並不存在。標準元件庫裡沒有提升電阻,而一條接了兩個主動驅動 CMOS 輸出的 net 就是短路。net 型 wand と wor は、複数のドライバを持ち、その値が通常の衝突規則ではなくワイヤード AND/ワイヤード OR の規則で解決される net を記述する。歴史的にはこれは実在の電気的構成を表していた。複数のオープンコレクタまたはオープンドレインのトランジスタがプルアップ抵抗に抗して線を引き下げ、正論理データならワイヤード AND、負論理データならワイヤード OR になった。現代のセルベース CMOS フローにこの構成は存在しない。スタンダードセルライブラリにプルアップ抵抗はなく、CMOS 出力が二つアクティブに駆動する net は短絡である。
So the synthesiser does the obvious thing: it collects the drivers and replaces the resolution function with an explicit gate. A wand with $k$ drivers becomes a $k$-input AND; a wor with $k$ drivers becomes a $k$-input OR. The cost is the cost of that gate — for $k$ drivers, roughly $k-1$ two-input gates arranged in a tree of depth $\lceil \log_2 k \rceil$, which the mapper may of course cover with wider library cells. The behaviour is the same for 0 and 1; it differs for x and z, but since the netlist has no x, that difference does not matter after synthesis.所以合成器做的是顯而易見的事:把所有驅動源收集起來,用一個明確的閘取代解析函式。有 $k$ 個驅動源的 wand 變成一個 $k$ 輸入 AND;有 $k$ 個驅動源的 wor 變成一個 $k$ 輸入 OR。成本就是那個閘的成本 — $k$ 個驅動源大約是 $k-1$ 顆二輸入閘排成深度 $\lceil \log_2 k \rceil$ 的樹,當然對應器可能改用更寬的庫元件來覆蓋。對 0 與 1 而言行為完全相同;對 x 與 z 則不同,但既然 netlist 裡沒有 x,合成後那個差異就無關緊要。そこで合成器は当然のことをする。ドライバを集め、解決関数を明示的なゲートで置き換えるのである。ドライバが $k$ 個の wand は $k$ 入力 AND になり、$k$ 個の wor は $k$ 入力 OR になる。コストはそのゲートのコスト、すなわち $k$ 個のドライバに対しておよそ $k-1$ 個の 2 入力ゲートが深さ $\lceil \log_2 k \rceil$ の木に並ぶ分である。もちろんマッパはより多入力のライブラリセルで覆うこともある。0 と 1 に対するふるまいは同じで、x と z では異なるが、ネットリストに x は存在しないので合成後その差は問題にならない。
wand busy; // three units, each pulls busy low when idle assign busy = unit0_busy; assign busy = unit1_busy; assign busy = unit2_busy; // synthesises to: busy = unit0_busy & unit1_busy & unit2_busy
The practical advice is to write the AND yourself. The wired form saves nothing, surprises readers, and multiple continuous assignments to one name trip up lint rules and some tools' multi-driver checks. The reason the lecture mentions it at all is that legacy code — especially code imported from a board-level or TTL-era description — still contains it, and you need to recognise that it is harmless rather than a multi-driver error.實務上的建議是自己把 AND 寫出來。wired 形式什麼也沒省到,會讓讀者困惑,而且對同一個名字做多次連續指定會觸發 lint 規則與某些工具的多驅動源檢查。這一講之所以要提它,是因為舊程式碼 — 尤其是從板級或 TTL 時代描述移植過來的程式碼 — 仍然含有它,你必須認得出那是無害的,而不是多驅動源錯誤。実務的な助言は、AND を自分で書くことである。ワイヤード形式は何も節約せず、読み手を驚かせ、一つの名前への複数の連続代入は lint ルールや一部ツールのマルチドライバ検査に引っかかる。講義がこれに触れるのは、レガシーコード — とくにボードレベルや TTL 時代の記述から取り込まれたコード — にいまだ残っているからで、それがマルチドライバのエラーではなく無害であると見分けられる必要がある。
The word reg is the single worst-chosen keyword in Verilog. It does not mean register. It means "an object that can be the target of a procedural assignment", and whether it becomes a flip-flop, a latch or a plain wire is decided by the inference pass according to the rules covered in Lectures 5 and 6: it becomes a flip-flop if it must hold a value across a clock edge, a latch if a combinational process can finish without assigning it, and a wire if every read in every pass is preceded by a write in the same pass. Section 10 of this page will show a reg declared in the counting-ones example that produces no storage at all.reg 這個字是 Verilog 裡選得最糟的關鍵字。它不是指暫存器。它的意思是「可以當成程序式指定目標的物件」,而它究竟變成正反器、閂鎖或單純的 wire,是由推論階段依第 5、6 講講過的規則決定:若它必須跨越時脈邊界保存值就變成正反器;若組合程序可能在沒有指定它的情況下結束就變成閂鎖;若每一條路徑上的每一次讀取之前都有同一條路徑上的寫入,就只是一條 wire。本頁第 10 節會示範一個在「數 1 的個數」範例裡宣告的 reg,它完全不產生任何儲存元件。reg は Verilog でもっとも選択を誤ったキーワードである。これはレジスタを意味しない。「手続き代入の対象になりうるオブジェクト」という意味であり、それがフリップフロップになるかラッチになるか単なる wire になるかは、第 5 講と第 6 講で扱った規則に従って推論パスが決める。クロックエッジをまたいで値を保持しなければならないならフリップフロップ、組合せプロセスが代入せずに終わりうるならラッチ、あらゆるパスで読み出しの前に同じパス内の書き込みがあるなら wire である。本ページの第 10 節では、1 の個数を数える例で宣言された reg が記憶素子をまったく生まないことを示す。
An integer is a reg with two extra properties: it is thirty-two bits wide and it is signed. Both matter. The width matters because the inference pass builds whatever the elaborated expression width demands. If you writeinteger 是一種多了兩個性質的 reg:它是三十二位元寬,而且是有號的。兩者都很要緊。位寬要緊,是因為推論階段會照展開後的運算式位寬去建硬體。如果你寫integer は二つの性質を追加した reg である。32 ビット幅で、かつ符号付きである。どちらも重要である。幅が重要なのは、推論パスがエラボレーション後の式幅が要求するものをそのまま構築するからである。次のように書けば
integer k; always @(posedge clk) begin k = k + 1; q <= k[1:0]; end
then after elaboration there is a thirty-two-bit incrementer feeding thirty-two flip-flops, of which only two are read. The lecture's second bullet says the size "may be reduced in the optimisation process", and that is true here: the mapping pass performs constant and unused-bit propagation, discovers that bits 2 through 31 of k can never influence any output, and sweeps them away, leaving a two-bit counter. But the word is may, and the conditions under which it works are narrower than students expect. The sweep succeeds when the unused bits are provably unobservable. It fails the moment the wide value escapes — through a port, through a comparison against a wide constant, or into an expression whose width the tool must preserve — and then you really do get thirty-two flip-flops and a thirty-two-bit carry chain for a counter that needed two.展開之後就會有一個三十二位元的遞增器驅動三十二顆正反器,而其中只有兩顆被讀取。這一講第二個要點說,大小「在最佳化過程中可能被縮減」,這裡確實如此:對應階段執行常數與未使用位元的傳播,發現 k 的第 2 到 31 位永遠不可能影響任何輸出,就把它們掃掉,只剩一個兩位元計數器。但那個字是可能,而它生效的條件比學生以為的窄得多。只有當未使用的位元可被證明無法觀測時,掃除才會成功。一旦這個寬值逸出 — 經由一個埠、經由與一個寬常數的比較,或進入一個工具必須保留其位寬的運算式 — 掃除就失敗,於是一個只需要兩位元的計數器,你真的會拿到三十二顆正反器加一條三十二位元的進位鏈。エラボレーション後には 32 ビットのインクリメンタが 32 個のフリップフロップを駆動し、そのうち読まれるのは 2 個だけとなる。講義の二つ目の項目はサイズが「最適化の過程で削減されうる」と述べており、ここではそのとおりである。マッピングパスは定数伝播と未使用ビットの伝播を行い、k のビット 2〜31 がどの出力にも影響しえないと気づいて掃き出し、2 ビットカウンタを残す。しかし言葉は「されうる」であり、それが働く条件は学生が思うより狭い。掃き出しが成功するのは未使用ビットが観測不能だと証明できるときだけである。その広い値が逃げ出した瞬間 — ポートを通って、広い定数との比較を通って、あるいはツールが幅を保たねばならない式の中へ — 掃き出しは失敗し、2 ビットで済むカウンタに対して本当に 32 個のフリップフロップと 32 ビットのキャリーチェーンを得ることになる。
The fix is in the third bullet: declare things with the width you mean. reg [1:0] k; states the intent, is independent of tool cleverness, documents the design, and makes the wrap-around behaviour explicit rather than accidental. Reserve integer for loop indices in static loops, where it disappears at elaboration anyway and where its thirty-two-bit range conveniently removes any risk of the index wrapping before the loop terminates.解法在第三個要點:用你真正需要的位寬去宣告。reg [1:0] k; 明確表達意圖,不依賴工具聰不聰明,同時替設計留下文件,並把回繞行為變成明示而非偶然。把 integer 留給靜態迴圈的迴圈索引用 — 那裡它在展開時反正會消失,而它三十二位元的範圍剛好消除了索引在迴圈結束前回繞的風險。解決策は三つ目の項目にある。意図した幅で宣言することである。reg [1:0] k; は意図を明示し、ツールの賢さに依存せず、設計を文書化し、ラップアラウンドのふるまいを偶然ではなく明示的なものにする。integer は静的ループのループインデックスに限って使うとよい。そこではどのみちエラボレーション時に消えるし、32 ビットという範囲はループ終了前にインデックスがラップする危険を都合よく取り除いてくれる。
The same bullet applies to literals, and this is the more common bug. An unsized decimal literal such as 9 is, by IEEE 1364, a thirty-two-bit signed quantity. In most contexts Verilog's expression-width rules rescue you, because the width of a binary operation is the maximum of the widths of its operands and of the assignment target, so assign y = x + 9; with y and x four bits wide builds a four-bit adder. But three situations break that rescue, and each produces a real and surprising hardware cost.同一個要點也適用於文字常數,而這是更常見的錯誤。像 9 這樣未指定位寬的十進位常數,依 IEEE 1364 是一個三十二位元的有號量。在大多數情境下,Verilog 的運算式位寬規則會救你,因為二元運算的位寬是其運算元與指定目標位寬的最大值,所以 assign y = x + 9; 在 y 與 x 都是四位元時會建出一個四位元加法器。但有三種情況會讓這份救援失效,而每一種都造成真實且令人意外的硬體成本。同じ項目はリテラルにも当てはまり、こちらのほうが頻出するバグである。9 のようなサイズ指定のない 10 進リテラルは、IEEE 1364 では 32 ビットの符号付き量である。多くの文脈では Verilog の式幅規則が救ってくれる。二項演算の幅はオペランドと代入先の幅の最大値だからで、y と x が 4 ビットなら assign y = x + 9; は 4 ビット加算器を作る。しかしこの救済が崩れる状況が三つあり、どれも現実的で驚くようなハードウェアコストを生む。
The first is a self-determined context — a concatenation. {a, 9} is not {a, 4'd9}; it is {a, 32'd9}, a bus thirty-two bits longer than you wanted. The second is an unsized x or z literal: 'bx fills the target width, but 4'bx is 4'bxxxx while a bare 1'bx in a wide context zero-extends. The third, and the nastiest, is signedness. Unsized decimal literals are signed; sized literals are unsigned unless you write 's. Mixing them silently changes a whole expression's signedness, because in Verilog an expression is unsigned if any operand is unsigned, and the sign rule then governs how every operand is extended.第一種是自決定情境 — 串接。{a, 9} 不是 {a, 4'd9},而是 {a, 32'd9},一條比你想要的多了三十二位元的匯流排。第二種是未指定位寬的 x 或 z 常數:'bx 會填滿目標位寬,但 4'bx 等於 4'bxxxx,而在寬情境中單獨的 1'bx 會做零擴展。第三種也是最惡劣的一種,是有號性。未指定位寬的十進位常數是有號的;有指定位寬的常數除非你寫 's,否則是無號的。把它們混用會悄悄改變整個運算式的有號性,因為在 Verilog 裡只要任何一個運算元是無號的,整個運算式就是無號的,而這條符號規則接著決定每個運算元如何被擴展。第一は自己決定文脈 — 連結である。{a, 9} は {a, 4'd9} ではなく {a, 32'd9} であり、望んだより 32 ビット長いバスになる。第二はサイズ指定のない x や z のリテラルである。'bx は対象幅を埋めるが、4'bx は 4'bxxxx であり、広い文脈に置かれた裸の 1'bx はゼロ拡張される。第三はもっとも厄介な符号属性である。サイズ指定のない 10 進リテラルは符号付き、サイズ指定付きリテラルは 's を書かない限り符号なしである。これらを混ぜると式全体の符号属性が黙って変わる。Verilog ではどれか一つでもオペランドが符号なしなら式は符号なしとなり、その符号規則が各オペランドの拡張方法を支配するからである。
With reg signed [3:0] a; reg [3:0] b; the expression a + b is unsigned, because b is unsigned, so a is zero-extended rather than sign-extended and a negative a becomes a large positive number. The synthesiser builds exactly that, so RTL and gate simulation agree — and both are wrong relative to your intent. Keep every operand of an arithmetic expression consistently signed or consistently unsigned, and use $signed()/$unsigned() explicitly when you must mix.在 reg signed [3:0] a; reg [3:0] b; 之下,運算式 a + b 是無號的,因為 b 是無號的,於是 a 被零擴展而非符號擴展,負的 a 就變成一個很大的正數。合成器會原原本本照這樣建,所以 RTL 與閘級模擬會一致 — 而兩者相對於你的意圖都是錯的。請讓算術運算式的每個運算元一致為有號或一致為無號,必須混用時就明確使用 $signed()/$unsigned()。reg signed [3:0] a; reg [3:0] b; のもとで、式 a + b は符号なしである。b が符号なしなので a は符号拡張ではなくゼロ拡張され、負の a は大きな正の数になる。合成器はまさにそのとおりに作るので RTL とゲートシミュレーションは一致する — そして両方とも意図に対して誤っている。算術式のオペランドはすべて符号付きかすべて符号なしで揃え、混ぜざるをえないときは $signed()/$unsigned() を明示的に使うこと。
This is not on the slide, but it causes more silent datapath bugs in student projects than everything that is on the slide combined.這一點並不在投影片上,但它在學生專題裡造成的靜默資料路徑錯誤,比投影片上所有項目加起來還多。これはスライドに載っていないが、学生のプロジェクトで黙って生じるデータパスのバグは、スライドに載っている項目すべてを合わせたよりも、これが原因であることが多い。
The lecture states flatly that real, time and realtime are not supported, and the reason is the one given in section 1.2. A real is a double-precision floating-point number in the simulator. Realising it would require the tool to invent a floating-point format, a rounding mode, denormal handling and exception semantics that the source text never specified, and then to instantiate an FPU costing tens of thousands of gates for what the designer probably thought was a convenience. A time or realtime variable holds simulation time, which exists only inside the simulator's event queue; there is no corresponding physical quantity in the finished chip.這一講直截了當地指出 real、time 與 realtime 不被支援,理由就是 1.2 節給的那個。real 在模擬器裡是一個雙精度浮點數。要把它實現出來,工具必須憑空發明原始碼從未指定的浮點格式、捨入模式、非正規數處理與例外語意,然後為設計者大概只是圖個方便的東西,實例化一個要花上數萬個閘的 FPU。time 或 realtime 變數保存的是模擬時間,那只存在於模擬器的事件佇列裡;完成的晶片上沒有對應的物理量。講義は real、time、realtime が非対応だと端的に述べており、その理由は 1.2 節で示したものである。real はシミュレータ内の倍精度浮動小数点数である。これを実現するには、ソーステキストが一度も指定していない浮動小数点形式、丸めモード、非正規数の扱い、例外意味論をツールが発明したうえで、設計者がおそらく便利さのつもりで書いたものに対して数万ゲート規模の FPU をインスタンス化しなければならない。time や realtime 変数はシミュレーション時刻を保持するが、それはシミュレータのイベントキューの中にしか存在せず、完成したチップに対応する物理量はない。
These types remain perfectly legal and extremely useful outside the synthesisable part of the design. Test benches use real for computing expected analogue values, for signal-to-noise ratio checks on a communication data path, and for timing measurements; they use time with $time and $realtime for latency instrumentation. Lecture 2 of this course covers that usage. The discipline is simply to keep them on the test-bench side of the boundary and out of any module that will be handed to the synthesiser.這些型態在設計的可合成部分之外仍然完全合法,而且極為有用。測試平台用 real 計算期望的類比值、對通訊資料路徑做訊號雜訊比檢查,以及做時序量測;也用 time 搭配 $time 與 $realtime 做延遲量測。本課程第 2 講涵蓋這類用法。紀律很簡單:把它們留在測試平台這一側,不要讓它們出現在任何要交給合成器的模組裡。これらの型は設計の合成可能部分の外側では完全に合法で、きわめて有用である。テストベンチは期待されるアナログ値の計算、通信データパスの信号対雑音比チェック、タイミング測定に real を使い、レイテンシ計測には $time/$realtime とともに time を使う。本科目の第 2 講がその用法を扱う。守るべき規律は単純で、それらを境界のテストベンチ側にとどめ、合成器に渡すモジュールには入れないことである。
The same reasoning extends to several things the slide does not list but that belong in the same category: system tasks such as $display, $monitor and $finish (ignored or rejected, with the standard exception of $signed, $unsigned and the $clog2-style elaboration-time functions, which are compile-time computations rather than runtime behaviour); file I/O; force and release; named events; and initial blocks, which describe power-up behaviour that an ASIC flip-flop simply does not have. An FPGA flow is more permissive about initial because the bitstream can preload the flip-flops, which is one of the few genuine differences between the ASIC and FPGA synthesisable subsets.同樣的道理也延伸到幾件投影片沒列、但屬於同一類的東西:系統任務如 $display、$monitor 與 $finish(被忽略或拒絕,標準的例外是 $signed、$unsigned 以及 $clog2 這類在展開期求值的函式,它們是編譯期計算而非執行期行為);檔案 I/O;force 與 release;具名事件;以及 initial 區塊 — 它描述的是 ASIC 正反器根本沒有的上電行為。FPGA 流程對 initial 較為寬容,因為位元流可以預載正反器,這是 ASIC 與 FPGA 可合成子集之間少數幾個真正的差異之一。同じ理屈は、スライドに列挙されていないが同じカテゴリに属するいくつかのものにも及ぶ。$display、$monitor、$finish といったシステムタスク(無視されるか拒否される。標準的な例外は $signed、$unsigned および $clog2 系のエラボレーション時関数で、これらは実行時のふるまいではなくコンパイル時の計算である)、ファイル I/O、force と release、名前付きイベント、そして ASIC のフリップフロップには存在しない電源投入時のふるまいを記述する initial ブロックである。FPGA フローは initial により寛容である。ビットストリームがフリップフロップをプリロードできるからで、これは ASIC と FPGA の合成可能サブセットの数少ない本質的な差の一つである。
Problem: For each line, state the width of the right-hand expression after elaboration and the hardware that results. All vectors are four bits unless stated.問題:對每一行,說出展開後右手邊運算式的位寬,以及產生的硬體。除非另外說明,所有向量都是四位元。問題:各行について、エラボレーション後の右辺式の幅と、結果として生じるハードウェアを述べよ。断りのない限りすべてのベクタは 4 ビットとする。
(a) wire [3:0] y; assign y = a + 1;
(b) wire [35:0] z; assign z = {a, 32};
(c) integer n; always @(posedge clk) n = n + 1; assign flag = n[3];
(d) wire [4:0] s; assign s = a + b;
(e) reg signed [3:0] p; wire [3:0] q; assign r = (p < q);
(a) Four bits. The maximum of the operand widths (4 and 32) and the target width (4) would be 32, but the assignment target truncates, and every tool recognises the pattern: the result is a four-bit incrementer, about 4 GE. Nothing is wasted, though writing 4'd1 states the intent.(a) 四位元。運算元位寬(4 與 32)與目標位寬(4)的最大值本來會是 32,但指定目標會截斷,而且每個工具都認得這個樣式:結果是一個四位元遞增器,約 4 GE。什麼都沒浪費,不過寫成 4'd1 能把意圖講清楚。(a) 4 ビット。オペランド幅(4 と 32)と代入先幅(4)の最大値は本来 32 になるが、代入先が切り詰め、どのツールもこのパターンを認識する。結果は 4 ビットのインクリメンタで約 4 GE である。無駄はないが、4'd1 と書けば意図が明確になる。
(b) Thirty-six bits, and the concatenation is the trap. Inside { } every operand is self-determined, so 32 is a thirty-two-bit literal and the concatenation is 4 + 32 = 36 bits wide. You almost certainly wanted {a, 6'd32} or similar. The result is pure wiring either way, but the bus is the wrong width and the connection downstream will be silently misaligned.(b) 三十六位元,而串接就是陷阱。在 { } 裡每個運算元都是自決定的,所以 32 是一個三十二位元常數,整個串接寬 4 + 32 = 36 位元。你幾乎可以確定想寫的是 {a, 6'd32} 之類的東西。兩種寫法結果都只是純接線,但匯流排寬度錯了,下游的連接會悄悄錯位。(b) 36 ビット。連結が罠である。{ } の中ではすべてのオペランドが自己決定なので、32 は 32 ビットリテラルとなり、連結の幅は 4 + 32 = 36 ビットになる。ほぼ確実に書きたかったのは {a, 6'd32} のようなものである。どちらにせよ結果は純粋な配線だが、バス幅が誤っており、下流の接続は黙ってずれる。
(c) Thirty-two bits at elaboration. Whether it shrinks depends on observability: here only n[3] is read, so bits 4–31 are unobservable and a good optimiser sweeps them, leaving a four-bit counter. If you also wrote assign count_out = n; to a thirty-two-bit port, nothing could be swept and you would get thirty-two flip-flops plus a thirty-two-bit carry chain, roughly 190 GE instead of 25.(c) 展開時是三十二位元。會不會縮小取決於可觀測性:這裡只讀取 n[3],所以第 4 到 31 位不可觀測,好的最佳化器會把它們掃掉,剩下一個四位元計數器。但如果你還寫了 assign count_out = n; 接到一個三十二位元的埠,就什麼都掃不掉,你會得到三十二顆正反器加一條三十二位元進位鏈,大約 190 GE 而不是 25。(c) エラボレーション時は 32 ビット。縮むかどうかは観測可能性に依存する。ここでは n[3] しか読まれないのでビット 4〜31 は観測不能であり、よい最適化器はこれらを掃き出して 4 ビットカウンタを残す。もし assign count_out = n; と書いて 32 ビットのポートに出していたら何も掃き出せず、25 ではなく約 190 GE 相当の 32 個のフリップフロップと 32 ビットのキャリーチェーンを得ることになる。
(d) Five bits. The target is five bits wide, so both operands are zero-extended to five and a five-bit adder is built — which is exactly what you want if you need the carry out. Had the target been four bits, the carry would have been discarded.(d) 五位元。目標是五位元寬,所以兩個運算元都零擴展到五位元,建出一個五位元加法器 — 如果你需要進位輸出,這正是你要的。若目標是四位元,進位就會被丟掉。(d) 5 ビット。代入先が 5 ビット幅なので両オペランドとも 5 ビットにゼロ拡張され、5 ビット加算器が作られる。キャリー出力が必要ならまさにこれが望みどおりである。代入先が 4 ビットならキャリーは捨てられていた。
(e) Unsigned, because q is unsigned, so p is zero-extended. A four-bit unsigned comparator is built, and p = 4'b1111 compares as 15 rather than as −1. Write $signed(q) or declare q signed.(e) 無號,因為 q 是無號的,所以 p 被零擴展。建出的是一個四位元無號比較器,而 p = 4'b1111 會被當成 15 而非 −1 來比較。請寫 $signed(q),或把 q 宣告為有號。(e) 符号なし。q が符号なしなので p はゼロ拡張される。作られるのは 4 ビットの符号なし比較器で、p = 4'b1111 は −1 ではなく 15 として比較される。$signed(q) と書くか、q を符号付きで宣言すること。
This is the part of Lecture 7 that most reliably costs marks, and it is also the part where the slide's bullet list is most compressed. The slide says: the only allowable uses of x are in casex, in casez and in the defaults of conditionals; the only allowable use of z is in constructs implying a tri-state output. Underneath those two sentences sit a semantic distinction that the Verilog language itself blurs, a classic simulation-divergence trap, and a genuinely useful optimisation lever. Take them in order.這是第 7 講最常讓人失分的部分,也是投影片條列壓縮得最嚴重的部分。投影片說:x 唯一允許的用法是在 casex、casez 以及條件式的預設分支中;z 唯一允許的用法是在隱含三態輸出的構件中。這兩句話底下藏著一個連 Verilog 語言本身都模糊掉的語意區別、一個經典的模擬分歧陷阱,以及一個真正有用的最佳化槓桿。依序來看。ここは第 7 講でもっとも確実に点を失う部分であり、スライドの箇条書きがもっとも圧縮されている部分でもある。スライドはこう述べる。x が許されるのは casex、casez、および条件文のデフォルトの中だけ。z が許されるのはトライステート出力を暗示する構文の中だけ、と。この 2 文の下には、Verilog 言語自身がぼかしている意味論的区別、古典的なシミュレーション乖離の罠、そして本当に有用な最適化の梃子が潜んでいる。順に見ていく。
Verilog's four values are 0, 1, x and z, and only three of them describe anything physical. A 0 is a node held near ground by a conducting pull-down stack. A 1 is a node held near the supply by a conducting pull-up stack. A z is a node held by nothing at all, because every driver attached to it is off — the output of a disabled tri-state buffer, or a bus nobody is currently driving. Those three are states a voltmeter could distinguish.Verilog 的四個值是 0、1、x 與 z,其中只有三個描述實際存在的物理狀態。0 是被導通的下拉堆疊拉到接近地的節點。1 是被導通的上拉堆疊拉到接近電源的節點。z 是完全沒有東西在保持的節點,因為接到它的每個驅動源都關閉了 — 例如一顆被禁用的三態緩衝器的輸出,或一條目前沒人驅動的匯流排。這三個是電壓表分辨得出來的狀態。Verilog の 4 値は 0、1、x、z であり、物理的な何かを記述するのはそのうち 3 つだけである。0 は導通したプルダウンスタックによってグラウンド付近に保たれたノード。1 は導通したプルアップスタックによって電源付近に保たれたノード。z は何にも保持されていないノードで、接続されたドライバがすべてオフだからである — 無効化されたトライステートバッファの出力や、現在誰も駆動していないバスがそれに当たる。この 3 つは電圧計で区別できる状態である。
An x is not a state. It is the simulator's way of saying "I do not know what this is". It appears when a flip-flop has not been reset and so has no defined initial content; when two drivers fight and the resolved value is genuinely indeterminate; when a timing check fails; or when an unknown propagates through a gate. A real node, at any instant, is at some voltage. It is never "unknown" — our model is uncertain, the silicon is not. This single observation is the whole of the x-rules.x 不是一個狀態。它是模擬器說「我不知道這是什麼」的方式。它出現在正反器尚未重置因而沒有定義好的初始內容時;兩個驅動源相爭、解析後的值真的不確定時;時序檢查失敗時;或未知值穿過一個閘傳播時。真實的節點在任何瞬間都處於某個電壓,它永遠不會是「未知」 — 不確定的是我們的模型,不是矽。這一個觀察就是 x 規則的全部。x は状態ではない。これは「これが何か分からない」とシミュレータが言うための手段である。フリップフロップがリセットされておらず初期値が定義されていないとき、二つのドライバが競合して解決値が本当に不定のとき、タイミングチェックが失敗したとき、あるいは未知値がゲートを通って伝播したときに現れる。実在のノードはどの瞬間にも何らかの電圧にある。決して「未知」ではない — 不確かなのは我々のモデルであって、シリコンではない。この一つの観察が x に関する規則のすべてである。
Because an x is ignorance rather than a value, it can only ever appear on the left of the synthesis boundary — in what you tell the tool — and never on the right, in the netlist. So the language is allowed to let you write x in a place where it means "I do not care what you build here", and is not allowed to let you test for x, because there is nothing in the finished circuit to test.因為 x 是無知而不是一個值,它只可能出現在合成邊界的左側 — 也就是你告訴工具的內容裡 — 絕不會出現在右側的 netlist 裡。所以語言允許你在某個位置寫下 x,意思是「我不在乎你在這裡建什麼」;但不允許你測試 x,因為完成的電路裡根本沒有東西可測。x は値ではなく無知であるため、合成境界の左側 — ツールに与える記述の中 — にしか現れえず、右側のネットリストには決して現れない。したがって言語は、「ここに何を作っても構わない」という意味で x を書くことは許すが、x を検査することは許さない。完成した回路に検査すべきものが存在しないからである。
When you assign x to a variable in the default branch of a conditional, you are making a promise to the tool: "this combination of inputs will never occur, or if it does I do not care what comes out". The tool takes you at your word and treats the corresponding entries of the truth table as don't cares. In classical two-level minimisation these are the $d$ terms of a Karnaugh map, and they are free to be covered or left uncovered, whichever makes the prime implicants larger. In modern multi-level optimisation they become external don't-care sets that the engine uses when resubstituting and simplifying nodes.當你在條件式的預設分支把 x 指定給一個變數,你是在對工具做出承諾:「這組輸入組合永遠不會發生,就算發生了我也不在乎輸出是什麼」。工具會照字面採信,把真值表中對應的項目視為 don't care。在古典的二階最小化裡,這些就是卡諾圖上的 $d$ 項,可以自由決定要不要被涵蓋,哪一種讓質蘊涵項更大就選哪一種。在現代多層最佳化裡,它們變成引擎在重新代換與化簡節點時會用到的外部 don't-care 集合。条件文のデフォルト分岐で変数に x を代入するとき、あなたはツールに約束をしている。「この入力の組合せは決して起こらない、あるいは起きても出力が何であっても構わない」と。ツールはその言葉どおりに受け取り、真理値表の対応する項目をドントケアとして扱う。古典的な 2 段階最小化ではこれがカルノー図の $d$ 項であり、主項が大きくなるほうを選んで覆っても覆わなくてもよい。現代の多段最適化では、ノードの再代入と簡単化の際にエンジンが用いる外部ドントケア集合になる。
The gain can be dramatic. Consider a three-bit opcode with only four defined operations:效果可能非常顯著。考慮一個只定義了四種運算的三位元 opcode:効果は劇的になりうる。定義された演算が 4 つしかない 3 ビットのオペコードを考える。
always @* begin
case (op)
3'b111: y = a & b;
3'b011: y = a | b;
3'b001: y = a ^ b;
default: y = 2'b00; // a real value
endcase
end
Five of eight codes must now produce exactly 00. The logic needs all three select bits and a full three-input decode.八個碼中有五個現在必須產生恰好 00。邏輯需要全部三個選擇位元,以及一個完整的三輸入解碼。8 個のコードのうち 5 個がちょうど 00 を出さねばならない。論理には選択ビット 3 本すべてと完全な 3 入力デコードが必要になる。
always @* begin
case (op)
3'b111: y = a & b;
3'b011: y = a | b;
3'b001: y = a ^ b;
default: y = 2'bx; // don't care
endcase
end
Five of eight codes are free. Because every listed code has op[0] = 1, the optimiser can ignore op[0] entirely and decode on two bits.八個碼中有五個是自由的。因為列出的每個碼都有 op[0] = 1,最佳化器可以完全忽略 op[0],只用兩個位元解碼。8 個のコードのうち 5 個が自由である。列挙されたコードはすべて op[0] = 1 なので、最適化器は op[0] を完全に無視し、2 ビットでデコードできる。
The lecture's ALU example on slide 19 is exactly this situation with the bits permuted, and the slide's own parenthetical remark — "note that unused input s2 is retained" — is the observable consequence. All three listed cases have the same value in one select position, the don't cares let the optimiser drop that select bit from the logic, and the port stays behind as a stub for the reasons given in section 2.1.這一講第 19 張投影片的 ALU 範例,就是位元排列不同的同一個情況,而投影片自己那句括號裡的註記 —「注意未使用的輸入 s2 被保留了下來」— 就是可觀測的後果。三個列出的 case 在某個選擇位置上的值相同,don't care 讓最佳化器把那個選擇位元從邏輯中移除,而那個埠因為 2.1 節講的理由,以一個樁的形式留了下來。講義のスライド 19 の ALU の例は、ビットの並びが違うだけで、まさにこの状況である。そしてスライド自身の括弧書き —「未使用の入力 s2 が残っていることに注意」— がその観測可能な帰結である。列挙された 3 つのケースはある選択位置で同じ値を持つため、ドントケアが最適化器にその選択ビットを論理から外させ、ポートは 2.1 節で述べた理由によりスタブとして残るのである。
Both versions above are correct designs. The difference is a trade. The don't-care version is smaller and faster; the fully specified version is deterministic, which means it behaves identically in RTL simulation, in gate simulation and in silicon even when an illegal opcode somehow reaches it. In a communication IC where an opcode might be corrupted by a soft error or by a mis-synchronised control bus, the determinism is often worth the gates. The usual compromise is to specify a real default in control logic that can see illegal inputs, and to use x only in datapath decoders whose inputs are provably constrained.上面兩個版本都是正確的設計。差別是一項取捨。don't-care 版本比較小、比較快;完整指定的版本則是確定性的,意思是即使某個非法 opcode 不知怎地跑了進來,它在 RTL 模擬、閘級模擬與矽晶片上的行為都完全一致。在通訊 IC 裡,opcode 可能被軟錯誤或未同步的控制匯流排破壞,這份確定性往往值得那些閘。常見的折衷是:在看得到非法輸入的控制邏輯裡指定真正的 default,而只在輸入可被證明受限的資料路徑解碼器裡使用 x。上記のどちらの版も正しい設計である。違いはトレードオフにある。ドントケア版は小さく速い。完全指定版は決定的であり、不正なオペコードが何らかの形で到達しても RTL シミュレーション、ゲートシミュレーション、シリコンで同一にふるまう。ソフトエラーや同期の崩れた制御バスによってオペコードが壊れうる通信 IC では、この決定性はしばしばゲート数に見合う。通常の妥協点は、不正入力を見うる制御論理では本物のデフォルトを指定し、入力が制約されていると証明できるデータパスのデコーダにだけ x を使うことである。
Writing default: y = 2'bx; makes RTL simulation produce a literal x on y for an illegal opcode, which is a splendid debugging aid — an unexpected x propagates and is loud. After synthesis that same stimulus produces whatever 0/1 pattern the optimiser happened to choose, so gate-level simulation shows a clean value and the bug goes quiet. This is a known and accepted RTL-to-gate mismatch for don't cares, and it is the reason formal equivalence checkers have a specific "don't care" handling mode. Do not report it as a tool bug.寫下 default: y = 2'bx; 會讓 RTL 模擬在遇到非法 opcode 時在 y 上產生一個貨真價實的 x,這是極好的除錯輔助 — 非預期的 x 會往外傳播,而且很吵。合成之後,同樣的激勵會產生最佳化器剛好挑中的某組 0/1 樣式,於是閘級模擬顯示的是乾淨的值,錯誤就安靜了。這是 don't care 造成的已知且被接受的 RTL 對閘級不匹配,也是形式等價檢查工具會有專門「don't care」處理模式的原因。不要把它當成工具的錯誤去回報。default: y = 2'bx; と書くと、不正なオペコードに対して RTL シミュレーションは y に文字どおりの x を出す。これはすばらしいデバッグ支援である。予期しない x は伝播し、よく目立つ。合成後は同じ刺激に対して最適化器がたまたま選んだ 0/1 のパターンが出るので、ゲートレベルシミュレーションはきれいな値を示し、バグは静かになる。これはドントケアによる既知かつ許容された RTL とゲートの不一致であり、形式等価性検査ツールに専用の「ドントケア」処理モードがある理由でもある。ツールのバグとして報告してはならない。
if (a == 1'bx) is meaningless3.3 為什麼 if (a == 1'bx) 毫無意義3.3 if (a == 1'bx) が無意味な理由Students write this line every semester, usually trying to catch an uninitialised value. It is wrong twice over, and the two failures are different, so it is worth separating them carefully.學生每個學期都會寫下這一行,通常是想抓出未初始化的值。它錯了兩次,而且兩種錯法不同,所以值得仔細分開來談。学生は毎学期この行を書く。たいていは未初期化の値を捕まえようとしてのことである。これは二重に誤っており、二つの失敗は性質が異なるので、丁寧に分けて論じる価値がある。
The simulation failure. In IEEE 1364, the equality operators == and != are four-state comparisons that propagate unknowns. If either operand contains an x or a z in any bit position, the result is not 0 and not 1; it is 1'bx. And an if statement treats x exactly as it treats 0 — the condition is false unless it is unambiguously true. Therefore if (a == 1'bx) is false when a is 0, false when a is 1, and false when a is x, because then the comparison returns x which the if rejects. The branch is dead code in every possible case. Worse, it is dead code that looks alive, so the designer believes the check is running.模擬上的失敗。在 IEEE 1364 中,相等運算子 == 與 != 是會傳播未知值的四值比較。只要任一運算元的任一位元含有 x 或 z,結果就既不是 0 也不是 1,而是 1'bx。而 if 敘述對待 x 的方式和對待 0 完全一樣 — 除非條件明確為真,否則就是假。因此 if (a == 1'bx) 在 a 為 0 時是假、a 為 1 時是假、a 為 x 時也是假,因為那時比較回傳 x,而 if 把它拒絕。這個分支在所有可能情況下都是死碼。更糟的是,它是看起來活著的死碼,於是設計者以為這道檢查正在運作。シミュレーション上の失敗。IEEE 1364 では、等価演算子 == と != は未知値を伝播する4 値比較である。どちらかのオペランドのどこか 1 ビットでも x または z を含めば、結果は 0 でも 1 でもなく 1'bx になる。そして if 文は x を 0 とまったく同じに扱う — 明確に真でない限り条件は偽である。したがって if (a == 1'bx) は a が 0 のとき偽、a が 1 のとき偽、a が x のときも偽である。そのとき比較は x を返し、if がそれを拒否するからである。この分岐はあらゆる場合において死んだコードである。さらに悪いことに、生きているように見える死んだコードなので、設計者は検査が働いていると信じてしまう。
if (a == 1'bx) flag = 1; // NEVER taken, for any value of a
else flag = 0;
The synthesis failure. Suppose you repair the simulation problem by using the case-equality operator ===, which does compare x and z literally and returns a definite 0 or 1:合成上的失敗。假設你用 case 相等運算子 === 修好了模擬問題 — 它確實會逐字比較 x 與 z,並回傳明確的 0 或 1:合成上の失敗。ケース等価演算子 === を使ってシミュレーションの問題を直したとしよう。これは x と z を文字どおり比較し、明確な 0 か 1 を返す。
if (a === 1'bx) flag = 1; // works in simulation, has no hardware
else flag = 0;
Now the line behaves as intended in RTL simulation but cannot be built. To build it the tool would have to emit a cell that detects "this node is in an unknown state", and no such cell exists, because as section 3.1 argued the state does not exist. Most synthesisers reject === and !== outright with an "operator not supported for synthesis" error. A few accept them and silently constant-fold the comparison against an x literal to the constant 0, which leaves you with flag = 0 hard-wired and a design that behaves differently before and after synthesis. Either way the check you wanted is not in the chip.現在這一行在 RTL 模擬裡如你所願地運作,卻建不出來。要建出它,工具必須產生一顆能偵測「這個節點處於未知狀態」的元件,而這樣的元件並不存在,因為如 3.1 節所論證的,那個狀態並不存在。大多數合成器會直接以「此運算子不支援合成」的錯誤拒絕 === 與 !==。少數工具會接受它們,並悄悄把與 x 常數的比較常數摺疊成常數 0,於是你得到硬接線的 flag = 0,以及一個合成前後行為不同的設計。不論哪一種,你想要的檢查都不在晶片裡。この行は RTL シミュレーションでは意図どおり動くが、作ることはできない。作るにはツールが「このノードは未知状態にある」と検出するセルを出力せねばならないが、3.1 節で論じたようにその状態が存在しない以上、そんなセルも存在しない。ほとんどの合成器は「この演算子は合成に対応していない」というエラーで === と !== をきっぱり拒否する。一部は受け付けたうえで、x リテラルとの比較を黙って定数 0 に畳み込む。その結果 flag = 0 が固定配線され、合成前後でふるまいの異なる設計が残る。いずれにせよ、望んだ検査はチップの中にない。
Slide 10 lists === and !== alongside == and != under "identity operators … implemented with simple combinational logics". That is a simplification. === and !== are not part of the synthesisable subset. They may be accepted when the tool can prove neither operand can ever be x or z, in which case they are identical to ==/!= and nothing is gained; otherwise they are an error. Treat them as test-bench-only operators, where they are genuinely valuable for comparing against expected values that contain x.第 10 張投影片把 === 與 !== 和 ==、!= 並列在「identity operators … implemented with simple combinational logics」底下。那是一個簡化。=== 與 !== 不屬於可合成子集。只有當工具能證明兩個運算元都不可能是 x 或 z 時才可能被接受,而那種情況下它們與 ==/!= 完全相同,什麼也沒賺到;否則就是錯誤。請把它們視為只能用在測試平台的運算子 — 在那裡,拿來比對含有 x 的期望值確實很有價值。スライド 10 は === と !== を ==、!= と並べて「identity operators … implemented with simple combinational logics」の下に挙げている。これは単純化である。=== と !== は合成可能サブセットに含まれない。どちらのオペランドも x や z になりえないとツールが証明できるときにだけ受理されうるが、その場合は ==/!= と同一であり、何も得られない。そうでなければエラーである。これらはテストベンチ専用の演算子と考えること。そこでは x を含む期待値との比較に本当に有用である。
If what you actually want is to detect an uninitialised or corrupted value in the real chip, you must encode the condition in real bits: add a valid flag that reset clears, use a redundant encoding such as one-hot with an explicit "no state asserted" detector, or add parity or ECC. There is no way around this. Unknown-ness is a property of your model, and only real bits survive synthesis.如果你真正想做的是在實際晶片裡偵測未初始化或損毀的值,你必須把這個條件編碼成真實的位元:加一個重置時清除的 valid 旗標、改用像是 one-hot 這種冗餘編碼並配上明確的「沒有任何狀態被設定」偵測器,或是加上同位元或 ECC。沒有別的辦法。未知性是你的模型的性質,而只有真實的位元能通過合成存活下來。実際のチップで未初期化や破損した値を検出したいのであれば、その条件を実在のビットで符号化しなければならない。リセットでクリアされる valid フラグを追加する、ワンホットのような冗長符号化と「どの状態も立っていない」ことを検出する回路を使う、あるいはパリティや ECC を加える。これを回避する方法はない。未知であることはあなたのモデルの性質であり、合成を生き延びるのは実在のビットだけである。
An ordinary case statement compares the case expression against each case item using the four-state identity rule — bit for bit, with x matching only x and z matching only z. casez and casex change that comparison by introducing a mask.一般的 case 敘述會用四值同一性規則,把 case 運算式逐位元與每個 case item 比較 — x 只和 x 相符,z 只和 z 相符。casez 與 casex 則引入一個遮罩來改變這個比較。通常の case 文は、ケース式を各ケースアイテムと 4 値同一性規則で比較する。ビットごとに比較し、x は x とだけ、z は z とだけ一致する。casez と casex はマスクを導入してこの比較を変える。
casez treats a z value as a don't-care position. Since the literal ? in Verilog is simply another way of writing z, this is what lets you write 4'b1??0. Crucially, the don't care applies whether the z appears in the case item or in the case expression.casez 把 z 值視為 don't-care 位置。由於 Verilog 裡的 ? 常數不過是 z 的另一種寫法,這就是你能寫 4'b1??0 的原因。關鍵在於,不論 z 出現在 case item或 case 運算式,這個 don't care 都成立。casez は z の値をドントケア位置として扱う。Verilog のリテラル ? は z の別表記にすぎないので、これにより 4'b1??0 と書ける。重要なのは、z がケースアイテム側に現れてもケース式側に現れても、このドントケアが適用されることである。casex treats both x and z as don't-care positions, again on either side of the comparison.casex 把 x 與 z 都視為 don't-care 位置,同樣不分比較的哪一側。casex は x と z の両方をドントケア位置として扱う。これも比較のどちら側でも同様である。The right mental model is not "the tool ignores some bits" but "the comparison is performed only on the positions where both sides are 0 or 1". Formally, for a case item $I$ and a case expression $E$ of $n$ bits, define a per-bit mask $m_i$ that is 0 where either $E_i$ or $I_i$ is a wildcard and 1 elsewhere. The item matches when正確的心智模型不是「工具忽略某些位元」,而是「比較只在兩側都是 0 或 1 的位置上進行」。形式上,對一個 $n$ 位元的 case item $I$ 與 case 運算式 $E$,定義逐位元遮罩 $m_i$:當 $E_i$ 或 $I_i$ 任一為萬用字元時為 0,其餘為 1。該 item 相符的條件是正しいメンタルモデルは「ツールが一部のビットを無視する」ではなく「比較は両側がともに 0 か 1 である位置でのみ行われる」である。形式的には、$n$ ビットのケースアイテム $I$ とケース式 $E$ に対し、$E_i$ か $I_i$ のいずれかがワイルドカードであれば 0、それ以外では 1 となるビットごとのマスク $m_i$ を定義する。アイテムが一致するのは次のときである。
In hardware the mask is almost always constant, because the wildcards sit in the literal case items and the case expression is a real signal that can only be 0 or 1. A constant mask costs nothing: the bit positions that are masked out simply have no gate attached to them. So a casez item such as 4'b1??0 becomes a two-input comparison on bits 3 and 0 only — one AND gate with one inverted input, about 1 GE — instead of the four-bit comparison an ordinary case item would need. That is the entire hardware meaning of a wildcard: fewer wires reach the comparator.在硬體裡這個遮罩幾乎永遠是常數,因為萬用字元落在字面的 case item 裡,而 case 運算式是只可能為 0 或 1 的真實訊號。常數遮罩不花任何成本:被遮掉的位元位置根本沒有閘接上去。所以像 4'b1??0 這樣的 casez item 只變成對第 3 位與第 0 位的兩輸入比較 — 一顆帶一個反相輸入的 AND 閘,約 1 GE — 而不是一般 case item 所需的四位元比較。這就是萬用字元在硬體上的全部意義:到達比較器的線變少了。ハードウェアではこのマスクはほぼ常に定数である。ワイルドカードはリテラルのケースアイテム側にあり、ケース式は 0 か 1 しか取りえない実在の信号だからである。定数マスクはコストゼロで、マスクされたビット位置にはそもそもゲートが接続されない。したがって 4'b1??0 のような casez アイテムは、通常の case アイテムが必要とする 4 ビット比較ではなく、ビット 3 とビット 0 だけの 2 入力比較 — 入力の一方を反転した AND ゲート 1 個、約 1 GE — になる。これがワイルドカードのハードウェア上の意味のすべてである。比較器に届く配線が減るのである。
One further point that the slide does not make and that costs marks: casez and casex items are still evaluated in order. Because wildcards make overlapping items easy to write, a wildcard case statement is almost always a priority encoder, not a parallel multiplexer. The canonical interrupt arbiter makes this obvious:還有一點投影片沒提、卻會讓人失分:casez 與 casex 的 item 仍然是依順序求值的。由於萬用字元讓重疊的 item 很容易寫出來,帶萬用字元的 case 敘述幾乎永遠是一個優先編碼器,而不是平行多工器。典型的中斷仲裁器讓這點一目了然:スライドが述べておらず、しかも失点につながる点がもう一つある。casez と casex のアイテムは依然として順番に評価される。ワイルドカードがあると重なり合うアイテムを書きやすいため、ワイルドカード付きの case 文はほぼ常に並列マルチプレクサではなくプライオリティエンコーダになる。典型的な割り込みアービタを見れば明らかである。
casez (req) // req is 4 bits, one bit per requester 4'b1???: grant = 2'd3; // highest priority 4'b01??: grant = 2'd2; 4'b001?: grant = 2'd1; 4'b0001: grant = 2'd0; default: grant = 2'd0; endcase
Here req = 4'b1100 matches the first item and also the second, and the first wins. The hardware is a chain of priority logic whose depth grows linearly with the number of items: roughly $k-1$ levels for $k$ items, or about $2k$ GE for a small $k$, compared with the $\log_2 k$ depth of a balanced multiplexer. For a four-way arbiter that is nothing; for a sixty-four-entry wildcard case it is a very long path, and you should instead build an explicit logarithmic priority encoder. Note also that the items above were deliberately written so that they are also mutually exclusive given the leading zeros — had I written 4'b??1? style items the priority behaviour would be much harder to read.這裡 req = 4'b1100 同時符合第一個與第二個 item,而第一個勝出。硬體是一串優先權邏輯,其深度隨 item 數線性成長:$k$ 個 item 大約 $k-1$ 層,小的 $k$ 約 $2k$ GE;相較之下平衡多工器的深度是 $\log_2 k$。對四路仲裁器來說這不算什麼;但對六十四項的萬用字元 case 來說就是一條很長的路徑,這時你應該改建一個明確的對數式優先編碼器。另外請注意,上面的 item 是刻意寫成在既有前導零的前提下彼此互斥的 — 如果我寫成 4'b??1? 這類 item,優先權行為會難讀得多。ここで req = 4'b1100 は最初のアイテムにも 2 番目のアイテムにも一致し、最初が勝つ。ハードウェアはアイテム数とともに深さが線形に増える優先順位論理の連鎖になる。$k$ 個のアイテムでおよそ $k-1$ 段、小さな $k$ なら約 $2k$ GE であり、平衡したマルチプレクサの深さ $\log_2 k$ と対照的である。4 方向のアービタなら問題にならないが、64 エントリのワイルドカード case では非常に長いパスになるので、代わりに明示的な対数段数のプライオリティエンコーダを作るべきである。なお上記のアイテムは、先頭のゼロを所与とすれば互いに排他にもなるよう意図的に書いてある。4'b??1? のような形で書いていたら、優先順位のふるまいはずっと読み取りにくかっただろう。
Now the trap the slide hints at by listing casex and casez but never distinguishing them. Reread the definition: casex treats x and z as don't cares on either side of the comparison. That symmetry is the bug. Consider:現在來看投影片只把 casex 與 casez 並列、卻從不加以區分所暗示的那個陷阱。重讀定義:casex 把 x 與 z 在比較的兩側都視為 don't care。那個對稱性就是臭蟲。考慮:ここでスライドが casex と casez を並べるだけで区別しないことがほのめかす罠を見る。定義を読み直そう。casex は x と z を比較のどちら側でもドントケアとして扱う。この対称性こそがバグである。次を考える。
always @* begin
casex (sel)
4'b1xxx: y = a;
4'b01xx: y = b;
4'b001x: y = c;
4'b0001: y = d;
default: y = 4'b0;
endcase
endalways @* begin
casez (sel)
4'b1???: y = a;
4'b01??: y = b;
4'b001?: y = c;
4'b0001: y = d;
default: y = 4'b0;
endcase
endBoth describe the same priority encoder, and both synthesise to the same netlist. They differ only in RTL simulation, and only when sel itself is not fully defined — which is exactly the situation in the first few nanoseconds after power-up, before reset has propagated, or whenever an upstream flip-flop is unreset.兩者描述的是同一個優先編碼器,也合成出同一份 netlist。它們只在 RTL 模擬中有差別,而且只有當 sel 本身沒有完全定義時才有差別 — 那正是上電後頭幾奈秒、重置尚未傳播開來時的情況,或任何上游正反器未被重置時的情況。どちらも同じプライオリティエンコーダを記述し、同じネットリストに合成される。違いは RTL シミュレーションにのみ現れ、しかも sel 自身が完全には定義されていないときだけである。それはまさに電源投入後の最初の数ナノ秒、リセットが伝播し切る前の状況であり、あるいは上流のフリップフロップがリセットされていないときの状況である。
Suppose sel = 4'bxxxx because the register driving it has not been reset. Under casex the first item 4'b1xxx compares bit 3 of the expression (x) against bit 3 of the item (1), finds an x on the expression side, masks the position, and — since the remaining three positions are wildcards in the item — declares a match. The statement takes the first branch and assigns y = a, cleanly and silently. RTL simulation shows a perfectly healthy multiplexer selecting a. The gate netlist, which has no x and whose sel will be some arbitrary 0/1 pattern, selects whatever that pattern decodes to. The two simulations disagree, and the RTL one is the optimistic liar.假設 sel = 4'bxxxx,因為驅動它的暫存器沒有被重置。在 casex 之下,第一個 item 4'b1xxx 會拿運算式的第 3 位(x)去比對 item 的第 3 位(1),發現運算式側是 x,於是把該位置遮掉,而既然 item 裡其餘三個位置都是萬用字元,它就宣告相符。敘述走進第一個分支,乾淨而安靜地指定 y = a。RTL 模擬顯示一個完全健康、正在選擇 a 的多工器。而閘級 netlist 裡沒有 x,它的 sel 會是某組任意的 0/1 樣式,於是選到那組樣式解碼出來的東西。兩邊的模擬不一致,而說謊的是過度樂觀的 RTL 那一邊。sel = 4'bxxxx だとしよう。それを駆動するレジスタがリセットされていないからである。casex のもとでは、最初のアイテム 4'b1xxx は式のビット 3(x)とアイテムのビット 3(1)を比較し、式側が x であることを見つけてその位置をマスクし、アイテムの残り 3 位置はワイルドカードなので一致を宣言する。文は最初の分岐に入り、きれいに、そして静かに y = a を代入する。RTL シミュレーションは a を選択する完全に健全なマルチプレクサを示す。x を持たず sel が任意の 0/1 パターンになるゲートネットリストは、そのパターンがデコードするものを選ぶ。二つのシミュレーションは食い違い、楽観的な嘘をついているのは RTL のほうである。
Under casez the same stimulus behaves differently and much better. The x on the expression side is not a wildcard for casez — only z is. So bit 3 compares x against 1 and yields x, the item does not definitively match, and the simulator falls through. In fact every item fails, the default is taken, and y gets 4'b0; depending on the simulator and on the exact pattern you may also see x propagate onto y. Either way the unknown is visible rather than hidden.在 casez 之下,同樣的激勵行為不同,而且好得多。對 casez 來說運算式側的 x 不是萬用字元 — 只有 z 是。所以第 3 位拿 x 比對 1 得到 x,該 item 並未確定相符,模擬器就往下掉。事實上每個 item 都失敗,於是走 default,y 得到 4'b0;視模擬器與確切樣式而定,你也可能看到 x 傳播到 y 上。不論哪一種,未知值都是可見的,而不是被藏起來。casez のもとでは同じ刺激に対するふるまいが異なり、はるかに望ましい。casez にとって式側の x はワイルドカードではない — z だけがそうである。したがってビット 3 は x と 1 を比較して x を返し、アイテムは確定的に一致せず、シミュレータは次へ落ちる。実際すべてのアイテムが失敗して default が選ばれ、y には 4'b0 が入る。シミュレータと具体的なパターンによっては x が y に伝播するのも見えるだろう。いずれにせよ未知値は隠されず、目に見える。
Never use casex in RTL. Use casez, and put the wildcards only in the case items, never let them reach the case expression. The reason is not that casex synthesises differently — it does not — but that it hides unknowns in simulation and so lets a reset bug escape to the gate level, where it is ten times more expensive to find.RTL 裡絕不要用 casex。用 casez,而且萬用字元只放在 case item 裡,絕不讓它們到達 case 運算式。理由不是 casex 會合成出不同的東西 — 它不會 — 而是它在模擬中藏起未知值,讓重置的錯誤逃到閘級,而在那裡找出它的代價要高上十倍。RTL で casex を使ってはならない。casez を使い、ワイルドカードはケースアイテムにだけ置き、ケース式には決して届かせないこと。理由は casex が異なる合成結果を生むからではない — そうはならない — シミュレーションで未知値を隠し、リセットのバグをゲートレベルまで逃がしてしまうからである。そこで見つける代償は 10 倍高くつく。
SystemVerilog adds case ... inside with the casez-style masking but asymmetric semantics: wildcards are honoured only in the items, never in the expression. If your flow allows SystemVerilog, that is the correct construct and the whole trap disappears.SystemVerilog 加入了 case ... inside,具備 casez 式的遮罩,但語意是不對稱的:萬用字元只在 item 中生效,絕不在運算式中生效。如果你的流程允許 SystemVerilog,那才是正確的構件,整個陷阱就消失了。SystemVerilog は casez 風のマスクを備えつつ非対称な意味論を持つ case ... inside を追加した。ワイルドカードはアイテムでのみ有効で、式では決して有効にならない。フローが SystemVerilog を許すなら、それが正しい構文であり、この罠はまるごと消える。
There is a smaller cousin of the same trap in casez itself. If the case expression is a bus that can genuinely float — the output of a tri-state bus, for instance — then a z really can reach the expression and casez will mask on it exactly as casex masks on an x. This is rare inside a synchronous block, but it is the reason the careful formulation is "wildcards belong in the items" rather than "casez is safe".同樣的陷阱在 casez 本身也有一個小一號的近親。如果 case 運算式是一條真的可能浮接的匯流排 — 例如三態匯流排的輸出 — 那麼 z 確實可能到達運算式,而 casez 會對它遮罩,就像 casex 對 x 遮罩一樣。這在同步區塊裡很罕見,但這正是為什麼謹慎的說法是「萬用字元屬於 item」,而不是「casez 是安全的」。同じ罠の一回り小さい親類が casez 自身にもある。ケース式が本当にフロートしうるバス — たとえばトライステートバスの出力 — であれば、z は実際に式に到達しうるし、casez は casex が x でマスクするのとまったく同様にそれでマスクする。同期ブロック内ではまれだが、慎重な言い方が「casez は安全である」ではなく「ワイルドカードはアイテムに属する」である理由はここにある。
The z rules are shorter because there is only one thing to say: z is the only four-state value that does correspond to a buildable cell, namely a tri-state buffer, and assigning z in a conditional is how you ask for one.z 的規則比較短,因為只有一件事要說:z 是唯一確實對應到某顆可建元件的四值值 — 也就是三態緩衝器 — 而在條件式中指定 z,就是你要求一顆三態緩衝器的方式。z の規則は短い。言うべきことが一つしかないからである。z は実際に構築可能なセル、すなわちトライステートバッファに対応する唯一の 4 値であり、条件文で z を代入することがそれを要求する方法なのである。
assign dout = oe ? data : 8'bz; // 8 tri-state buffers, enable = oe
The inference rule is precise: in a conditional whose branches are a normal expression and the literal z, the tool instantiates one tri-state driver per bit, with the branch condition driving the enable pin. The slide's wording — "z assigned to wire or reg as the default outcome for a conditional statement" — covers both the continuous-assignment form above and the procedural form inside an always block. The lecture's ALU example uses exactly this: assign alu_out = (en == 1) ? out_int : 2'bz; gives two tri-state buffers between the internal register and the output port.推論規則很精確:在一個分支為正常運算式、另一分支為常數 z 的條件式中,工具會為每個位元實例化一顆三態驅動器,並讓分支條件驅動其致能腳。投影片的用語 —「z 被指定給 wire 或 reg 作為條件敘述的預設結果」— 同時涵蓋上面的連續指定形式與 always 區塊內的程序式形式。這一講的 ALU 範例用的正是這個:assign alu_out = (en == 1) ? out_int : 2'bz; 在內部暫存器與輸出埠之間放了兩顆三態緩衝器。推論規則は明確である。分岐の一方が通常の式、他方がリテラル z である条件文では、ツールはビットごとにトライステートドライバを一つインスタンス化し、分岐条件をそのイネーブルピンに接続する。スライドの表現 —「条件文の既定の結果として wire または reg に z を代入する」— は、上記の連続代入形式と always ブロック内の手続き形式の両方を指す。講義の ALU の例はまさにこれで、assign alu_out = (en == 1) ? out_int : 2'bz; は内部レジスタと出力ポートの間にトライステートバッファを 2 個置く。
A tri-state buffer is not free. On a typical library a tri-state inverter or buffer is about 2 to 3 GE, comparable to a multiplexer, and it is slower than an ordinary buffer because the enable path must gate both the pull-up and the pull-down stack. It also brings three real engineering problems that a multiplexer does not: a floating node has no defined value, so the bus must either be guaranteed always driven or be fitted with a bus holder (a weak keeper, which costs area and fights the drivers during a transition); contention between two drivers that are both enabled is a short circuit that burns current and can damage the chip; and automatic test-pattern generation must prove that no test ever enables two drivers at once, which is hard and sometimes requires extra logic.三態緩衝器不是免費的。在典型的庫裡,一顆三態反相器或緩衝器約 2 到 3 GE,與多工器相當,而且比一般緩衝器慢,因為致能路徑必須同時閘控上拉與下拉堆疊。它還帶來三個多工器沒有的真實工程問題:浮接節點沒有定義好的值,所以匯流排要嘛必須保證永遠有人驅動,要嘛得裝上 bus holder(一個弱保持器,既佔面積又在轉態時與驅動源對抗);兩個同時致能的驅動源相爭是短路,會燒電流甚至損壞晶片;而自動測試樣式產生必須證明沒有任何測試會同時致能兩個驅動源,這很困難,有時還需要額外邏輯。トライステートバッファは無料ではない。典型的なライブラリではトライステートのインバータやバッファは約 2〜3 GE でマルチプレクサに匹敵し、イネーブル経路がプルアップとプルダウンの両スタックをゲートせねばならないため通常のバッファより遅い。さらにマルチプレクサにはない現実の設計問題を三つ持ち込む。フローティングのノードには定義された値がないため、バスは常に駆動されることを保証するか、バスホルダ(面積を食い、遷移中にドライバと競合する弱いキーパ)を付けるかしなければならない。二つのドライバが同時にイネーブルになる競合は短絡であり、電流を食いチップを損傷しうる。そして自動テストパターン生成は、いかなるテストでも二つのドライバが同時にイネーブルにならないことを証明せねばならず、これは難しく、追加論理を要することもある。
On a chip pad, a tri-state is mandatory — a bidirectional pin has no alternative. On an internal bus it was standard practice in the 1990s and is now almost universally replaced by a multiplexer tree, because the mux is testable, has no contention mode, needs no keeper, and is usually no larger once the keeper and the enable decode are counted. Modern physical-synthesis flows will sometimes convert an internal tri-state bus into a mux tree automatically. If you are writing an internal bus in this course's project, write a mux.在晶片的 pad 上,三態是必須的 — 雙向接腳沒有別的選擇。在內部匯流排上,1990 年代這是標準做法,如今幾乎一律被多工器樹取代,因為多工器可測試、沒有競爭模式、不需要保持器,而且把保持器與致能解碼一併算進去後通常也不會更大。現代的實體合成流程有時會自動把內部三態匯流排轉成多工器樹。如果你在本課程的專題裡要寫一條內部匯流排,請寫多工器。チップの パッド ではトライステートは必須である。双方向ピンに代替はない。内部バスでは 1990 年代には標準的な手法だったが、今ではほぼ例外なくマルチプレクサツリーに置き換えられている。マルチプレクサはテスト可能で、競合モードがなく、キーパを要さず、キーパとイネーブルデコードまで数えれば通常は大きくもならないからである。現代の物理合成フローは、内部トライステートバスを自動的にマルチプレクサツリーへ変換することもある。本科目のプロジェクトで内部バスを書くなら、マルチプレクサを書くこと。
One final subtlety that trips people up: z is legal only as the whole value of a branch, not mixed into an expression. assign y = a & 1'bz; is meaningless, because a tri-state buffer's output cannot be an input to a gate; a gate whose input floats has undefined behaviour and may even draw crowbar current. Similarly assign y = oe ? data : 8'b0; is a multiplexer, not a tri-state, even though it looks almost identical — the difference is entirely whether the else-branch is z or a real value.最後一個會絆倒人的細節:z 只有當成一個分支的整體值時才合法,不能混進運算式裡。assign y = a & 1'bz; 毫無意義,因為三態緩衝器的輸出不能當成閘的輸入;輸入浮接的閘行為未定義,甚至可能產生貫穿電流。同理,assign y = oe ? data : 8'b0; 是多工器而非三態,儘管看起來幾乎一樣 — 差別完全在於 else 分支是 z 還是一個真實的值。最後に人をつまずかせる細かな点がある。z が合法なのは分岐の値全体としてだけで、式の中に混ぜることはできない。assign y = a & 1'bz; は無意味である。トライステートバッファの出力はゲートの入力になりえず、入力がフロートするゲートはふるまいが未定義で、貫通電流を引くことさえあるからである。同様に assign y = oe ? data : 8'b0; はほとんど同じに見えてもトライステートではなくマルチプレクサである。違いはひとえに else 側の分岐が z か実在の値かにある。
Problem: For each line, say whether it is acceptable in synthesisable RTL, what hardware it produces if so, and what goes wrong if not.問題:對每一行,說明它在可合成 RTL 中是否可接受;若可接受,它產生什麼硬體;若不可接受,會出什麼問題。問題:各行について、合成可能な RTL として許容されるか、許容されるならどんなハードウェアを生むか、されないなら何が問題になるかを述べよ。
(a) assign y = en ? d : 8'bz; (b) if (sel == 2'bx1) y = a; (c) case (op) 2'b00: y = a; 2'b01: y = b; default: y = 4'bx; endcase (d) reg [7:0] r = 8'bx; (e) casez (req) 4'b1???: g = 0; 4'b01??: g = 1; default: g = 2; endcase (f) assign busy = (state === 3'bx) ? 1'b1 : 1'b0; (g) assign y = sel ? 4'bz : 4'bz; (h) wire [3:0] m = a & 4'b1z1z;
(a) Legal. Eight tri-state buffers with en on the enable. About 20 GE on a typical library, plus whatever the bus needs by way of a keeper.(a) 合法。八顆三態緩衝器,致能接 en。在典型的庫上約 20 GE,再加上匯流排所需的保持器。(a) 合法。en をイネーブルに接続したトライステートバッファ 8 個。典型的なライブラリで約 20 GE、これにバスが必要とするキーパ分が加わる。
(b) Not meaningful. The literal 2'bx1 contains an x, so == returns x for every value of sel and the if never fires. This is dead code that reads as live code. If you wanted "sel[0] is 1, sel[1] is anything", write if (sel[0]) or use a casez item 2'b?1.(b) 沒有意義。常數 2'bx1 含有 x,所以不論 sel 是什麼值,== 都回傳 x,if 永遠不會成立。這是讀起來像活碼的死碼。如果你想表達「sel[0] 是 1,sel[1] 隨便」,請寫 if (sel[0]),或用 casez 的 item 2'b?1。(b) 無意味。リテラル 2'bx1 は x を含むので、sel がどんな値でも == は x を返し、if は決して成立しない。生きたコードに見える死んだコードである。「sel[0] が 1 で sel[1] は何でもよい」と言いたいなら if (sel[0]) と書くか、casez のアイテム 2'b?1 を使うこと。
(c) Legal and good. Two of four codes are don't cares, which the optimiser uses. Here, since only op[0] distinguishes the two listed cases, the logic collapses to a single 2:1 mux on op[0] and op[1] drops out of the cone — though it stays as a port.(c) 合法而且很好。四個碼中有兩個是 don't care,最佳化器會加以利用。這裡因為只有 op[0] 區分兩個列出的 case,邏輯塌縮成一顆由 op[0] 控制的 2:1 多工器,op[1] 則退出邏輯錐 — 雖然它仍以一個埠的形式留著。(c) 合法かつ良好。4 個のコードのうち 2 個がドントケアで、最適化器がこれを利用する。ここでは列挙された 2 ケースを区別するのが op[0] だけなので、論理は op[0] による 2:1 マルチプレクサ一つに縮退し、op[1] はコーンから外れる — ただしポートとしては残る。
(d) Not a conditional default. A declaration initialiser is not one of the three allowed places. For ASIC synthesis it is ignored entirely (a flip-flop has no power-up value); for FPGA synthesis it would set the bitstream preload, but to x, which is not a value, so it is ignored there too. Use a reset branch.(d) 不是條件式的預設分支。宣告時的初始值不屬於三個被允許的位置。對 ASIC 合成來說它完全被忽略(正反器沒有上電值);對 FPGA 合成來說它會設定位元流的預載值,但預載成 x 而 x 不是一個值,所以那裡也一樣被忽略。請改用重置分支。(d) 条件文のデフォルトではない。宣言時の初期化子は、許された三つの場所のいずれでもない。ASIC 合成では完全に無視される(フリップフロップに電源投入時の値はない)。FPGA 合成ではビットストリームのプリロードを設定することになるが、値でない x を設定することになるので、そこでも無視される。リセット分岐を使うこと。
(e) Legal. A four-input priority encoder, roughly 6 to 8 GE. The wildcards are in the items, which is the safe position.(e) 合法。一個四輸入優先編碼器,約 6 到 8 GE。萬用字元在 item 裡,這是安全的位置。(e) 合法。4 入力のプライオリティエンコーダで、およそ 6〜8 GE。ワイルドカードはアイテム側にあり、これは安全な位置である。
(f) Not synthesisable. === against an x literal has no hardware. The tool will either error or constant-fold the comparison to 0, hard-wiring busy low and silently deleting the check. To detect an invalid state, add a valid bit or use a state encoding with an explicit illegal code.(f) 不可合成。拿 === 去比對 x 常數沒有對應的硬體。工具要嘛報錯,要嘛把比較常數摺疊成 0,把 busy 硬接成低電位,悄悄刪掉這道檢查。要偵測非法狀態,請加一個 valid 位元,或使用帶有明確非法碼的狀態編碼。(f) 合成不可。x リテラルに対する === に対応するハードウェアはない。ツールはエラーを出すか、比較を定数 0 に畳み込んで busy を Low に固定し、検査を黙って削除する。不正な状態を検出するには valid ビットを追加するか、明示的な不正コードを持つ状態符号化を使うこと。
(g) Legal but pointless. Both branches are z, so the net is never driven. The synthesiser produces either nothing at all or a permanently disabled buffer, and every downstream consumer floats. A lint tool should flag it.(g) 合法但毫無用處。兩個分支都是 z,所以這條 net 從來沒被驅動過。合成器產生的要嘛什麼都沒有,要嘛是一顆永遠被禁用的緩衝器,而下游每一個使用者都浮接。lint 工具應該要標出它。(g) 合法だが無意味。両方の分岐が z なので、この net は一度も駆動されない。合成器は何も生成しないか、恒久的に無効化されたバッファを生成し、下流の利用者はすべてフロートする。lint ツールが警告すべきである。
(h) Illegal in spirit and useless in practice. A z inside an ordinary bitwise expression propagates as an unknown in simulation (a & z gives x unless a is 0) and has no gate realisation. Tri-state values must be the whole value of a conditional branch, never an operand.(h) 精神上非法,實務上無用。放在一般位元運算式裡的 z 在模擬中會以未知值的形式傳播(a & z 除非 a 為 0 否則給出 x),而且沒有閘級實現。三態值必須是某個條件分支的整個值,絕不能當作運算元。(h) 精神において不正、実務において無用。通常のビット演算式の中の z はシミュレーションで未知値として伝播し(a が 0 でない限り a & z は x を返す)、ゲートでの実現も存在しない。トライステート値は条件分岐の値全体でなければならず、決してオペランドにしてはならない。
Problem: A four-bit register sel drives the priority encoder of section 3.5. The register has no reset, so at time 0 it holds 4'bxxxx; at the first clock edge it loads 4'b0010. Give the value of y at time 0 and after the first edge, for (i) RTL simulation of the casex version, (ii) RTL simulation of the casez version, (iii) gate-level simulation of the synthesised netlist, assuming the unreset flip-flops come up holding 4'b1111. What is the practical consequence?問題:一個四位元暫存器 sel 驅動 3.5 節的優先編碼器。該暫存器沒有重置,所以在時間 0 時它保存 4'bxxxx;在第一個時脈邊緣它載入 4'b0010。請給出在時間 0 與第一個邊緣之後 y 的值,分別針對 (i) casex 版本的 RTL 模擬、(ii) casez 版本的 RTL 模擬、(iii) 合成後 netlist 的閘級模擬(假設未重置的正反器上電後保持 4'b1111)。實務上的後果是什麼?問題:4 ビットのレジスタ sel が 3.5 節のプライオリティエンコーダを駆動する。このレジスタにはリセットがないため、時刻 0 では 4'bxxxx を保持し、最初のクロックエッジで 4'b0010 をロードする。(i) casex 版の RTL シミュレーション、(ii) casez 版の RTL シミュレーション、(iii) 合成後ネットリストのゲートレベルシミュレーション(リセットされないフリップフロップは 4'b1111 を保持して立ち上がると仮定)のそれぞれについて、時刻 0 と最初のエッジ後の y の値を示せ。実務上の帰結は何か。
At time 0, sel = 4'bxxxx:時間 0,sel = 4'bxxxx:時刻 0、sel = 4'bxxxx のとき:
4'b1xxx masks all four positions (bit 3 because the expression is x, bits 2–0 because the item is x) and matches. y = a. Clean, confident, and completely fictitious.(i) casex:第一個 item 4'b1xxx 把四個位置全部遮掉(第 3 位是因為運算式為 x,第 2–0 位是因為 item 為 x),然後宣告相符。y = a。乾淨、篤定,而且完全是虛構的。(i) casex:最初のアイテム 4'b1xxx は 4 位置すべてをマスクし(ビット 3 は式が x だから、ビット 2–0 はアイテムが x だから)、一致を宣言する。y = a。きれいで自信に満ち、そして完全な作り話である。default and gives y = 4'b0; many simulators additionally propagate x onto y depending on how the comparison is optimised. Either way the problem is visible.(ii) casez:第 3 位拿 x 比對 1 得到 x,所以沒有任何 item 確定相符。模擬器落到 default,給出 y = 4'b0;視比較如何被最佳化,許多模擬器還會額外把 x 傳播到 y 上。不論哪一種,問題都是看得見的。(ii) casez:ビット 3 は x と 1 を比較して x を返すので、どのアイテムも確定的には一致しない。シミュレータは default まで落ちて y = 4'b0 を与える。比較がどう最適化されるかによっては、多くのシミュレータがさらに x を y に伝播させる。いずれにせよ問題は見える。sel = 4'b1111, which decodes in the real priority logic to the highest-priority item, so y = a. Also confident, also a different reason.(iii) 閘級:sel = 4'b1111,在真實的優先權邏輯中會解碼到優先權最高的 item,所以 y = a。同樣篤定,但理由不同。(iii) ゲートレベル:sel = 4'b1111 であり、実在の優先順位論理では最優先のアイテムにデコードされるので y = a となる。これも自信に満ちているが、理由は別である。After the first edge, sel = 4'b0010: all three agree — the third item 4'b001? matches, y = c.第一個邊緣之後,sel = 4'b0010:三者一致 — 第三個 item 4'b001? 相符,y = c。最初のエッジ後、sel = 4'b0010 のとき:三者は一致する。3 番目のアイテム 4'b001? が一致し、y = c となる。
Consequence. With casex the RTL simulation and the gate simulation happen to agree at time 0 (both say a), which is the worst possible outcome: the agreement is a coincidence of this particular power-up pattern, and the missing reset on sel is never discovered. Change the library or the power-up pattern and the two diverge. With casez the RTL simulation immediately shows that something upstream is unreset, and the bug is found in an afternoon rather than at tape-out. This is the entire argument for the coding rule, and it is worth being able to narrate in an exam.後果。用 casex 時,RTL 模擬與閘級模擬在時間 0 恰好一致(都說是 a),這是最糟的結果:這份一致只是這個特定上電樣式的巧合,而 sel 少了重置這件事永遠不會被發現。換一套庫或換一個上電樣式,兩者就分歧了。用 casez 時,RTL 模擬立刻顯示上游有東西沒被重置,於是這個錯誤在一個下午內就被找到,而不是等到 tape-out。這就是那條編碼規則的全部論證,值得練到能在考試中完整講出來。帰結。casex では RTL シミュレーションとゲートシミュレーションが時刻 0 でたまたま一致する(どちらも a と言う)。これは最悪の結果である。一致はこの特定の電源投入パターンの偶然にすぎず、sel のリセット欠落は決して発見されない。ライブラリや電源投入パターンを変えれば両者は乖離する。casez なら RTL シミュレーションが上流にリセットされていないものがあることを直ちに示し、バグはテープアウト時ではなく午後のうちに見つかる。これがこのコーディング規則の論拠のすべてであり、試験で語れるようにしておく価値がある。
Slide 5 is four lines long and contains most of what determines whether your data path fits in the budget. It says that adders and subtractors have a variety of possible implementations, ripple-carry and carry-look-ahead among them; that * and / "may not be implemented efficiently as combinational logics"; and that the special cases of multiplying, dividing or taking the remainder by a power of two reduce to shifts and part selects. Each of those deserves unpacking, and before any of them there is a question the slide does not raise at all.第 5 張投影片只有四行,卻包含了決定你的資料路徑能不能塞進預算的大部分內容。它說加法器與減法器有多種可能實現方式,漣波進位與超前進位都在其中;說 * 與 /「may not be implemented efficiently as combinational logics」;並說乘以、除以或對 2 的冪取餘數這些特例會化約成移位與部分選取。每一條都值得拆開來看,而在這之前還有一個投影片完全沒提的問題。スライド 5 は 4 行しかないが、データパスが予算に収まるかどうかを決める要素の大半を含む。加算器と減算器にはリップルキャリーやキャリー先見を含む多様な実装がありうること、* と / は「may not be implemented efficiently as combinational logics」であること、そして 2 のべき乗による乗算・除算・剰余という特殊ケースはシフトと部分選択に帰着すること。そのどれも掘り下げる価値があり、そのすべての前に、スライドがまったく提起しない問いがある。
Before the tool decides how to build an adder it must decide how wide the adder is, and Verilog's width rules are unusual enough that getting them wrong is the most common cause of a data path that costs twice what it should. The rules, briefly: an expression has a context-determined width equal to the maximum of the widths of all its operands and, for the top-level expression of an assignment, of the assignment target as well; this width is then pushed down into every operand, which is zero- or sign-extended to match. Certain sub-expressions are instead self-determined — the operands of concatenation and replication, the shift amount of a shift, the condition of ?:, and the operands of comparison operators — and are evaluated at their own natural width.在工具決定如何建一個加法器之前,它必須先決定這個加法器多寬,而 Verilog 的位寬規則特殊到「弄錯它」成為資料路徑花費兩倍代價最常見的原因。規則簡述如下:運算式有一個由情境決定的位寬,等於其所有運算元位寬的最大值,而對指定敘述的最上層運算式,還要納入指定目標的位寬;這個位寬接著被往下推到每個運算元,運算元會零擴展或符號擴展以符合它。某些子運算式則是自決定的 — 串接與重複的運算元、移位的位移量、?: 的條件,以及比較運算子的運算元 — 它們依自己的自然位寬求值。ツールが加算器をどう作るか決める前に、その加算器がどれだけ広いかを決めねばならない。Verilog の幅規則は独特で、これを誤ることがデータパスのコストが本来の 2 倍になる最大の原因である。規則を簡潔に述べる。式は文脈決定の幅を持ち、それは全オペランドの幅の最大値であり、代入文の最上位の式では代入先の幅も含める。この幅は各オペランドへ押し下げられ、オペランドはゼロ拡張または符号拡張されて合わせられる。一方、連結と繰り返しのオペランド、シフト量、?: の条件、比較演算子のオペランドは自己決定であり、それぞれの自然な幅で評価される。
The consequence in hardware is direct. In assign s = a + b; with a and b eight bits and s eight bits, the tool builds an eight-bit adder and throws away the carry. With s nine bits, it builds a nine-bit adder and the ninth bit is the carry out. With s sixteen bits, it builds a sixteen-bit adder whose top eight bits are provably zero, and only then relies on constant propagation to sweep them. The intermediate sub-expressions matter too: in assign s = (a + b) >> 1; the shift is applied after the addition has been truncated to the context width, so if s is eight bits you lose the carry before the shift and get the wrong average. The correct eight-bit average of two eight-bit numbers is {1'b0,a} + {1'b0,b} shifted right, or equivalently a nine-bit intermediate.在硬體上的後果很直接。在 assign s = a + b; 中,若 a、b 為八位元而 s 也是八位元,工具建一個八位元加法器並把進位丟掉。若 s 是九位元,它建一個九位元加法器,第九位就是進位輸出。若 s 是十六位元,它建一個十六位元加法器,其高八位可證明為零,然後才靠常數傳播把它們掃掉。中間子運算式同樣重要:在 assign s = (a + b) >> 1; 中,移位是在加法已被截斷到情境位寬之後才套用的,所以若 s 是八位元,你會在移位前就丟掉進位,得到錯誤的平均值。兩個八位元數正確的八位元平均是 {1'b0,a} + {1'b0,b} 再右移,或等價地用一個九位元的中間值。ハードウェア上の帰結は直接的である。assign s = a + b; で a と b が 8 ビット、s も 8 ビットなら、ツールは 8 ビット加算器を作りキャリーを捨てる。s が 9 ビットなら 9 ビット加算器を作り、9 ビット目がキャリー出力になる。s が 16 ビットなら上位 8 ビットがゼロだと証明できる 16 ビット加算器を作り、そのうえで定数伝播に頼って掃き出す。中間の部分式も重要である。assign s = (a + b) >> 1; では、シフトは加算が文脈幅に切り詰められた後に適用されるので、s が 8 ビットならシフト前にキャリーを失い、誤った平均を得る。2 つの 8 ビット数の正しい 8 ビット平均は {1'b0,a} + {1'b0,b} を右シフトしたもの、あるいは同値だが 9 ビットの中間値を使ったものである。
wire [7:0] a, b, avg;
assign avg = (a + b) >> 1;
// 8-bit add: 200+100 = 300 -> 44,
// then >>1 = 22. Expected 150.wire [7:0] a, b, avg;
wire [8:0] sum = {1'b0,a} + {1'b0,b};
assign avg = sum[8:1];
// 9-bit add keeps the carry;
// the shift is a part select, free.The extra cost of the correct version is one bit of adder, about 5 GE. The cost of the wrong version is a silent wraparound that only shows up on large inputs.正確版本多出的成本是一位元的加法器,約 5 GE。錯誤版本的代價是一個只有在輸入很大時才會現形的靜默回繞。正しい版の追加コストは加算器 1 ビット分、約 5 GE である。誤った版のコストは、入力が大きいときにだけ現れる黙ったラップアラウンドである。
Writing a + b does not pick an adder architecture. It emits a generic add node, and the mapping pass later chooses an implementation from a library of arithmetic generators — DesignWare in the Synopsys flow, ChipWare in the Cadence flow, or the tool's own built-ins — guided by the timing constraint on the path. This is one of the few places where synthesis genuinely does architectural design on your behalf, and it is worth knowing what the choices are.寫下 a + b 並不會挑選加法器架構。它產生的是一個泛用的 add 節點,之後對應階段才會從一組算術產生器庫中 — Synopsys 流程裡的 DesignWare、Cadence 流程裡的 ChipWare,或工具自己的內建產生器 — 依該路徑上的時序限制挑一個實現。這是少數幾個合成真的替你做架構設計的地方,值得知道選項有哪些。a + b と書いても加算器のアーキテクチャは選ばれない。生成されるのはジェネリックな add ノードであり、実装はのちにマッピングパスが算術ジェネレータのライブラリ — Synopsys フローの DesignWare、Cadence フローの ChipWare、あるいはツール内蔵のもの — から、その経路のタイミング制約に導かれて選ぶ。合成が本当にあなたの代わりにアーキテクチャ設計を行う数少ない場面であり、選択肢を知っておく価値がある。
A ripple-carry adder is $n$ full adders chained carry-out to carry-in. Its area is the cleanest possible: $n$ full adders at roughly 5 to 7 GE each, so about $6n$ GE, with no overhead. Its delay is the problem. The carry must propagate through all $n$ stages, so the worst-case path is $n$ carry delays plus one sum delay:漣波進位加法器是把 $n$ 顆全加器的進位輸出接到下一顆的進位輸入串成一串。它的面積是最乾淨的:$n$ 顆全加器,每顆約 5 到 7 GE,所以約 $6n$ GE,沒有額外開銷。問題出在延遲。進位必須走過全部 $n$ 級,所以最差路徑是 $n$ 個進位延遲加一個和延遲:リップルキャリー加算器は $n$ 個の全加算器をキャリー出力からキャリー入力へ鎖状につないだものである。面積は考えうるもっとも素直な形で、$n$ 個の全加算器が各 5〜7 GE、合計で約 $6n$ GE、オーバーヘッドはない。問題は遅延である。キャリーは $n$ 段すべてを伝播せねばならず、最悪経路はキャリー遅延 $n$ 段に和の遅延 1 段を加えたものになる。
For a sixteen-bit adder that is about sixteen levels of logic, which at a typical 65 nm library's fan-out-of-four delay of roughly 25 ps per level is around 0.4 ns — already a significant fraction of a 1 ns clock period, and that is before routing. For a thirty-two-bit adder it is about 0.8 ns and the path is almost certainly critical.對一個十六位元加法器來說那大約是十六層邏輯;以典型 65 nm 庫每層約 25 ps 的 fan-out-of-four 延遲計算,約為 0.4 ns — 在 1 ns 的時脈週期裡已經是很可觀的一部分,而且這還沒算繞線。三十二位元加法器約 0.8 ns,這條路徑幾乎一定會是關鍵路徑。16 ビット加算器ではおよそ 16 段の論理に相当し、典型的な 65 nm ライブラリの段あたり約 25 ps のファンアウト 4 遅延で換算すると約 0.4 ns になる。1 ns のクロック周期のかなりの部分を占めており、しかもこれは配線前の値である。32 ビット加算器なら約 0.8 ns で、この経路はほぼ確実にクリティカルになる。
A carry-look-ahead adder breaks the dependency by computing, for each bit, a generate signal $g_i = a_i b_i$ and a propagate signal $p_i = a_i \oplus b_i$, and then combining them in a tree. In the classical four-bit block form the carries within a block are computed in two levels from the block's $g$ and $p$, and the blocks themselves are chained or combined in a second-level look-ahead. In the modern parallel-prefix form (Kogge–Stone, Brent–Kung, Sklansky, Han–Carlson) the group generate and propagate signals are combined in a prefix tree, giving超前進位加法器打破這個相依性:它為每一位元計算一個產生訊號 $g_i = a_i b_i$ 與一個傳播訊號 $p_i = a_i \oplus b_i$,再用樹狀結構把它們組合起來。在古典的四位元區塊形式中,區塊內的進位由該區塊的 $g$ 與 $p$ 以兩層算出,而區塊本身再串接或以第二層超前進位組合。在現代的平行前綴形式(Kogge–Stone、Brent–Kung、Sklansky、Han–Carlson)中,群組的產生與傳播訊號在一棵前綴樹中組合,得到キャリー先見加算器は、各ビットについて生成信号 $g_i = a_i b_i$ と伝播信号 $p_i = a_i \oplus b_i$ を計算し、それらを木構造で結合することでこの依存関係を断ち切る。古典的な 4 ビットブロック形式では、ブロック内のキャリーはそのブロックの $g$ と $p$ から 2 段で計算され、ブロック自体は連鎖されるか第 2 段の先見で結合される。現代の並列プレフィックス形式(Kogge–Stone、Brent–Kung、Sklansky、Han–Carlson)では、グループの生成・伝播信号がプレフィックスツリーで結合され、次が得られる。
For the same sixteen-bit adder that is about four to five levels instead of sixteen, at perhaps 1.6 to 2.5 times the area. The trade is explicit and the tool makes it for you: with a loose constraint it picks the ripple form and saves area; tighten the constraint and it swaps in a prefix form; tighten it further and it may pick a carry-select adder, which duplicates the upper half of the adder for both possible carry-in values and multiplexes the answer, buying $O(\sqrt{n})$ or $O(\log n)$ delay at roughly double the area.對同一個十六位元加法器來說,層數從十六降到大約四到五層,代價是大約 1.6 到 2.5 倍的面積。這個取捨是明確的,而且是工具替你做的:限制寬鬆時它挑漣波形式以省面積;把限制收緊,它就換成前綴形式;再更緊,它可能挑 carry-select 加法器 — 把加法器上半部針對兩種可能的進位輸入各複製一份再用多工器選答案,用大約兩倍面積換到 $O(\sqrt{n})$ 或 $O(\log n)$ 的延遲。同じ 16 ビット加算器で段数は 16 ではなく 4〜5 段になり、面積はおそらく 1.6〜2.5 倍になる。このトレードオフは明示的で、しかもツールがあなたの代わりに行う。制約が緩ければリップル形式を選んで面積を節約し、制約を締めればプレフィックス形式に入れ替え、さらに締めればキャリーセレクト加算器を選ぶこともある。これは加算器の上位半分をキャリー入力の 2 通りについて複製し、答えをマルチプレクサで選ぶもので、およそ倍の面積で $O(\sqrt{n})$ あるいは $O(\log n)$ の遅延を買う。
| Architecture架構アーキテクチャ | Area, 32-bit (approx. GE)面積,32 位元(約 GE)面積、32 ビット(概算 GE) | Levels of logic邏輯層數論理段数 | Chosen when何時被選用選ばれる条件 |
|---|---|---|---|
| Ripple carry漣波進位リップルキャリー | 190 | ~32 | Timing is slack; area matters時序有餘裕;面積要緊タイミングに余裕があり、面積が重要なとき |
| Carry-skipCarry-skipキャリースキップ | 230 | ~14 | Mild timing pressure時序壓力輕微軽度のタイミング圧力 |
| Carry-selectCarry-selectキャリーセレクト | 390 | ~10 | Moderate pressure中等壓力中程度の圧力 |
| Brent–Kung prefixBrent–Kung 前綴Brent–Kung プレフィックス | 320 | ~9 | Area-aware fast adder兼顧面積的快速加法器面積を意識した高速加算器 |
| Kogge–Stone prefixKogge–Stone 前綴Kogge–Stone プレフィックス | 520 | ~6 | Hard timing constraint時序限制嚴苛厳しいタイミング制約 |
The figures are indicative only and vary by a factor of two between libraries, but the ordering and the ratios are robust. The practical lesson is that you influence the choice through create_clock and set_max_delay, not through the RTL, and that if you need a particular architecture — because you are matching a published area figure, or because you need a specific carry-save structure for a multiplier — you must instantiate it, not describe it.這些數字只是指示性的,不同庫之間可能相差兩倍,但排序與比例是穩健的。實務上的教訓是:你是透過 create_clock 與 set_max_delay 影響這個選擇,而不是透過 RTL;而如果你需要某個特定架構 — 因為要對上某篇已發表的面積數據,或因為乘法器需要特定的 carry-save 結構 — 你必須實例化它,而不是描述它。これらの数値はあくまで目安で、ライブラリ間で 2 倍ほど違いうるが、順序と比率は頑健である。実務上の教訓は、選択に影響を与えるのは RTL ではなく create_clock と set_max_delay であること、そして特定のアーキテクチャが必要なら — 公表された面積値に合わせるため、あるいは乗算器に特定のキャリーセーブ構造が必要なため — 記述するのではなくインスタンス化せねばならないことである。
a - b becomes a + ~b + 1, which is the same adder with the b inputs inverted and the carry-in tied to 1. The inverters cost about $0.7n$ GE and nothing in depth, because they sit in parallel with the generate/propagate computation. So a subtractor is an adder plus about ten per cent, and a block that must do both — an ALU with an add/subtract control — is an adder preceded by $n$ XOR gates (b XOR sub) with sub as the carry-in, costing about $2n$ GE extra and one XOR level of delay.a - b 變成 a + ~b + 1,也就是同一個加法器,只是 b 的輸入被反相、進位輸入綁為 1。那些反相器約花 $0.7n$ GE,深度上不花任何成本,因為它們與產生/傳播的計算並行。所以減法器就是加法器再多約一成;而一個兩者都要做的區塊 — 帶加減控制的 ALU — 則是在加法器前面放 $n$ 顆 XOR 閘(b XOR sub)並以 sub 當進位輸入,額外約 $2n$ GE 與一層 XOR 的延遲。a - b は a + ~b + 1 になる。すなわち b の入力を反転し、キャリー入力を 1 に固定した同じ加算器である。インバータは約 $0.7n$ GE かかるが深さには影響しない。生成/伝播の計算と並列に位置するからである。したがって減算器は加算器プラス約 1 割であり、両方を行うブロック — 加減算制御付きの ALU — は加算器の前に $n$ 個の XOR ゲート(b XOR sub)を置き sub をキャリー入力にしたもので、追加で約 $2n$ GE と XOR 1 段分の遅延がかかる。
This is also why the slide's trick for relational operators works, and why section 6.1 can be short: a comparator is a subtractor whose sum outputs you throw away.這也是投影片對關係運算子那個技巧之所以成立的原因,以及 6.1 節可以寫得很短的原因:比較器就是一個把和輸出丟掉的減法器。これはスライドの関係演算子に関する手口が成り立つ理由でもあり、6.1 節が短くて済む理由でもある。比較器とは和の出力を捨てた減算器にほかならない。
When both operands of * are signals, the tool must build a real multiplier. The slide's warning that this "may not be implemented efficiently as combinational logics" is an understatement worth quantifying.當 * 的兩個運算元都是訊號時,工具必須建一個真正的乘法器。投影片那句「may not be implemented efficiently as combinational logics」的警告是個輕描淡寫,值得量化。* の両オペランドが信号であれば、ツールは本物の乗算器を作らねばならない。スライドの「may not be implemented efficiently as combinational logics」という警告は控えめすぎる表現であり、数値で示す価値がある。
The textbook array multiplier for two $n$-bit unsigned numbers forms $n^2$ partial-product bits with $n^2$ AND gates, then sums the $n$ shifted partial products. Summing them with a carry-save array uses roughly $n^2 - n$ full adders followed by an $n$-bit carry-propagate adder for the final merge. At 1 GE per AND and 6 GE per full adder:教科書上的陣列乘法器對兩個 $n$ 位元無號數,用 $n^2$ 顆 AND 閘形成 $n^2$ 個部分積位元,再把 $n$ 個位移後的部分積相加。用 carry-save 陣列相加大約需要 $n^2 - n$ 顆全加器,最後再接一個 $n$ 位元的進位傳播加法器做最終合併。以每顆 AND 1 GE、每顆全加器 6 GE 計算:教科書的なアレイ乗算器は、2 つの $n$ ビット符号なし数に対し $n^2$ 個の AND ゲートで $n^2$ 個の部分積ビットを作り、$n$ 個のシフトされた部分積を加算する。キャリーセーブアレイで加算するとおよそ $n^2 - n$ 個の全加算器が必要で、最後に $n$ ビットのキャリー伝播加算器でマージする。AND が 1 GE、全加算器が 6 GE とすると:
Numerically: an 8 × 8 multiplier is around 450 GE and fifteen to twenty levels deep; a 16 × 16 is around 1800 GE; a 32 × 32 is around 7000 GE. Compare the 190 GE of a 32-bit ripple adder. One multiply is worth forty adds. That ratio is the number to remember.具體數字:8 × 8 乘法器約 450 GE、深十五到二十層;16 × 16 約 1800 GE;32 × 32 約 7000 GE。對照一下 32 位元漣波加法器的 190 GE。一次乘法抵四十次加法。這個比值就是該記住的數字。数値で言えば、8 × 8 の乗算器は約 450 GE で深さ 15〜20 段、16 × 16 は約 1800 GE、32 × 32 は約 7000 GE である。32 ビットリップル加算器の 190 GE と比べてほしい。乗算 1 回は加算 40 回分に相当する。この比こそ覚えるべき数字である。
A Wallace or Dadda tree reduces the partial-product array in $O(\log n)$ levels of 3:2 compressors instead of $O(n)$ levels of carry-save rows, giving $T = O(\log n)$ at similar or slightly larger area but much worse wiring regularity. Booth recoding (radix-4 is standard) halves the number of partial products to $n/2$, roughly halving the array area at the cost of a recoding stage and of having to handle the −2× and −1× multiples. Combining radix-4 Booth with a Wallace tree and a prefix final adder is what a commercial DesignWare multiplier actually instantiates when the constraint is tight.Wallace 樹或 Dadda 樹用 $O(\log n)$ 層的 3:2 壓縮器來縮減部分積陣列,而不是 $O(n)$ 層的 carry-save 列,得到 $T = O(\log n)$,面積相近或略大,但繞線規律性差得多。Booth 重編碼(基數 4 是標準做法)把部分積數目減半到 $n/2$,大致把陣列面積減半,代價是多一級重編碼,以及必須處理 −2× 與 −1× 這兩個倍數。把基數 4 的 Booth 與 Wallace 樹及前綴式最終加法器結合起來,正是商用 DesignWare 乘法器在限制很緊時實際實例化的東西。Wallace 木や Dadda 木は、部分積アレイを $O(n)$ 段のキャリーセーブ行ではなく $O(\log n)$ 段の 3:2 圧縮器で縮約し、面積は同程度かやや大きいまま $T = O(\log n)$ を得る。ただし配線の規則性はかなり悪くなる。Booth 再符号化(基数 4 が標準)は部分積の数を $n/2$ に半減させ、アレイ面積をほぼ半分にする。その代償は再符号化段と、−2× および −1× の倍数を扱う必要が生じることである。基数 4 Booth と Wallace 木とプレフィックス最終加算器の組合せこそ、制約が厳しいときに商用 DesignWare 乗算器が実際にインスタンス化するものである。
None of that is visible from the RTL. What is visible, and what you control, is whether the multiply is combinational at all. A multiplier is the classic candidate for pipelining: insert a register between the partial-product reduction and the final adder and you roughly halve the critical path at the cost of $2n$ flip-flops and one cycle of latency. Writing that yourself means splitting the operation, since a plain c <= a * b; gives you an unpipelined multiplier in front of a register; retiming (set_optimize_registers in Design Compiler) can move registers you have already provided into the middle of the multiplier, but it cannot invent registers you did not write.這些從 RTL 上都看不出來。看得出來、而且由你控制的,是這個乘法到底是不是組合式的。乘法器是管線化的經典候選:在部分積縮減與最終加法器之間插入一級暫存器,就能把關鍵路徑大致減半,代價是 $2n$ 顆正反器與一個週期的延遲。要自己寫出來就得把這個運算拆開,因為單純的 c <= a * b; 給你的是一個未管線化的乘法器接在暫存器前面;retiming(Design Compiler 裡的 set_optimize_registers)可以把你已經提供的暫存器移進乘法器中間,但它無法憑空發明你沒寫的暫存器。以上はどれも RTL からは見えない。見えるもの、そしてあなたが制御できるものは、その乗算がそもそも組合せ回路かどうかである。乗算器はパイプライン化の典型的な候補で、部分積縮約と最終加算器の間にレジスタを 1 段入れればクリティカルパスはほぼ半減し、代償は $2n$ 個のフリップフロップと 1 サイクルのレイテンシである。自分で書くには演算を分割せねばならない。素朴な c <= a * b; はパイプライン化されていない乗算器をレジスタの手前に置くだけだからである。リタイミング(Design Compiler の set_optimize_registers)はすでに与えたレジスタを乗算器の内部へ移動できるが、書かれていないレジスタを発明することはできない。
The moment one operand is a constant, the whole picture changes, and this is the slide's "special cases" bullet. If the constant is a power of two, the multiply is a shift, and a shift by a constant is not logic at all — it is a renaming of wires. $a \times 8$ is the bus {a, 3'b000}. Zero gates, zero delay.只要有一個運算元是常數,整個局面就改變了,這正是投影片「特例」那一條。如果常數是 2 的冪,乘法就是移位,而常數位移量的移位根本不是邏輯 — 它只是替線重新命名。$a \times 8$ 就是匯流排 {a, 3'b000}。零個閘、零延遲。一方のオペランドが定数になった瞬間、全体像は変わる。これがスライドの「特殊ケース」の項目である。定数が 2 のべき乗なら乗算はシフトであり、定数シフトはそもそも論理ではない — 配線の名前の付け替えにすぎない。$a \times 8$ はバス {a, 3'b000} そのものである。ゲート 0、遅延 0。
If the constant is not a power of two, it is a sum of powers of two, and the multiply becomes a small number of shifted adds. $a \times 10 = a \times 8 + a \times 2 = (a \ll 3) + (a \ll 1)$ — one adder, about $6n$ GE, instead of a full multiplier. $a \times 7$ is better written as $a \times 8 - a = (a \ll 3) - a$, one subtractor, than as $(a\ll2)+(a\ll1)+a$, two adders. That substitution of a subtraction for several additions generalises to canonical signed-digit recoding, in which every run of consecutive ones $0111\ldots1$ is rewritten as $100\ldots0\bar{1}$; the number of adders needed is one less than the number of non-zero CSD digits, and CSD guarantees no two adjacent non-zero digits, so an $n$-bit constant needs at most about $n/2$ adders and on average about $n/3$.如果常數不是 2 的冪,它就是若干個 2 的冪之和,於是乘法變成少數幾次位移後的加法。$a \times 10 = a \times 8 + a \times 2 = (a \ll 3) + (a \ll 1)$ — 一個加法器,約 $6n$ GE,而不是一個完整的乘法器。$a \times 7$ 寫成 $a \times 8 - a = (a \ll 3) - a$(一個減法器)比寫成 $(a\ll2)+(a\ll1)+a$(兩個加法器)更好。用一次減法取代數次加法這個替換可推廣為正則有號數字(CSD)重編碼:每一串連續的 1,$0111\ldots1$,都改寫成 $100\ldots0\bar{1}$;所需加法器數目比非零 CSD 數字的個數少一,而 CSD 保證沒有兩個相鄰的非零數字,所以一個 $n$ 位元常數最多約需 $n/2$ 個加法器,平均約 $n/3$。定数が 2 のべき乗でなければ、それは2 のべき乗の和であり、乗算は少数のシフト加算になる。$a \times 10 = a \times 8 + a \times 2 = (a \ll 3) + (a \ll 1)$ — 完全な乗算器ではなく加算器 1 個、約 $6n$ GE である。$a \times 7$ は $(a\ll2)+(a\ll1)+a$(加算器 2 個)より $a \times 8 - a = (a \ll 3) - a$(減算器 1 個)と書くほうがよい。複数の加算を減算 1 回で置き換えるこの手法は正準符号付き数字(CSD)再符号化に一般化される。連続する 1 の並び $0111\ldots1$ を $100\ldots0\bar{1}$ に書き換えるもので、必要な加算器の数は非零の CSD 桁数より 1 少ない。CSD は隣接する非零桁が生じないことを保証するので、$n$ ビットの定数に必要な加算器は多くても約 $n/2$、平均では約 $n/3$ である。
| Expression運算式式 | Naive cost天真的成本素朴なコスト | Decomposition分解分解 | Actual cost實際成本実際のコスト |
|---|---|---|---|
a * 2 | multiplier乘法器乗算器 | {a, 1'b0} | 0 GE |
a * 8 | multiplier乘法器乗算器 | {a, 3'b000} | 0 GE |
a * 9 | multiplier乘法器乗算器 | (a << 3) + a | 1 adder1 個加法器加算器 1 個 |
a * 10 | multiplier乘法器乗算器 | (a << 3) + (a << 1) | 1 adder1 個加法器加算器 1 個 |
a * 7 | multiplier乘法器乗算器 | (a << 3) - a | 1 subtractor1 個減法器減算器 1 個 |
a * 255 | multiplier乘法器乗算器 | (a << 8) - a | 1 subtractor1 個減法器減算器 1 個 |
a * b | multiplier乘法器乗算器 | — | $O(n^2)$ |
Every competent synthesiser performs this recoding automatically when it sees a constant operand, so you do not have to write the shifts yourself. There are two reasons to do it anyway. First, it documents the intent, so that a later editor who changes the constant to a signal knows they have just added two thousand gates. Second, and more importantly, the tool can only do it when it can prove the operand is constant; if the coefficient arrives through a port, through a configuration register or through a parameter the tool cannot resolve, you get the full multiplier. In a digital communication chip this is the usual case for filter coefficients, and it is precisely why fixed-coefficient FIR filters are written as shift-add networks (the "multiplierless" or "multiple constant multiplication" literature) while programmable filters pay for real multipliers.任何像樣的合成器看到常數運算元時都會自動做這種重編碼,所以你不必親手寫出那些移位。但仍有兩個理由去寫。第一,它把意圖寫成文件,讓後來把常數改成訊號的編輯者知道自己剛剛加了兩千個閘。第二,也更重要:工具只有在能證明該運算元是常數時才做得到;如果係數是經由一個埠、一個組態暫存器,或一個工具無法解析的參數進來的,你拿到的就是完整的乘法器。在數位通訊晶片裡,濾波器係數通常正是這種情況,這也正是固定係數 FIR 濾波器會寫成移位加法網路(所謂「multiplierless」或「multiple constant multiplication」文獻)、而可程式化濾波器得付出真正乘法器代價的原因。まともな合成器は定数オペランドを見れば自動でこの再符号化を行うので、シフトを自分で書く必要はない。それでも書く理由が二つある。第一に意図が文書化され、のちに定数を信号に変える編集者が、自分がいま 2000 ゲート追加したことを知れる。第二に、こちらがより重要だが、ツールがそれを行えるのはオペランドが定数だと証明できるときだけである。係数がポート経由、コンフィギュレーションレジスタ経由、あるいはツールが解決できないパラメータ経由で来るなら、完全な乗算器が作られる。ディジタル通信チップではフィルタ係数がまさにこの状況であり、固定係数 FIR フィルタがシフト加算ネットワーク(いわゆる multiplierless、multiple constant multiplication の文献)として書かれ、プログラマブルなフィルタが本物の乗算器の代価を払う理由もここにある。
Division by a variable is the worst operator in the language. A combinational restoring divider for $n$-bit operands is $n$ stages, each containing an $n$-bit subtractor and an $n$-bit multiplexer that selects between the difference and the original partial remainder:用變數做除法是這個語言裡最糟的運算子。$n$ 位元運算元的組合式回復型除法器有 $n$ 級,每一級包含一個 $n$ 位元減法器與一個在差值與原來的部分餘數之間做選擇的 $n$ 位元多工器:変数による除算はこの言語で最悪の演算子である。$n$ ビットオペランドの組合せ引き戻し除算器は $n$ 段からなり、各段には $n$ ビットの減算器と、差と元の部分剰余のどちらかを選ぶ $n$ ビットのマルチプレクサが入る。
Note that unlike the multiplier, the delay is also quadratic, because each stage's subtraction must complete before the next stage can begin and each subtraction is itself $O(n)$ if built from a ripple adder. A 16-bit combinational divider is on the order of 2500 GE with a path of well over a hundred levels; a 32-bit one is unsynthesisable in any practical sense. Many tools simply refuse / with a non-constant divisor and emit an error. Those that accept it produce a block that will never meet timing.注意,與乘法器不同,這裡的延遲也是二次的,因為每一級的減法必須完成,下一級才能開始,而若用漣波加法器來建,每次減法本身又是 $O(n)$。一個 16 位元組合式除法器大約是 2500 GE,路徑遠超過一百層;32 位元的在任何實際意義上都不可合成。許多工具對非常數除數的 / 乾脆拒絕並報錯。接受它的那些工具,會產生一個永遠無法滿足時序的區塊。乗算器と違い遅延も二次であることに注意してほしい。各段の減算が終わらないと次の段が始められず、しかもリップル加算器で作れば各減算自体が $O(n)$ だからである。16 ビットの組合せ除算器はおよそ 2500 GE で経路は優に 100 段を超え、32 ビットのものは実用上あらゆる意味で合成不可能である。多くのツールは除数が定数でない / を単に拒否してエラーを出す。受け付けるツールは、決してタイミングを満たさないブロックを生成する。
The correct engineering response is to not do it. Replace the division with a sequential divider that takes $n$ cycles and one subtractor (about $6n + 3n$ GE total, a hundredth of the combinational area); or with a reciprocal multiplication, where you compute $1/b$ once by table lookup plus Newton–Raphson refinement and then multiply, which is how processors do it; or with a Newton–Raphson or CORDIC iteration in a small sequential block; or, very often in communications, by restructuring the algorithm so the division disappears — scaling both sides of a comparison rather than dividing, or working in the log domain.正確的工程回應是:不要這樣做。改用一個花 $n$ 個週期、只需一個減法器的循序除法器(總共約 $6n + 3n$ GE,是組合式面積的百分之一);或改用倒數乘法 — 先用查表加 Newton–Raphson 修正算出一次 $1/b$,再做乘法,這是處理器的做法;或在一個小的循序區塊裡用 Newton–Raphson 或 CORDIC 迭代;或者 — 在通訊領域中很常見 — 重構演算法讓除法消失,例如把比較式的兩邊同時縮放而不是相除,或改在對數域運算。正しい工学的な対応は、やらないことである。$n$ サイクルと減算器 1 個で済む逐次除算器に置き換える(合計およそ $6n + 3n$ GE で、組合せ版の面積の 100 分の 1)。あるいは逆数乗算に置き換える。$1/b$ をテーブル参照と Newton–Raphson による精密化で一度求めてから乗算するやり方で、プロセッサはこうしている。あるいは小さな逐次ブロックで Newton–Raphson や CORDIC を反復する。あるいは通信分野では非常によくあることだが、アルゴリズムを組み替えて除算そのものを消す。比較の両辺をスケールして割り算を避ける、対数領域で計算する、といった方法である。
Division by a constant power of two is the free case and reduces to a right shift. Division by a general constant is not free but is far cheaper than a divider: $a/c$ can be computed as $(a \times m) \gg s$ for a precomputed magic multiplier $m$ and shift $s$, which costs one constant multiplication. Good synthesisers do this; it is the same transformation a C compiler performs.除以 2 的冪常數是免費的情況,化約成一次右移。除以一般常數不是免費,但遠比除法器便宜:$a/c$ 可以算成 $(a \times m) \gg s$,其中 $m$ 是預先算好的魔術乘數、$s$ 是位移量,成本只是一次常數乘法。好的合成器會這麼做;這和 C 編譯器執行的是同一個變換。2 のべき乗の定数による除算は無料のケースで、右シフトに帰着する。一般の定数による除算は無料ではないが、除算器よりはるかに安い。$a/c$ は、あらかじめ計算したマジック乗数 $m$ とシフト量 $s$ を使って $(a \times m) \gg s$ として計算でき、コストは定数乗算 1 回分である。よい合成器はこれを行う。C コンパイラが行うのと同じ変換である。
The modulo operator % follows the same pattern. By a power of two it is a part select — a % 16 is a[3:0], zero gates, which is the slide's third special case. By a general constant it is a constant division plus a constant multiplication plus a subtraction. By a variable it is a divider, with the divider's cost.取模運算子 % 遵循同樣的模式。對 2 的冪取模是部分選取 — a % 16 就是 a[3:0],零個閘,這是投影片的第三個特例。對一般常數取模,是一次常數除法加一次常數乘法再加一次減法。對變數取模,就是一個除法器,帶著除法器的成本。剰余演算子 % も同じパターンに従う。2 のべき乗に対する剰余は部分選択であり、a % 16 は a[3:0]、ゲート 0 である。これがスライドの三つ目の特殊ケースである。一般の定数に対しては、定数除算と定数乗算と減算の組合せになる。変数に対しては除算器そのものであり、除算器のコストがかかる。
Verilog's / truncates toward zero and % takes the sign of the dividend, exactly as C does. An arithmetic right shift >>> floors toward minus infinity. For negative numbers these disagree:Verilog 的 / 朝零截斷,% 取被除數的符號,和 C 完全一樣。而算術右移 >>> 是朝負無窮大取底。對負數而言兩者並不一致:Verilog の / はゼロ方向に切り捨て、% は被除数の符号を取る。C とまったく同じである。一方、算術右シフト >>> は負の無限大方向に床を取る。負の数ではこの二つは一致しない。
-5 / 4 = -1 // truncate toward zero -5 >>> 2 = -2 // floor -5 % 4 = -1 // sign of dividend (-5) & 3 = 3 // part select gives the unsigned residue
So a / 4 is not a >>> 2 and a % 4 is not a[1:0] when a is signed. The slide's bullets are written for unsigned operands and the exam questions on them are too, but an unexamined substitution in signed code gives an off-by-one that only appears on negative inputs. The correct signed division by $2^k$ adds a bias first: (a + (a[n-1] ? (1<<k)-1 : 0)) >>> k.所以當 a 是有號時,a / 4 不是 a >>> 2,a % 4 也不是 a[1:0]。投影片的條列是針對無號運算元寫的,考題也是,但在有號程式碼裡不假思索地替換,會造成一個只在負輸入時現形的差一錯誤。對 $2^k$ 的正確有號除法要先加偏移:(a + (a[n-1] ? (1<<k)-1 : 0)) >>> k。したがって a が符号付きのとき、a / 4 は a >>> 2 ではなく、a % 4 も a[1:0] ではない。スライドの項目は符号なしオペランドを前提に書かれており、試験問題もそうだが、符号付きのコードで無造作に置き換えると、負の入力のときだけ現れる 1 のずれが生じる。$2^k$ による正しい符号付き除算は、まずバイアスを加える。(a + (a[n-1] ? (1<<k)-1 : 0)) >>> k である。
Problem: a and b are unsigned [7:0]. Rewrite each expression using only shifts, part selects, additions and subtractions, and estimate the gate count. Assume 6 GE per bit of adder.問題:a 與 b 是無號的 [7:0]。請只用移位、部分選取、加法與減法改寫每個運算式,並估算閘數。假設加法器每位元 6 GE。問題:a と b は符号なしの [7:0] とする。各式をシフト、部分選択、加算、減算だけで書き換え、ゲート数を見積もれ。加算器は 1 ビットあたり 6 GE とする。
(a) a * 16 (b) a / 8 (c) a % 32 (d) a * 12 (e) a * 100 (f) (a * 3) / 4 (g) a / b
(a) {a, 4'b0000}, a 12-bit bus. 0 GE.(a) {a, 4'b0000},一條 12 位元匯流排。0 GE。(a) {a, 4'b0000}、12 ビットのバス。0 GE。
(b) a[7:3]. 0 GE.
(c) a[4:0]. 0 GE.
(d) $12 = 8 + 4$, so (a << 3) + (a << 2). The operands are 11 and 10 bits, so an 11-bit adder: about 66 GE. Alternatively $12 = 16 - 4$, (a << 4) - (a << 2), a 12-bit subtractor at about 80 GE — here the sum form is better, which shows that CSD is a heuristic, not a law.(d) $12 = 8 + 4$,所以是 (a << 3) + (a << 2)。運算元是 11 與 10 位元,所以是一個 11 位元加法器:約 66 GE。另一種寫法是 $12 = 16 - 4$,即 (a << 4) - (a << 2),一個 12 位元減法器約 80 GE — 這裡和的形式比較好,顯示 CSD 是一個啟發式方法,不是定律。(d) $12 = 8 + 4$ なので (a << 3) + (a << 2)。オペランドは 11 ビットと 10 ビットなので 11 ビット加算器となり、約 66 GE。別案として $12 = 16 - 4$、すなわち (a << 4) - (a << 2) は 12 ビット減算器で約 80 GE である。ここでは和の形のほうがよく、CSD が法則ではなく発見的手法であることを示している。
(e) $100 = 64 + 32 + 4$, so (a<<6) + (a<<5) + (a<<2): two adders, about 160 GE, three levels if chained or two if written as ((a<<6)+(a<<5)) + (a<<2). In CSD, $100 = 128 - 32 + 4$, also two operations. Either way, far below the ~450 GE of a general 8×8 multiplier.(e) $100 = 64 + 32 + 4$,所以是 (a<<6) + (a<<5) + (a<<2):兩個加法器,約 160 GE;串接寫法是三層,寫成 ((a<<6)+(a<<5)) + (a<<2) 則是兩層。在 CSD 中 $100 = 128 - 32 + 4$,同樣是兩次運算。不論哪種,都遠低於一般 8×8 乘法器的約 450 GE。(e) $100 = 64 + 32 + 4$ なので (a<<6) + (a<<5) + (a<<2)。加算器 2 個、約 160 GE で、鎖状に書けば 3 段、((a<<6)+(a<<5)) + (a<<2) と書けば 2 段である。CSD では $100 = 128 - 32 + 4$ で、やはり演算 2 回。いずれにせよ汎用 8×8 乗算器の約 450 GE をはるかに下回る。
(f) Be careful about the order and the width. a * 3 is (a << 1) + a, a 10-bit value, so use a 10-bit adder (60 GE); then /4 is a part select of that 10-bit intermediate, bits [9:2]. About 60 GE. If you truncate to 8 bits before the divide you lose the top two bits and get the wrong answer for a > 85 — the width trap of section 4.1 again.(f) 要小心順序與位寬。a * 3 是 (a << 1) + a,一個 10 位元的值,所以用 10 位元加法器(60 GE);接著 /4 是對那個 10 位元中間值做部分選取,取 [9:2] 位元。約 60 GE。如果你在除法前先截斷成 8 位元,就會丟掉最高的兩位,在 a > 85 時得到錯誤答案 — 又是 4.1 節的位寬陷阱。(f) 順序と幅に注意すること。a * 3 は (a << 1) + a で 10 ビットの値になるので 10 ビット加算器(60 GE)を使う。続く /4 はその 10 ビット中間値のビット [9:2] の部分選択である。約 60 GE。除算の前に 8 ビットへ切り詰めると上位 2 ビットを失い、a > 85 で誤答になる — 4.1 節の幅の罠がまた現れる。
(g) Not reducible. A combinational 8-bit divider is roughly eight stages of 8-bit subtract-and-select, about 700 GE with a path of sixty-plus levels; most tools will reject it. Build a sequential divider: one 8-bit subtractor, a shift register and a small controller, about 120 GE and eight cycles.(g) 無法化約。一個組合式 8 位元除法器大約是八級的 8 位元減法與選擇,約 700 GE,路徑六十層以上;多數工具會拒絕它。請改建循序除法器:一個 8 位元減法器、一個移位暫存器與一個小控制器,約 120 GE,八個週期。(g) 簡約できない。組合せ 8 ビット除算器は 8 ビットの減算と選択を 8 段重ねたもので約 700 GE、経路は 60 段超になり、多くのツールは拒否する。逐次除算器を作ること。8 ビット減算器 1 個、シフトレジスタ、小さなコントローラで約 120 GE、8 サイクルである。
Slide 6 says two things: the shift amount, or the number of bits storing it, must be a constant; and within a behavioural block shifts are implemented with shift registers and multiplexers built from low-level gates rather than mapped onto a library shift-register cell. Both statements are true but both are easy to misread, and the underlying hardware distinction — between a shift by a constant and a shift by a signal — is one of the sharpest cost cliffs in the whole language.第 6 張投影片講了兩件事:位移量,或是儲存位移量的位元數,必須是常數;以及在行為區塊內,移位是用低階閘建成的移位暫存器與多工器來實現,而不是對應到庫裡的移位暫存器元件。兩句話都正確,但兩句都容易誤讀,而底層的硬體區別 — 常數移位與訊號移位之間的區別 — 是整個語言裡最陡的成本懸崖之一。スライド 6 は二つのことを述べる。シフト量、あるいはそれを格納するビット数は定数でなければならないこと。そして振る舞いブロック内でのシフトは、ライブラリのシフトレジスタセルにマップされるのではなく、低レベルゲートから作られたシフトレジスタとマルチプレクサで実装されること。どちらも正しいが、どちらも読み違えやすい。そして背後にあるハードウェア上の区別 — 定数によるシフトと信号によるシフトの違い — は、この言語全体でもっとも急峻なコストの崖の一つである。
When the shift amount is a literal or an elaboration-time constant, no logic is generated at all. a << 3 is the expression whose bit $i$ is $a_{i-3}$, with zeros below; the synthesiser implements it by connecting the right wires to the right places. There is no gate, no delay beyond the wire itself, and no area beyond routing. The same is true of a >> 3, of part selects, of bit reversals written as a static loop, and of any other fixed permutation of the bits of a bus.當位移量是字面常數或展開期常數時,完全不會產生任何邏輯。a << 3 這個運算式的第 $i$ 位是 $a_{i-3}$,底下補零;合成器實現它的方式就是把對的線接到對的地方。沒有閘、除了線本身沒有延遲、除了繞線沒有面積。a >> 3、部分選取、寫成靜態迴圈的位元反轉,以及任何其他對匯流排位元的固定置換,都是如此。シフト量がリテラルかエラボレーション時定数であれば、論理はまったく生成されない。a << 3 はビット $i$ が $a_{i-3}$ で下位にゼロが入る式であり、合成器は正しい配線を正しい場所につなぐことでこれを実装する。ゲートはなく、配線自体を超える遅延もなく、配線以外の面積もない。a >> 3、部分選択、静的ループとして書かれたビット反転、そしてバスのビットの固定した並べ替えはすべて同様である。
This is why so many of the arithmetic shortcuts in section 4 are described as free. It is also why a constant shift is the right way to express scaling in a fixed-point data path: a Q1.15 value multiplied by a Q1.15 value gives a Q2.30 product, and the renormalisation to Q1.15 is prod[30:15] — a part select, costing nothing, not a division.這就是為什麼第 4 節裡那麼多算術捷徑被描述成免費的。這也是為什麼在定點資料路徑中,用常數移位來表達縮放是正確做法:一個 Q1.15 的值乘以一個 Q1.15 的值得到 Q2.30 的乘積,而正規化回 Q1.15 就是 prod[30:15] — 一次部分選取,不花成本,而不是一次除法。第 4 節の算術的な近道の多くが無料だと説明されるのはこのためである。固定小数点データパスでスケーリングを表すのに定数シフトが正しい方法である理由でもある。Q1.15 の値に Q1.15 の値を掛けると Q2.30 の積になり、Q1.15 への再正規化は prod[30:15] — 除算ではなく部分選択であり、コストはゼロである。
A subtlety: a constant shift that discards bits is free, but a constant shift that widens is free only if the target is wide enough. assign y = a << 3; with y and a both eight bits silently drops the top three bits of a, because the context width is eight. If you wanted the full eleven-bit result the target must be eleven bits wide. Here, as everywhere in Verilog arithmetic, width is decided before structure.一個細節:丟棄位元的常數移位是免費的,但加寬的常數移位只有在目標夠寬時才免費。assign y = a << 3; 若 y 與 a 都是八位元,會悄悄丟掉 a 的最高三位,因為情境位寬是八。如果你要完整的十一位元結果,目標就必須是十一位元寬。在這裡,如同 Verilog 算術的任何地方,位寬永遠在結構之前決定。細かな点が一つある。ビットを捨てる定数シフトは無料だが、広げる定数シフトが無料なのは代入先が十分に広いときだけである。y と a がともに 8 ビットのとき assign y = a << 3; は文脈幅が 8 なので a の上位 3 ビットを黙って落とす。完全な 11 ビットの結果が欲しければ代入先を 11 ビット幅にしなければならない。Verilog の算術ではどこでもそうであるように、ここでも幅は構造より先に決まる。
When the shift amount is a signal, the circuit must be able to produce every possible permutation, and selecting among permutations costs multiplexers. The standard structure is the logarithmic barrel shifter, and its construction follows directly from binary representation of the shift amount: to shift by $s = s_{k-1}\ldots s_1 s_0$, shift conditionally by $2^{k-1}$, then conditionally by $2^{k-2}$, and so on down to conditionally by 1, with each conditional shift controlled by one bit of $s$.當位移量是訊號時,電路必須能產生所有可能的置換,而在置換之間做選擇就要花多工器。標準結構是對數式桶式移位器,它的構造直接來自位移量的二進位表示:要位移 $s = s_{k-1}\ldots s_1 s_0$,就先條件式地移 $2^{k-1}$、再條件式地移 $2^{k-2}$,一路往下到條件式地移 1,每一次條件移位由 $s$ 的一個位元控制。シフト量が信号のとき、回路はあらゆる並べ替えを生成できなければならず、並べ替えの選択にはマルチプレクサが要る。標準的な構造は対数バレルシフタで、その構成はシフト量の 2 進表現から直接導かれる。$s = s_{k-1}\ldots s_1 s_0$ だけシフトするには、まず条件付きで $2^{k-1}$、次に条件付きで $2^{k-2}$、と下りていって最後に条件付きで 1 だけシフトする。各条件シフトは $s$ の 1 ビットが制御する。
Counting is easy once the structure is clear. For an $n$-bit word and a shift amount of $k = \lceil \log_2 n\rceil$ bits there are $k$ stages, each containing $n$ two-to-one multiplexers, so結構一清楚,計數就很容易。對一個 $n$ 位元字與 $k = \lceil \log_2 n\rceil$ 位元的位移量,共有 $k$ 級,每級含 $n$ 顆二對一多工器,所以構造が分かれば数えるのは簡単である。$n$ ビットのワードと $k = \lceil \log_2 n\rceil$ ビットのシフト量に対して段数は $k$、各段に 2 対 1 マルチプレクサが $n$ 個あるので、
For $n = 8$: 24 muxes, about 60 GE, three levels. For $n = 32$: 160 muxes, about 400 GE, five levels. For $n = 64$: 384 muxes, about 960 GE, six levels. Compare the zero gates of a constant shift and you have the cost cliff: changing a >> 3 into a >> s, a one-character edit, turns free wiring into a block the size of two 32-bit adders.$n = 8$ 時:24 顆多工器、約 60 GE、三層。$n = 32$ 時:160 顆多工器、約 400 GE、五層。$n = 64$ 時:384 顆多工器、約 960 GE、六層。對照常數移位的零個閘,成本懸崖就出現了:把 a >> 3 改成 a >> s,一個字元的編輯,就把免費的接線變成兩個 32 位元加法器大小的區塊。$n = 8$ なら 24 個のマルチプレクサ、約 60 GE、3 段。$n = 32$ なら 160 個、約 400 GE、5 段。$n = 64$ なら 384 個、約 960 GE、6 段。定数シフトのゲート数ゼロと比べればコストの崖が見える。a >> 3 を a >> s に変える 1 文字の編集が、無料の配線を 32 ビット加算器 2 個分の大きさのブロックに変えてしまう。
The alternative structure, an $n$-to-1 multiplexer per output bit, needs $n$ multiplexers of $n$ inputs each. A 2:1-mux-based $n$-input mux costs $n-1$ two-to-one muxes, so this arrangement is $n(n-1) = O(n^2)$ — for $n = 32$ that is 992 muxes versus the barrel shifter's 160. It is strictly worse and no tool builds it, but it is a useful thing to be able to dismiss in an exam.另一種結構 — 每個輸出位元用一顆 $n$ 對 1 多工器 — 需要 $n$ 顆各有 $n$ 個輸入的多工器。以 2:1 多工器構成的 $n$ 輸入多工器要花 $n-1$ 顆二對一多工器,所以這個配置是 $n(n-1) = O(n^2)$ — $n = 32$ 時是 992 顆,而桶式移位器只要 160 顆。它嚴格地更差,沒有工具會建它,但能在考試中把它排除掉是很有用的。もう一つの構造、すなわち出力ビットごとに $n$ 対 1 マルチプレクサを置く方式は、$n$ 入力のマルチプレクサを $n$ 個必要とする。2:1 マルチプレクサで作る $n$ 入力マルチプレクサは 2 対 1 を $n-1$ 個使うので、この構成は $n(n-1) = O(n^2)$ となる。$n = 32$ なら 992 個で、バレルシフタの 160 個と比べて厳密に劣る。どのツールもこれを作らないが、試験で却下できるようにしておくと役に立つ。
Read literally, slide 6 would forbid the barrel shifter entirely, which cannot be right — barrel shifters are in every processor and every floating-point normaliser. The bullet is a compressed statement of an older and narrower restriction, and it is worth getting the modern reading right.照字面讀,第 6 張投影片會完全禁止桶式移位器,那不可能對 — 每顆處理器、每個浮點正規化器裡都有桶式移位器。這一條是一個更舊、更窄的限制被壓縮後的說法,值得把現代的讀法弄清楚。文字どおり読めばスライド 6 はバレルシフタを全面的に禁じることになるが、それは正しくありえない。バレルシフタはあらゆるプロセッサ、あらゆる浮動小数点正規化器に入っている。この項目は、より古くより狭い制限を圧縮した言明であり、現代的な読み方を正しく押さえておく価値がある。
What the rule actually requires is that the width of the shift amount be fixed at elaboration — "the shift amount or the number of bits storing the shift amount must be a constant". The tool must know how many stages to build. It cannot build a shifter whose control bus has a data-dependent width, for the same reason it cannot unroll a data-dependent loop.這條規則真正要求的是位移量的位寬必須在展開期固定 —「位移量或儲存位移量的位元數必須是常數」。工具必須知道要建幾級。它無法建一個控制匯流排寬度隨資料而變的移位器,理由和它無法展開隨資料而變的迴圈相同。この規則が実際に要求しているのは、シフト量の幅がエラボレーション時に固定されていることである —「シフト量、またはシフト量を格納するビット数が定数でなければならない」。ツールは何段作ればよいかを知る必要がある。制御バスの幅がデータに依存するシフタを作れないのは、データ依存のループを展開できないのと同じ理由である。
The value may vary at run time. assign y = a >> s; with wire [2:0] s; is perfectly synthesisable and gives Figure 2. Some early-1990s tools did require a literal constant, which is where the bullet comes from; every tool in use today builds the barrel shifter. What you should take from the slide is the cost message underneath it: if you can make the shift amount constant, do, because then it costs nothing.值可以在執行時變動。assign y = a >> s; 搭配 wire [2:0] s; 完全可合成,得到的就是圖 2。1990 年代初期的一些工具確實要求字面常數,那就是這一條的由來;今天在用的每一個工具都會建出桶式移位器。你該從投影片帶走的是它底下的成本訊息:如果你能把位移量變成常數,就這麼做,因為那樣它什麼都不花。値は実行時に変わってよい。wire [2:0] s; とともに書いた assign y = a >> s; は完全に合成可能で、図 2 が得られる。1990 年代初期の一部のツールはリテラル定数を要求し、この項目の出どころはそこにある。今日使われているツールはどれもバレルシフタを作る。スライドから持ち帰るべきは、その下にあるコストのメッセージである。シフト量を定数にできるならそうせよ、そうすればコストはゼロになる。
One real restriction does survive: the shift amount is a self-determined expression, so its width is its own and it is always treated as unsigned. A negative shift amount is therefore not a shift in the other direction; it is an enormous positive shift that zeroes the result. If you need a bidirectional shift, build two shifters and a multiplexer, or reverse the bus, shift right and reverse back — reversal being free wiring, this costs one extra $n$-bit mux rather than a second shifter.有一條真正的限制確實留了下來:位移量是一個自決定運算式,所以它的位寬是它自己的,而且永遠被當成無號。因此負的位移量不是反方向的移位,而是一個巨大的正位移,會把結果清成零。如果你需要雙向移位,就建兩個移位器加一顆多工器,或者把匯流排反轉、右移、再反轉回來 — 反轉只是免費的接線,所以這只多花一顆 $n$ 位元多工器,而不是第二個移位器。本当の制限が一つだけ残っている。シフト量は自己決定の式なので幅は自身のものであり、常に符号なしとして扱われる。したがって負のシフト量は逆方向のシフトではなく、結果をゼロにする巨大な正のシフトである。双方向シフトが必要なら、シフタを 2 つとマルチプレクサを作るか、バスを反転して右シフトし再び反転する。反転は無料の配線なので、後者の追加コストは 2 つ目のシフタではなく $n$ ビットのマルチプレクサ 1 個で済む。
IEEE 1364-2001 added <<< and >>>. The left arithmetic shift <<< is identical to <<. The right arithmetic shift >>> fills from the sign bit if and only if the left operand is signed; on an unsigned operand it fills with zeros and is identical to >>. That conditional is a frequent source of bugs, because the signedness of the operand may come from a declaration several lines away, or may have been destroyed by mixing with an unsigned operand as described in section 2.3.IEEE 1364-2001 加入了 <<< 與 >>>。算術左移 <<< 與 << 完全相同。算術右移 >>> 若且唯若左運算元為有號時才以符號位元填補;對無號運算元它填零,與 >> 完全相同。這個條件是錯誤的常見來源,因為運算元的有號性可能來自好幾行之外的宣告,也可能像 2.3 節描述的那樣,因為與無號運算元混用而被破壞掉了。IEEE 1364-2001 は <<< と >>> を追加した。算術左シフト <<< は << と同一である。算術右シフト >>> が符号ビットで埋めるのは左オペランドが符号付きである場合に限る。符号なしオペランドに対してはゼロで埋め、>> と同一になる。この条件はバグの頻出源である。オペランドの符号属性は数行離れた宣言に由来することもあれば、2.3 節で述べたように符号なしオペランドとの混在によって失われていることもあるからである。
In hardware the difference is nothing at all: both are wiring for a constant amount, and for a variable amount both are the same barrel shifter with a different choice of fill. The sign-extending fill costs no extra gates, only a different wire to the vacated mux inputs. So there is never a performance reason to prefer one; there is only a correctness reason to use the one that matches your number format.在硬體上兩者毫無差別:常數位移量時兩者都只是接線,變數位移量時兩者都是同一個桶式移位器,只差填補來源不同。符號擴展的填補不花額外的閘,只是把不同的線接到空出來的多工器輸入。所以永遠沒有效能上的理由去偏好哪一個;只有正確性上的理由 — 要用與你的數值格式相符的那一個。ハードウェア上の差はまったくない。定数シフト量ならどちらも配線であり、変数シフト量ならどちらも同じバレルシフタで埋める値の選び方が違うだけである。符号拡張の埋め込みに追加のゲートはかからず、空いたマルチプレクサ入力に別の配線をつなぐだけである。したがって一方を選ぶ性能上の理由は決してない。あるのは、自分の数値形式に合うほうを使うという正しさの理由だけである。
The second half of slide 6 — that shifts inside a behavioural block become "shift registers and multiplexers ... implemented from low-level logic gates, and not necessarily mapped into a library cell of a shift register" — is about a different structure altogether, and the lecture's data_shift example illustrates it.第 6 張投影片的後半 — 行為區塊內的移位會變成「shift registers and multiplexers ... implemented from low-level logic gates, and not necessarily mapped into a library cell of a shift register」— 講的完全是另一種結構,而這一講的 data_shift 範例正好說明了它。スライド 6 の後半 — 振る舞いブロック内のシフトは「shift registers and multiplexers ... implemented from low-level logic gates, and not necessarily mapped into a library cell of a shift register」になる — はまったく別の構造についての話であり、講義の data_shift の例がそれを示している。
module data_shift (data_out, data_in, clk, reset, data_load, SL, SR);
input [3:0] data_in;
input clk, reset, data_load, SL, SR;
output [3:0] data_out;
reg [3:0] data_out;
always @ (posedge clk or negedge reset)
if (reset == 0) data_out = 0; else
if (data_load) data_out = data_in; else
if (SL) data_out = data_out << 1; else
if (SR) data_out = data_out >> 1;
endmodule
| Control控制制御 | Action動作動作 | Priority優先權優先順位 |
|---|---|---|
reset = 0 | Clear to zero, asynchronously非同步清為零非同期にゼロクリア | Highest (it is in the event control)最高(它在事件控制裡)最高(イベント制御に含まれるため) |
data_load = 1 | Load data_in載入 data_indata_in をロード | First synchronous branch第一個同步分支最初の同期分岐 |
SL = 1 | Shift left by one, zero fill左移一位,補零1 ビット左シフト、ゼロ埋め | Second第二2 番目 |
SR = 1 | Shift right by one, zero fill右移一位,補零1 ビット右シフト、ゼロ埋め | Third第三3 番目 |
| none asserted全都未設定どれも立っていない | Hold保持ホールド | Implicit else隱含的 else暗黙の else |
Each << 1 here is a shift by a literal, so it contributes no logic; what costs area is the selection among the five possible next states. The slide's hand-drawn structure shows the result for the least significant bit: three 2:1 multiplexers in a priority chain, with data_load nearest the flip-flop because it has the highest synchronous priority, then SL, then SR, and the hold path threaded back from the flip-flop output. The four possible data sources for bit 0 are data_in[0] (load), a hard zero (shift left brings in a zero at the bottom), data_out[1] (shift right) and data_out[0] itself (hold).這裡每一個 << 1 都是對字面常數的移位,所以不產生任何邏輯;花面積的是在五種可能的下一狀態之間做選擇。投影片手繪的結構顯示最低有效位元的結果:三顆 2:1 多工器排成優先權鏈,data_load 最靠近正反器因為它的同步優先權最高,接著是 SL,再來是 SR,而保持路徑則從正反器輸出繞回來。第 0 位元的四個可能資料來源是 data_in[0](載入)、一個硬零(左移時底部補零)、data_out[1](右移),以及 data_out[0] 自己(保持)。ここでの各 << 1 はリテラルによるシフトなので論理を生まない。面積を食うのは 5 通りの次状態のあいだの選択である。スライドの手描きの構造は最下位ビットの結果を示している。3 個の 2:1 マルチプレクサが優先順位の鎖をなし、同期分岐で最優先の data_load がフリップフロップにもっとも近く、次が SL、その次が SR、そしてホールドの経路がフリップフロップ出力から戻されている。ビット 0 の 4 つのデータ源は data_in[0](ロード)、固定のゼロ(左シフトで下位に入るゼロ)、data_out[1](右シフト)、そして data_out[0] 自身(ホールド)である。
Per bit the cost is three 2:1 muxes at roughly 2.5 GE each plus one flip-flop with asynchronous reset at roughly 6 GE, so about 13.5 GE per bit and 54 GE for the four-bit unit. The slide's synthesis result shows this covered not as discrete muxes but as AND-OR-INVERT cells feeding the D pins — the function is a four-input select, and an AOI22 plus an inverter covers it in fewer transistors than two separate mux cells. That substitution is exactly what the bullet means by "implemented from low-level logic gates, and not necessarily mapped into a library cell of a shift register".每位元的成本是三顆 2:1 多工器(每顆約 2.5 GE)加一顆帶非同步重置的正反器(約 6 GE),所以每位元約 13.5 GE,四位元單元共約 54 GE。投影片的合成結果顯示它不是被覆蓋成一顆顆獨立的多工器,而是被覆蓋成餵進 D 腳的 AND-OR-INVERT 元件 — 這個函式是一個四輸入選擇,而一顆 AOI22 加一顆反相器覆蓋它所用的電晶體比兩顆獨立的多工器元件更少。那個替換正是這一條所說的「implemented from low-level logic gates, and not necessarily mapped into a library cell of a shift register」。1 ビットあたりのコストは 2:1 マルチプレクサ 3 個(各約 2.5 GE)と非同期リセット付きフリップフロップ 1 個(約 6 GE)で、約 13.5 GE、4 ビットのユニットで約 54 GE となる。スライドの合成結果は、これが個別のマルチプレクサではなく D ピンを駆動する AND-OR-INVERT セルとして覆われることを示している。この関数は 4 入力の選択であり、AOI22 1 個とインバータ 1 個のほうが、独立したマルチプレクサ 2 個よりトランジスタ数が少なくて済むからである。この置き換えこそ、項目が言う「implemented from low-level logic gates, and not necessarily mapped into a library cell of a shift register」の意味である。
Many libraries do contain multi-bit or scan-chained flip-flop cells. The tool will not use one here because this register is not a pure shift register: it must also load, hold and clear. A dedicated shift-register cell has no load path and no hold enable, so the multiplexing would have to be rebuilt outside it anyway, gaining nothing. Dedicated cells get used for long, unconditional delay lines — the taps of a matched filter, say — which is precisely the structure that does occur a lot in a digital communication receiver.許多庫確實含有多位元或帶掃描鏈的正反器元件。工具在這裡不會用它,因為這個暫存器不是純粹的移位暫存器:它還必須載入、保持與清除。專用的移位暫存器元件沒有載入路徑也沒有保持致能,所以多工邏輯反正還是得在它外面重建,什麼也沒賺到。專用元件會被用在長的、無條件的延遲線上 — 比方說匹配濾波器的抽頭 — 而那恰好是數位通訊接收機裡大量出現的結構。多くのライブラリには多ビットやスキャンチェーン付きのフリップフロップセルが実際に存在する。ここでツールがそれを使わないのは、このレジスタが純粋なシフトレジスタではないからである。ロードもホールドもクリアも行わねばならない。専用のシフトレジスタセルにはロード経路もホールドイネーブルもないので、結局マルチプレクサをその外側に作り直すことになり、何の得もない。専用セルが使われるのは、長く無条件の遅延線 — たとえば整合フィルタのタップ — であり、それはまさにディジタル通信受信機で大量に現れる構造である。
Finally, note the use of blocking assignments in a clocked block. The lecture's own example uses =, and because each branch assigns data_out exactly once and the right-hand side reads the pre-edge value (there is no earlier write to data_out in the same pass), the inferred hardware is correct. But it is a race against any other block that reads data_out at the same edge, for the reasons set out in section 8. Write <= in clocked blocks; the lecture's example is from a textbook of the mid-1990s, when the convention was not yet universal.最後,請注意在時脈區塊裡使用了阻塞式指定。這一講自己的範例用的是 =,而因為每個分支都恰好指定 data_out 一次,且右手邊讀到的是邊緣前的值(同一次執行中沒有更早對 data_out 的寫入),所以推論出來的硬體是正確的。但基於第 8 節闡述的理由,它對任何在同一個邊緣讀取 data_out 的其他區塊而言是一場競爭。請在時脈區塊裡寫 <=;這一講的範例出自 1990 年代中期的教科書,當時這個慣例還不普及。最後に、クロックブロック内でブロッキング代入が使われている点に注意してほしい。講義自身の例は = を使っており、各分岐が data_out をちょうど一度代入し、右辺がエッジ前の値を読む(同じパス内に data_out への先行する書き込みがない)ため、推論されるハードウェアは正しい。しかし第 8 節で述べる理由により、同じエッジで data_out を読む他のブロックに対しては競合になる。クロックブロックでは <= を書くこと。講義の例は 1990 年代半ばの教科書のもので、当時この慣習はまだ普遍的ではなかった。
Problem: Give the multiplexer count and the number of logic levels for each, and state which are free.問題:請給出每一項的多工器數量與邏輯層數,並指出哪些是免費的。問題:それぞれについてマルチプレクサの個数と論理段数を示し、どれが無料かを述べよ。
(a) wire [15:0] y = a >> 4; // a is [15:0] (b) wire [15:0] y = a >> s; // s is [3:0] (c) wire [31:0] y = a << s; // a is [31:0], s is [4:0] (d) wire [31:0] y = dir ? (a << s) : (a >> s); (e) wire [15:0] y = a >> (2*k); // k is a parameter (f) wire [15:0] y = {a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7], a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]};
(a) Free. Constant shift: y = {4'b0000, a[15:4]}. Zero muxes, zero levels.(a) 免費。常數移位:y = {4'b0000, a[15:4]}。零顆多工器、零層。(a) 無料。定数シフト:y = {4'b0000, a[15:4]}。マルチプレクサ 0 個、0 段。
(b) $n = 16$, $k = 4$, so four stages of sixteen muxes: 64 muxes, about 160 GE, four levels.(b) $n = 16$、$k = 4$,所以是四級、每級十六顆多工器:64 顆多工器,約 160 GE,四層。(b) $n = 16$、$k = 4$ なので 16 個のマルチプレクサからなる段が 4 つ。マルチプレクサ 64 個、約 160 GE、4 段。
(c) $n = 32$, $k = 5$: 160 muxes, about 400 GE, five levels.(c) $n = 32$、$k = 5$:160 顆多工器,約 400 GE,五層。(c) $n = 32$、$k = 5$:マルチプレクサ 160 個、約 400 GE、5 段。
(d) Naively two barrel shifters (320 muxes) plus a 32-bit output mux (32 muxes) = 352 muxes, six levels. Better: reverse the bus before and after a single right shifter, since reversal is free wiring. That gives one shifter (160) plus two 32-bit reversal muxes (64) = 224 muxes and seven levels — a 36 % area saving for one extra level. Which you want depends on whether the path is critical.(d) 天真的做法是兩個桶式移位器(320 顆多工器)加一顆 32 位元輸出多工器(32 顆)= 352 顆多工器、六層。更好的做法:在單一右移器前後把匯流排反轉,因為反轉是免費的接線。那樣是一個移位器(160)加兩組 32 位元反轉多工器(64)= 224 顆多工器、七層 — 多一層換來 36 % 的面積節省。要哪一種,取決於這條路徑是不是關鍵路徑。(d) 素朴にはバレルシフタ 2 個(320 個のマルチプレクサ)と 32 ビットの出力マルチプレクサ(32 個)で合計 352 個、6 段となる。よりよい方法は、右シフタ 1 個の前後でバスを反転することである。反転は無料の配線だからである。これならシフタ 1 個(160)と 32 ビットの反転用マルチプレクサ 2 組(64)で 224 個、7 段 — 1 段増やして面積を 36 % 節約できる。どちらを選ぶかは、その経路がクリティカルかどうかによる。
(e) Free. k is a parameter, so 2*k is resolved at elaboration and the shift is constant. This is the pattern to aim for: make the shift amount a parameter whenever the algorithm allows it.(e) 免費。k 是參數,所以 2*k 在展開期就被解析掉,移位是常數的。這是該追求的樣式:只要演算法允許,就把位移量做成參數。(e) 無料。k はパラメータなので 2*k はエラボレーション時に解決され、シフトは定数になる。これが目指すべきパターンである。アルゴリズムが許す限り、シフト量はパラメータにすること。
(f) Free. A bit reversal is a permutation of wires, exactly like a constant shift. It is worth recognising because a reversal written as a static for loop (section 10.2) is equally free, and because the reversal trick in (d) depends on it.(f) 免費。位元反轉是線的置換,和常數移位完全一樣。值得認出來,因為寫成靜態 for 迴圈的反轉(10.2 節)同樣免費,而且 (d) 裡的反轉技巧正是靠它。(f) 無料。ビット反転は配線の並べ替えであり、定数シフトとまったく同じである。認識しておく価値がある。静的な for ループとして書いた反転(10.2 節)も同じく無料であり、(d) の反転の工夫もこれに依拠しているからである。
Slides 10 through 17 sweep through relational operators, identity operators, reduction, bitwise and logical operators, the conditional operator, concatenation and the grouping of operators, mostly in a line each. The underlying message of all of them is the same — "translated to Boolean equations and mapped to physical gates" — but the cost of those equations differs by orders of magnitude between classes, and that is what this section quantifies.第 10 到 17 張投影片橫掃關係運算子、同一性運算子、歸約、位元與邏輯運算子、條件運算子、串接,以及運算子的分組,大多一行一個。它們底下的訊息都一樣 —「translated to Boolean equations and mapped to physical gates」— 但這些方程式的成本在不同類別之間相差好幾個數量級,而這正是本節要量化的東西。スライド 10 から 17 は、関係演算子、同一性演算子、リダクション、ビット演算子と論理演算子、条件演算子、連結、そして演算子のグループ化を、ほとんど 1 行ずつで駆け抜ける。その底にあるメッセージはどれも同じ —「translated to Boolean equations and mapped to physical gates」— だが、それらの式のコストはクラス間で桁違いに異なる。本節が定量化するのはそこである。
Slide 10 gives two implementations for <, >, <= and >=: dedicated combinational logic, or an adder/subtractor whose sign bit is examined. Both are used in practice.第 10 張投影片給了 <、>、<= 與 >= 兩種實現:專用的組合邏輯,或是檢視加法器/減法器的符號位元。兩種在實務上都會用到。スライド 10 は <、>、<=、>= に二つの実装を与える。専用の組合せ論理か、加算器/減算器の符号ビットを見る方法である。どちらも実務で使われる。
The dedicated comparator is a prefix structure much like a carry chain. Working from the most significant bit down, $A > B$ holds if at the highest differing bit position $A$ has a 1 and $B$ a 0. Defining per-bit "greater" $g_i = a_i\overline{b_i}$ and "equal" $e_i = \overline{a_i \oplus b_i}$, the result is $\bigvee_i \bigl(g_i \wedge \bigwedge_{j>i} e_j\bigr)$, which is a prefix-AND of the equal terms combined with the greater terms. Built as a tree this costs about $3n$ to $4n$ GE with $O(\log n)$ depth; built as a ripple it costs about $2n$ GE with $O(n)$ depth. A magnitude comparator is therefore roughly half an adder.專用比較器是一個很像進位鏈的前綴結構。從最高位往下看,$A > B$ 成立的條件是在最高的相異位元位置上 $A$ 為 1 而 $B$ 為 0。定義逐位元的「大於」$g_i = a_i\overline{b_i}$ 與「相等」$e_i = \overline{a_i \oplus b_i}$,結果就是 $\bigvee_i \bigl(g_i \wedge \bigwedge_{j>i} e_j\bigr)$,也就是把相等項的前綴 AND 與大於項組合起來。建成樹狀約花 $3n$ 到 $4n$ GE、深度 $O(\log n)$;建成漣波狀約花 $2n$ GE、深度 $O(n)$。所以一個大小比較器大約是半個加法器。専用比較器はキャリーチェーンによく似たプレフィックス構造である。最上位ビットから下りていくと、$A > B$ が成り立つのは最上位の相違ビット位置で $A$ が 1、$B$ が 0 のときである。ビットごとの「大きい」$g_i = a_i\overline{b_i}$ と「等しい」$e_i = \overline{a_i \oplus b_i}$ を定義すると、結果は $\bigvee_i \bigl(g_i \wedge \bigwedge_{j>i} e_j\bigr)$ であり、等価項のプレフィックス AND と大小項を組み合わせたものになる。木として作れば約 $3n$〜$4n$ GE で深さ $O(\log n)$、リップルとして作れば約 $2n$ GE で深さ $O(n)$ である。したがって大小比較器はおよそ加算器の半分である。
The subtract-and-test-the-sign implementation reuses an adder that the design may already contain, which is why an ALU that already has an adder gets its comparisons nearly free: feed a - b through the existing adder and read one bit. The slide states the criterion as "$A \ge B$ if the sign bit of $A-B$ is 0, and $A < B$ otherwise". That is right for a particular case and wrong in general, and the correction is worth knowing because it is a standard exam trap.相減再看符號的實現方式,重用了設計中可能已經有的加法器,這就是為什麼已經有加法器的 ALU 幾乎可以免費得到比較功能:把 a - b 餵進既有的加法器,再讀一個位元就好。投影片把判準寫成「若 $A-B$ 的符號位元為 0 則 $A \ge B$,否則 $A < B$」。那對某個特定情況是對的,一般情況則是錯的,而這個更正值得知道,因為它是標準的考試陷阱。引き算して符号を見る実装は、設計がすでに持っているかもしれない加算器を再利用する。すでに加算器のある ALU が比較をほぼ無料で得られるのはこのためである。a - b を既存の加算器に通し、1 ビット読むだけでよい。スライドは判定条件を「$A-B$ の符号ビットが 0 なら $A \ge B$、そうでなければ $A < B$」と述べる。これは特定の場合には正しく、一般には誤りである。この訂正は標準的な試験の罠なので知っておく価値がある。
Unsigned operands. Compute $A - B$ in $n+1$ bits, that is $A + \overline{B} + 1$ with the carry retained. Then $A \ge B$ exactly when the carry out is 1 (no borrow). Looking at bit $n-1$ of an $n$-bit difference is simply the wrong bit; in four bits, $A = 4'd2$, $B = 4'd9$ gives $A-B = 4'd9$, whose MSB is 1, which happens to signal $A < B$ correctly — but $A = 4'd8$, $B = 4'd1$ gives $4'd7$, MSB 0, also correct, while $A = 4'd0$, $B = 4'd8$ gives $4'd8$, MSB 1, correct again. The MSB of the truncated difference is only a reliable indicator once you realise it is really the borrow in disguise for these cases; the clean and always-correct statement is the carry-out rule.無號運算元。以 $n+1$ 位元計算 $A - B$,也就是保留進位的 $A + \overline{B} + 1$。那麼 $A \ge B$ 的充要條件就是進位輸出為 1(沒有借位)。去看 $n$ 位元差值的第 $n-1$ 位根本是看錯位元;以四位元為例,$A = 4'd2$、$B = 4'd9$ 得 $A-B = 4'd9$,其 MSB 為 1,剛好正確地指出 $A < B$ — 但 $A = 4'd8$、$B = 4'd1$ 得 $4'd7$,MSB 為 0,也正確;而 $A = 4'd0$、$B = 4'd8$ 得 $4'd8$,MSB 為 1,又正確。截斷後差值的 MSB 之所以在這些情況下是可靠的指標,只因為你意識到它其實是偽裝過的借位;乾淨而永遠正確的陳述是進位輸出規則。符号なしオペランド。$A - B$ を $n+1$ ビットで、すなわちキャリーを残した $A + \overline{B} + 1$ として計算する。すると $A \ge B$ はキャリー出力が 1(借りが発生しない)であることとちょうど同値になる。$n$ ビットの差のビット $n-1$ を見るのは、単に見るビットが違う。4 ビットの例で、$A = 4'd2$、$B = 4'd9$ なら $A-B = 4'd9$ で MSB は 1、たまたま $A < B$ を正しく示す。$A = 4'd8$、$B = 4'd1$ なら $4'd7$ で MSB は 0、これも正しい。$A = 4'd0$、$B = 4'd8$ なら $4'd8$ で MSB は 1、やはり正しい。切り詰めた差の MSB が信頼できる指標なのは、それが実は姿を変えた借りだと気づいた場合に限られる。きれいで常に正しい言明はキャリー出力の規則である。
Signed operands. The sign bit $s$ of the $n$-bit difference is correct only when the subtraction does not overflow. The always-correct test is $A \ge B \iff (s \oplus v) = 0$, where $v$ is the signed overflow flag $v = c_{n} \oplus c_{n-1}$. Example in four bits: $A = -8$ ($1000$), $B = 1$ ($0001$). $A - B = -9$, which does not fit, and the four-bit result is $0111 = +7$ with $s = 0$, suggesting $A \ge B$ — wrong. The overflow bit $v = 1$ corrects it. Every processor's condition-code logic implements the XOR; the slide omits it.有號運算元。$n$ 位元差值的符號位元 $s$ 只有在減法沒有溢位時才正確。永遠正確的判準是 $A \ge B \iff (s \oplus v) = 0$,其中 $v$ 是有號溢位旗標 $v = c_{n} \oplus c_{n-1}$。四位元的例子:$A = -8$($1000$)、$B = 1$($0001$)。$A - B = -9$,裝不下,四位元結果是 $0111 = +7$,$s = 0$,暗示 $A \ge B$ — 錯了。溢位位元 $v = 1$ 把它更正過來。每顆處理器的條件碼邏輯都實作了這個 XOR;投影片把它略掉了。符号付きオペランド。$n$ ビットの差の符号ビット $s$ が正しいのは、減算がオーバーフローしないときだけである。常に正しい判定は $A \ge B \iff (s \oplus v) = 0$ で、$v$ は符号付きオーバーフローフラグ $v = c_{n} \oplus c_{n-1}$ である。4 ビットの例:$A = -8$($1000$)、$B = 1$($0001$)。$A - B = -9$ は収まらず、4 ビットの結果は $0111 = +7$、$s = 0$ となって $A \ge B$ を示唆する — 誤りである。オーバーフロービット $v = 1$ がこれを訂正する。どのプロセッサの条件コード論理もこの XOR を実装している。スライドはそれを省いている。
A useful special case: comparison against a constant is much cheaper than comparison against a signal, because the per-bit $g$ and $e$ terms collapse. a > 8'd0 is simply the OR-reduction of a, about 7 GE and three levels for eight bits, not a comparator. a >= 8'd128 is just a[7], free. Whenever a threshold in a communication design can be made a power of two, make it one.一個有用的特例:與常數比較比與訊號比較便宜得多,因為逐位元的 $g$ 與 $e$ 項會塌縮。a > 8'd0 不過是對 a 做 OR 歸約,八位元約 7 GE、三層,而不是一個比較器。a >= 8'd128 就只是 a[7],免費。在通訊設計中,只要門檻值能設成 2 的冪,就把它設成 2 的冪。有用な特殊ケース:定数との比較は信号との比較よりはるかに安い。ビットごとの $g$ と $e$ の項が縮退するからである。a > 8'd0 は単に a の OR リダクションで、8 ビットなら約 7 GE、3 段であり、比較器ではない。a >= 8'd128 はただの a[7] で無料である。通信設計でしきい値を 2 のべき乗にできるなら、常にそうすること。
The logical equality operators == and != are the cheapest comparison in the language. Equality of two $n$-bit vectors is "no bit differs":邏輯相等運算子 == 與 != 是這個語言裡最便宜的比較。兩個 $n$ 位元向量相等就是「沒有任何位元不同」:論理等価演算子 == と != は、この言語でもっとも安い比較である。2 つの $n$ ビットベクタが等しいとは「どのビットも相違しない」ことである。
That is $n$ XOR gates feeding an $n$-input NOR, so about $2n$ GE for the XORs and $n-1$ two-input gates for the reduction tree — roughly $3n$ GE with $1 + \lceil\log_2 n\rceil$ levels. The lecture's identity_4bit example is exactly this: four XORs into a four-input NOR gives A_noteq_B inverted, and one more inverter gives A_eq_B. About 12 GE for four bits, three levels. Note that the tool produces one comparator and inverts it, rather than two independent cones — common sub-expression elimination at work, and visible in the slide's schematic as the single NOR with a buffer and an inverter on its output.也就是 $n$ 顆 XOR 閘餵進一個 $n$ 輸入 NOR,所以 XOR 約 $2n$ GE,歸約樹用 $n-1$ 顆二輸入閘 — 大約 $3n$ GE,$1 + \lceil\log_2 n\rceil$ 層。這一講的 identity_4bit 範例正是如此:四顆 XOR 進入一個四輸入 NOR 得到反相的 A_noteq_B,再加一顆反相器得到 A_eq_B。四位元約 12 GE、三層。請注意工具產生的是一個比較器再取反,而不是兩個獨立的邏輯錐 — 這是共同子運算式消除在運作,在投影片的電路圖上就是那個輸出端接了一顆緩衝器與一顆反相器的單一 NOR。すなわち $n$ 個の XOR ゲートが $n$ 入力 NOR を駆動する形で、XOR に約 $2n$ GE、リダクションツリーに 2 入力ゲート $n-1$ 個 — 合わせておよそ $3n$ GE、段数は $1 + \lceil\log_2 n\rceil$ である。講義の identity_4bit の例はまさにこれで、XOR 4 個が 4 入力 NOR に入って反転した A_noteq_B を与え、インバータをもう 1 個通すと A_eq_B になる。4 ビットで約 12 GE、3 段である。ツールが 2 つの独立したコーンではなく一つの比較器を作って反転している点に注目してほしい。共通部分式除去が働いており、スライドの回路図では出力にバッファとインバータが付いた単一の NOR として見える。
Equality against a constant is cheaper still: it is an AND of selected bits and their complements, about $n$ GE with $\lceil \log_2 n\rceil$ levels, which is why decoding an opcode is inexpensive and comparing two data words is not.與常數比較相等更便宜:它只是選定位元與其補數的 AND,約 $n$ GE、$\lceil \log_2 n\rceil$ 層,這就是為什麼解碼一個 opcode 很便宜,而比較兩個資料字則不然。定数との等価比較はさらに安い。選ばれたビットとその補数の AND であり、約 $n$ GE、$\lceil \log_2 n\rceil$ 段である。オペコードのデコードが安く、2 つのデータワードの比較がそうでない理由はここにある。
The case-equality operators === and !== were dealt with in section 3.3: they are simulation-only. Slide 10 lists them under "implemented with simple combinational logics", which is the one place in this lecture where the slide is, strictly, incorrect.case 相等運算子 === 與 !== 在 3.3 節已經處理過:它們只能用於模擬。第 10 張投影片把它們列在「implemented with simple combinational logics」底下,那是這一講裡投影片嚴格來說唯一不正確的地方。ケース等価演算子 === と !== は 3.3 節で扱った。これらはシミュレーション専用である。スライド 10 はこれらを「implemented with simple combinational logics」の下に挙げているが、そこがこの講義でスライドが厳密に誤っている唯一の箇所である。
Nothing could be simpler. &, |, ^, ~, ~&, ~|, ~^ applied to two $n$-bit vectors produce $n$ independent two-input gates with no interaction between bit positions. Area is exactly $n$ gates — about $n$ GE for AND/OR/NAND/NOR, about $2n$ to $2.5n$ for XOR/XNOR — and delay is exactly one level regardless of $n$. These are the only operators in the language whose delay does not grow with word width.沒有比這更簡單的了。&、|、^、~、~&、~|、~^ 作用在兩個 $n$ 位元向量上,產生 $n$ 顆彼此獨立的二輸入閘,位元位置之間沒有任何互動。面積恰好是 $n$ 顆閘 — AND/OR/NAND/NOR 約 $n$ GE,XOR/XNOR 約 $2n$ 到 $2.5n$ — 而延遲恰好是一層,不論 $n$ 多大。這是語言裡唯一一類延遲不隨字寬成長的運算子。これ以上単純なものはない。2 つの $n$ ビットベクタに適用された &、|、^、~、~&、~|、~^ は、ビット位置間に相互作用のない独立した 2 入力ゲートを $n$ 個生む。面積はちょうど $n$ ゲート分 — AND/OR/NAND/NOR で約 $n$ GE、XOR/XNOR で約 $2n$〜$2.5n$ — であり、遅延は $n$ によらずちょうど 1 段である。遅延がワード幅とともに増えない唯一の演算子クラスである。
The one thing to watch is width mismatch. Bitwise operators are context-determined, so a narrow operand is extended to the context width, and the extension is with zeros for an unsigned operand and with the sign for a signed one. wire [7:0] y = a & 1'b1; does not mask to one bit; it extends 1'b1 to 8'b00000001 and gives {7'b0, a[0]}. If you wanted "is a non-zero" you wanted the logical operators of section 6.5.唯一要留意的是位寬不匹配。位元運算子由情境決定位寬,所以較窄的運算元會被擴展到情境位寬,無號運算元補零、有號運算元補符號。wire [7:0] y = a & 1'b1; 並不會遮罩成一位元;它把 1'b1 擴展成 8'b00000001,得到 {7'b0, a[0]}。如果你想問的是「a 是否非零」,你要的是 6.5 節的邏輯運算子。注意すべき点は幅の不一致だけである。ビット演算子は文脈決定なので、狭いオペランドは文脈幅へ拡張される。符号なしならゼロ拡張、符号付きなら符号拡張である。wire [7:0] y = a & 1'b1; は 1 ビットにマスクしない。1'b1 を 8'b00000001 に拡張し、{7'b0, a[0]} を与える。「a が非ゼロか」を問いたかったのなら、必要なのは 6.5 節の論理演算子である。
Also worth noting: XOR and XNOR are the expensive gates in CMOS. A two-input NAND is four transistors; a two-input XOR is eight to twelve depending on the construction. A wide XOR network — a parity tree, a CRC, a scrambler, a Galois-field multiplier — therefore costs two to three times what a gate count alone suggests, and XOR networks do not simplify under Boolean optimisation the way AND/OR networks do, because every input is in the support of the output. This matters enormously in communication IC design, where CRC and LFSR logic is pure XOR.另外值得一提:XOR 與 XNOR 是 CMOS 裡昂貴的閘。二輸入 NAND 是四顆電晶體;二輸入 XOR 視構造而定是八到十二顆。因此一個寬 XOR 網路 — 同位元樹、CRC、擾碼器、Galois 體乘法器 — 的成本是單看閘數所暗示的兩到三倍;而且 XOR 網路不像 AND/OR 網路那樣能在布林最佳化下化簡,因為每個輸入都在輸出的支撐集裡。這在通訊 IC 設計裡極為重要,因為 CRC 與 LFSR 邏輯全都是 XOR。もう一つ注目に値する点がある。CMOS において XOR と XNOR は高価なゲートである。2 入力 NAND はトランジスタ 4 個、2 入力 XOR は構成によって 8〜12 個である。したがって幅広い XOR ネットワーク — パリティツリー、CRC、スクランブラ、ガロア体乗算器 — のコストはゲート数だけから想像される値の 2〜3 倍になる。さらに XOR ネットワークは AND/OR ネットワークのようにブール最適化で簡単化されない。すべての入力が出力のサポートに含まれるからである。CRC と LFSR の論理が純粋な XOR である通信 IC 設計では、この点が決定的に重要になる。
A reduction operator takes one vector and produces one bit by applying the operator across all bit positions: &a is the AND of every bit, |a the OR, ^a the XOR (that is, the parity), and ~&, ~|, ~^ their complements. The slide's reduction module exercises all six on a two-bit word and shows the result: six two-input gates, since for $n=2$ a reduction over the whole word is just one gate.歸約運算子接受一個向量,把運算子套用到所有位元位置上,產生一個位元:&a 是所有位元的 AND,|a 是 OR,^a 是 XOR(也就是同位元),而 ~&、~|、~^ 是它們的補數。投影片的 reduction 模組在一個兩位元字上操演了全部六種,結果是六顆二輸入閘 — 因為 $n=2$ 時,對整個字做歸約就只是一顆閘。リダクション演算子はベクタを 1 つ取り、全ビット位置に演算子を適用して 1 ビットを生む。&a は全ビットの AND、|a は OR、^a は XOR(すなわちパリティ)であり、~&、~|、~^ はそれらの補数である。スライドの reduction モジュールは 2 ビットのワードに対して 6 種すべてを試し、結果は 2 入力ゲート 6 個となる。$n=2$ ではワード全体のリダクションがゲート 1 個にすぎないからである。
module reduction (data_word, y_red_and, y_red_or, y_red_nand,
y_red_nor, y_red_xor, y_red_xnor);
input [1:0] data_word;
output y_red_and, y_red_or, y_red_nand, y_red_nor, y_red_xor, y_red_xnor;
reg y_red_and, y_red_or, y_red_nand, y_red_nor, y_red_xor, y_red_xnor;
always @ (data_word) begin
y_red_and <= &data_word;
y_red_or <= |data_word;
y_red_nand <= ~&data_word;
y_red_nor <= ~|data_word;
y_red_xor <= ^data_word;
y_red_xnor <= ~^data_word;
end
endmodule
The interesting question is what happens for a wide vector, and the answer is that a reduction over $n$ bits is $n-1$ two-input gates, which the tool may arrange as a linear chain of depth $n-1$ or as a balanced tree of depth $\lceil \log_2 n\rceil$. The area is identical; only the depth differs. Associative and commutative operators — and all six reductions are both — give the optimiser complete freedom to rebalance, so you get the tree automatically whenever timing demands it. This is in sharp contrast to the addition chain of section 6.8, where rebalancing changes the widths of the intermediate results and the tool is more conservative.有趣的問題是寬向量會怎樣,答案是:對 $n$ 位元做歸約需要 $n-1$ 顆二輸入閘,工具可以把它們排成深度 $n-1$ 的線性串,也可以排成深度 $\lceil \log_2 n\rceil$ 的平衡樹。面積完全相同,只有深度不同。可結合且可交換的運算子 — 這六種歸約都是 — 給了最佳化器完全的重新平衡自由,所以只要時序有要求,你就會自動拿到樹狀形式。這和 6.8 節的加法鏈形成強烈對比:在那裡重新平衡會改變中間結果的位寬,工具就保守得多。興味深いのは幅の広いベクタで何が起こるかであり、答えは、$n$ ビットのリダクションは 2 入力ゲート $n-1$ 個であり、ツールはそれを深さ $n-1$ の直鎖にも深さ $\lceil \log_2 n\rceil$ の平衡木にも並べうる、というものである。面積は同一で、違うのは深さだけである。結合的かつ可換な演算子 — 6 種のリダクションはすべてそうである — は最適化器に再平衡の完全な自由を与えるので、タイミングが要求すれば自動的に木の形が得られる。これは 6.8 節の加算の連鎖と鋭い対照をなす。そちらでは再平衡が中間結果の幅を変えるため、ツールはずっと保守的になる。
The cost table, for a vector of $n$ bits:對一個 $n$ 位元向量,成本表如下:$n$ ビットのベクタに対するコスト表は次のとおりである。
| Reduction歸約リダクション | Gates閘ゲート | Approx. area約略面積概算面積 | Depth (balanced)深度(平衡樹)深さ(平衡木) | Typical use典型用途典型的な用途 |
|---|---|---|---|---|
&a | $n-1$ AND2$n-1$ 顆 AND2AND2 $n-1$ 個 | $n$ GE | $\lceil\log_2 n\rceil$ | "all ones" / counter terminal count「全為 1」/計數器終值「オール 1」/カウンタのターミナルカウント |
|a | $n-1$ OR2$n-1$ 顆 OR2OR2 $n-1$ 個 | $n$ GE | $\lceil\log_2 n\rceil$ | "non-zero" test「非零」測試「非ゼロ」の判定 |
~|a | $n-1$ OR2 + INV$n-1$ 顆 OR2 加 INVOR2 $n-1$ 個 + INV | $n$ GE | $\lceil\log_2 n\rceil$ | zero detect零值偵測ゼロ検出 |
^a | $n-1$ XOR2$n-1$ 顆 XOR2XOR2 $n-1$ 個 | $2.2n$ GE | $\lceil\log_2 n\rceil$ | parity, CRC, LFSR同位元、CRC、LFSRパリティ、CRC、LFSR |
Two idioms are worth committing to memory because they replace comparators with reductions. "Is a zero?" is ~|a, about $n$ GE, not a == 0 — though every tool recognises the latter and produces the former. "Is a all ones?" is &a. And the terminal-count signal of a counter is &count, which is why counters that count up to an all-ones value are cheaper than counters that count to an arbitrary modulus.有兩個慣用寫法值得背下來,因為它們用歸約取代了比較器。「a 是零嗎?」是 ~|a,約 $n$ GE,而不是 a == 0 — 雖然每個工具都認得後者並產生前者。「a 全為 1 嗎?」是 &a。而計數器的終值訊號是 &count,這就是為什麼數到全 1 的計數器比數到任意模數的計數器便宜。覚えておく価値のある慣用句が二つある。比較器をリダクションで置き換えるものである。「a はゼロか」は a == 0 ではなく ~|a で、約 $n$ GE である — もっともどのツールも後者を認識して前者を生成する。「a はオール 1 か」は &a である。そしてカウンタのターミナルカウント信号は &count であり、オール 1 まで数えるカウンタが任意の法まで数えるカウンタより安い理由がここにある。
&&, || and ! are not bitwise. They first reduce each operand to a single truth value — zero is false, anything non-zero is true — and then combine those truth values. So a && b with eight-bit operands is (|a) & (|b): two OR-reductions feeding one AND, about $2n + 1$ GE and $\lceil\log_2 n\rceil + 1$ levels. !a is ~|a.&&、|| 與 ! 不是位元運算子。它們先把每個運算元歸約成單一真假值 — 零為假,任何非零為真 — 再把這些真假值組合起來。所以八位元運算元的 a && b 是 (|a) & (|b):兩個 OR 歸約餵進一顆 AND,約 $2n + 1$ GE、$\lceil\log_2 n\rceil + 1$ 層。!a 就是 ~|a。&&、||、! はビット演算子ではない。まず各オペランドを単一の真偽値に縮約し — ゼロは偽、非ゼロは真 — それらの真偽値を結合する。したがって 8 ビットオペランドの a && b は (|a) & (|b) であり、2 つの OR リダクションが AND 1 個を駆動する形で、約 $2n + 1$ GE、$\lceil\log_2 n\rceil + 1$ 段である。!a は ~|a である。
Confusing & with && is a classic error with a clear hardware signature. if (a & b) on multi-bit operands tests whether any bit position has a 1 in both, which needs an AND per bit plus a reduction; if (a && b) tests whether both are non-zero. They coincide for single-bit operands, which is why the mistake survives simulation of simple cases, and diverge the moment either operand is widened. On a single bit the two cost the same, so there is no efficiency argument either way; use && for conditions and & for data, and the intent reads correctly.把 & 和 && 搞混是經典錯誤,而且有清楚的硬體特徵。對多位元運算元而言,if (a & b) 測試的是是否有任何位元位置兩邊都是 1,這需要每位元一顆 AND 再加一次歸約;if (a && b) 測試的則是兩者是否都非零。對單位元運算元它們一致,這就是為什麼這個錯誤能在簡單情況的模擬中存活下來,而只要任一運算元加寬就會分歧。在單一位元上兩者成本相同,所以沒有效率上的論據偏向哪一邊;條件用 &&、資料用 &,意圖就讀得正確。& と && の取り違えは古典的な誤りで、ハードウェア上の特徴がはっきりしている。多ビットオペランドでは if (a & b) はどこかのビット位置で両方が 1 かを調べ、ビットごとの AND とリダクションを要する。if (a && b) は両方が非ゼロかを調べる。1 ビットのオペランドでは一致するので、この誤りは単純なケースのシミュレーションを生き延び、どちらかのオペランドが広がった瞬間に乖離する。1 ビットでは両者のコストは同じなので効率上の論拠はどちらにもない。条件には &&、データには & を使えば意図が正しく読める。
One Verilog-specific hazard: unlike C, Verilog's && and || do not reliably short-circuit in a way you can depend on for safety, and in synthesis they never do — both sides of the expression exist as hardware simultaneously. Writing if (ptr != 0 && mem[ptr]) gives you a memory read that happens regardless, so the guard protects nothing in silicon.一個 Verilog 特有的風險:與 C 不同,Verilog 的 && 與 || 不會以你能拿來當安全保障的方式可靠地短路,而在合成中它們從來不短路 — 運算式的兩邊同時都以硬體的形式存在。寫 if (ptr != 0 && mem[ptr]) 得到的記憶體讀取無論如何都會發生,所以這個守衛在矽上什麼都沒保護到。Verilog 固有の落とし穴が一つある。C と異なり Verilog の && と || は、安全性の根拠にできるような形で確実に短絡評価されるわけではなく、合成では決して短絡しない。式の両辺が同時にハードウェアとして存在する。if (ptr != 0 && mem[ptr]) と書けばメモリ読み出しは無条件に起こるので、このガードはシリコン上で何も守っていない。
Slide 15 says ?: is "synthesized into muxes or gates that implement muxes", and that is exactly right. cond ? a : b on $n$-bit operands is $n$ two-to-one multiplexers sharing one select line, about $2.5n$ GE and one mux level, plus whatever logic computes cond. Whether the mapper uses actual MUX2 cells or covers the function with AOI/OAI gates is a technology-mapping decision invisible from the RTL — the lecture's mux_conditional schematic shows the AOI form, with the tri-state-looking boxes being mux cells and the rest AND-OR-INVERT plus inverters.第 15 張投影片說 ?: 會「synthesized into muxes or gates that implement muxes」,這完全正確。$n$ 位元運算元上的 cond ? a : b 是 $n$ 顆共用一條選擇線的二對一多工器,約 $2.5n$ GE、一層多工器延遲,再加上計算 cond 所需的邏輯。對應器究竟是用真正的 MUX2 元件,還是用 AOI/OAI 閘來覆蓋這個函式,是從 RTL 看不見的製程對應決策 — 這一講的 mux_conditional 電路圖顯示的是 AOI 形式,那些看起來像三態的方塊是多工器元件,其餘是 AND-OR-INVERT 加反相器。スライド 15 は ?: が「synthesized into muxes or gates that implement muxes」だと述べており、これはまったく正しい。$n$ ビットオペランドに対する cond ? a : b は、選択線を共有する 2 対 1 マルチプレクサ $n$ 個で、約 $2.5n$ GE、マルチプレクサ 1 段分の遅延に加え cond を計算する論理が乗る。マッパが実際の MUX2 セルを使うか AOI/OAI ゲートで関数を覆うかは RTL からは見えないテクノロジマッピングの判断である。講義の mux_conditional の回路図は AOI の形を示しており、トライステートのように見える箱がマルチプレクサセル、残りが AND-OR-INVERT とインバータである。
module mux_conditional (data_out, data_a, data_b, data_c, data_d, sel);
input [3:0] data_a, data_b, data_c, data_d;
input [1:0] sel;
output [3:0] data_out;
assign data_out = (sel == 2'b00) ? data_a :
(sel == 2'b01) ? data_b :
(sel == 2'b10) ? data_c : data_d;
endmodule
The important structural point is what a chain of conditionals becomes. Written as above, the expression is a nested priority structure: test the first condition, else test the second, else test the third. Taken literally that is three mux levels on the data_d path and one on the data_a path — an unbalanced tree. But the three conditions here are mutually exclusive and exhaustive over a two-bit selector, and a good optimiser proves that and collapses the chain into a balanced 4:1 mux of two levels, which is what the slide's schematic shows. When the conditions are not provably exclusive, the chain stays a chain, and a twenty-way priority ?: cascade gives you a twenty-level path.結構上重要的一點是條件運算子串接後會變成什麼。照上面那樣寫,這個運算式是一個巢狀的優先權結構:先測第一個條件,否則測第二個,否則測第三個。照字面看,那是 data_d 路徑上三層多工器、data_a 路徑上一層 — 一棵不平衡的樹。但這裡三個條件在一個兩位元選擇子上是互斥且窮盡的,好的最佳化器會證明這點,把這串塌縮成一顆兩層的平衡 4:1 多工器,這正是投影片電路圖顯示的樣子。當條件不能被證明互斥時,這串就仍然是一串,而一個二十路的優先權 ?: 級聯會給你一條二十層的路徑。構造上重要なのは、条件演算子の連鎖が何になるかである。上記のように書けば、式は入れ子の優先順位構造になる。最初の条件を調べ、だめなら 2 番目、だめなら 3 番目、という具合である。文字どおりに取れば data_d の経路には 3 段、data_a の経路には 1 段のマルチプレクサがある — 不平衡な木である。しかしここでの 3 つの条件は 2 ビットのセレクタに対して互いに排他かつ網羅的であり、よい最適化器はそれを証明して連鎖を 2 段の平衡した 4:1 マルチプレクサに縮退させる。スライドの回路図が示すのがこれである。条件が排他だと証明できない場合、連鎖は連鎖のまま残り、20 分岐の優先順位 ?: カスケードは 20 段の経路を生む。
If you want a balanced multiplexer, write a case on the selector rather than a conditional chain on comparisons. case (sel) 2'b00: ... endcase states exclusivity structurally and needs no proof. If you want a priority structure, write the chain — but then know that the last branch is the slowest, and put the timing-critical input first.如果你要的是平衡多工器,就對選擇子寫 case,而不是對一串比較寫條件運算子鏈。case (sel) 2'b00: ... endcase 在結構上就宣告了互斥性,不需要任何證明。如果你要的是優先權結構,就寫鏈 — 但要知道最後一個分支最慢,所以把時序關鍵的輸入放在最前面。平衡したマルチプレクサが欲しいなら、比較の条件連鎖ではなくセレクタに対する case を書くこと。case (sel) 2'b00: ... endcase は排他性を構造として宣言し、証明を必要としない。優先順位構造が欲しいなら連鎖を書く — ただし最後の分岐がもっとも遅いことを知り、タイミングクリティカルな入力を先頭に置くこと。
Slide 16's two lines — supported in general, equivalent to a logical bus with no functionality of its own — are complete and correct. {a, b} costs nothing; it names a bus formed from existing wires. Replication {4{a}} likewise costs nothing; it fans one signal out to four positions, which has a fanout cost in buffering but no logic cost.第 16 張投影片的兩行 — 一般都支援,等價於一條本身沒有任何功能的邏輯匯流排 — 既完整又正確。{a, b} 不花成本;它只是替由既有線路組成的匯流排取個名字。重複 {4{a}} 同樣不花成本;它把一個訊號扇出到四個位置,在緩衝上有扇出成本,但沒有邏輯成本。スライド 16 の 2 行 — 一般に対応しており、それ自体は機能を持たない論理的なバスに等しい — は完全かつ正しい。{a, b} はコストゼロで、既存の配線から成るバスに名前を付けるだけである。繰り返し {4{a}} も同様にコストゼロで、1 本の信号を 4 箇所へファンアウトする。バッファリングのファンアウトコストはあるが論理コストはない。
The concatenation operator is nonetheless one of the most useful tools in synthesisable RTL, precisely because it is free. Three idioms recur constantly:儘管如此,串接運算子仍是可合成 RTL 中最有用的工具之一,正因為它免費。有三個慣用寫法不斷出現:とはいえ連結演算子は合成可能 RTL でもっとも有用な道具の一つである。まさに無料だからである。三つの慣用句が繰り返し現れる。
// 1. Capture the carry out of an addition without a separate comparator wire [8:0] sum = {1'b0, a} + {1'b0, b}; wire carry = sum[8]; // 2. Sign-extend explicitly rather than relying on signedness rules wire [15:0] sx = {{8{a[7]}}, a}; // 3. Build a shift register with no shift operator at all always @(posedge clk) sr <= {sr[6:0], serial_in};
The third idiom is the canonical way to write a shift register in modern RTL, and it is worth seeing why it costs nothing beyond the flip-flops: the right-hand side is a concatenation of a part select and a wire, which is pure routing, so the inferred hardware is eight flip-flops with their D pins wired to their neighbours' Q pins. No multiplexers, no gates. Contrast the data_shift module of section 5.5, which needed three muxes per bit because it also had to load, hold and clear.第三個慣用寫法是現代 RTL 裡寫移位暫存器的典範做法,而且值得看看它為什麼除了正反器以外什麼都不花:右手邊是一個部分選取與一條線的串接,純粹是繞線,所以推論出來的硬體就是八顆正反器,D 腳接到鄰居的 Q 腳。沒有多工器,沒有閘。對照 5.5 節的 data_shift 模組,它每位元需要三顆多工器,因為它還得載入、保持與清除。三つ目の慣用句は現代 RTL でシフトレジスタを書く定石であり、フリップフロップ以外に何もコストがかからない理由を見ておく価値がある。右辺は部分選択と配線の連結であり純粋なルーティングなので、推論されるハードウェアはフリップフロップ 8 個の D ピンが隣の Q ピンに接続されたものになる。マルチプレクサもゲートもない。ロード・ホールド・クリアも行わねばならないため 1 ビットあたり 3 個のマルチプレクサを要した 5.5 節の data_shift モジュールと対照的である。
Two traps. First, the self-determined-width rule of section 2.3: operands inside { } keep their own width, so an unsized literal is thirty-two bits. Second, an empty or unsized replication count is illegal, and a replication count must be a constant — {n{a}} with n a signal cannot be elaborated, for the same reason a non-static loop cannot.兩個陷阱。第一是 2.3 節的自決定位寬規則:{ } 內的運算元保有自己的位寬,所以未指定位寬的常數是三十二位元。第二,空的或未指定的重複次數是非法的,而重複次數必須是常數 — {n{a}} 若 n 是訊號就無法展開,理由和非靜態迴圈無法展開相同。罠が二つある。第一は 2.3 節の自己決定幅の規則で、{ } 内のオペランドは自身の幅を保つため、サイズ指定のないリテラルは 32 ビットになる。第二に、空または未指定の繰り返し回数は不正であり、繰り返し回数は定数でなければならない。n が信号である {n{a}} はエラボレートできない。非静的ループが展開できないのと同じ理由である。
Slide 17 is a single example with no text, and it is one of the most practically useful slides in the lecture.第 17 張投影片只有一個範例、沒有文字,卻是這一講裡實用價值最高的投影片之一。スライド 17 は本文のない例が一つだけだが、この講義でもっとも実用的に有用なスライドの一つである。
module operator_group (sum1, sum2, a, b, c, d); input [3:0] a, b, c, d; output [4:0] sum1, sum2; assign sum1 = a + b + c + d; assign sum2 = (a + b) + (c + d); endmodule
Verilog's binary operators are left-associative, so a + b + c + d parses as ((a + b) + c) + d. Three additions, arranged in a chain: the result of the first feeds the second, which feeds the third. The parenthesised version is also three additions, but arranged as a balanced tree: the two inner sums are computed in parallel and then combined. The slide's figure shows precisely this — a three-deep ladder for sum1, a two-deep tree for sum2.Verilog 的二元運算子是左結合的,所以 a + b + c + d 解析成 ((a + b) + c) + d。三次加法,排成一串:第一次的結果餵給第二次,第二次餵給第三次。加了括號的版本同樣是三次加法,但排成一棵平衡樹:兩個內層的和平行算出後再合併。投影片的圖顯示的正是這個 — sum1 是三層的梯子,sum2 是兩層的樹。Verilog の二項演算子は左結合なので、a + b + c + d は ((a + b) + c) + d と解析される。加算 3 回が鎖状に並び、1 つ目の結果が 2 つ目へ、2 つ目が 3 つ目へ流れる。括弧を付けた版も加算 3 回だが、平衡木として並ぶ。内側の 2 つの和が並列に計算され、その後で結合される。スライドの図が示すのはまさにこれで、sum1 は 3 段のはしご、sum2 は 2 段の木である。
Two quantitative remarks. First, the widths. Both targets are five bits, so the context width of the whole expression is five, every operand is zero-extended to five bits, and all three adders are five-bit adders — about 30 GE each, 90 GE in total, identical for both versions. Only the depth differs: about fifteen levels for the chain against about ten for the tree if ripple adders are used. Second, the saving generalises. Summing $k$ operands as a chain costs $k-1$ adder delays; as a balanced tree it costs $\lceil\log_2 k\rceil$. For eight operands that is seven against three.兩點量化的說明。第一是位寬。兩個目標都是五位元,所以整個運算式的情境位寬是五,每個運算元都零擴展到五位元,三個加法器都是五位元加法器 — 每個約 30 GE,合計 90 GE,兩個版本完全相同。只有深度不同:若用漣波加法器,串接版約十五層,樹狀版約十層。第二,這個節省可以推廣。把 $k$ 個運算元串成鏈相加要花 $k-1$ 個加法器延遲;排成平衡樹則是 $\lceil\log_2 k\rceil$。八個運算元就是七對三。定量的な指摘が二つある。第一に幅である。どちらの代入先も 5 ビットなので式全体の文脈幅は 5 となり、すべてのオペランドが 5 ビットにゼロ拡張され、3 つの加算器はいずれも 5 ビット加算器になる — 各約 30 GE、合計 90 GE で、両方の版で同一である。異なるのは深さだけで、リップル加算器なら連鎖版が約 15 段、木の版が約 10 段である。第二に、この節約は一般化する。$k$ 個のオペランドを鎖状に加算すると加算器遅延 $k-1$ 段、平衡木なら $\lceil\log_2 k\rceil$ 段で済む。8 オペランドなら 7 対 3 である。
Modern synthesisers contain an arithmetic restructuring engine that will rebalance an addition chain on its own when timing demands it, so the parentheses are not always necessary. They are still worth writing, for three reasons: restructuring is disabled or limited in some flows and for some operator mixes, especially when signed and unsigned operands are combined or when an intermediate result is also used elsewhere; the parenthesised form documents the intended structure for a reader; and restructuring cannot help at all when the intermediate value is itself a named signal that the design uses, because then the tool must preserve it.現代合成器內含一個算術重構引擎,在時序有要求時會自行重新平衡加法鏈,所以括號並非總是必要。但它們仍然值得寫,理由有三:在某些流程與某些運算子組合下重構會被停用或受限,尤其是有號與無號運算元混用時,或中間結果在別處也被使用時;加了括號的寫法把預期的結構記錄給讀者看;而當中間值本身是設計會用到的具名訊號時,重構完全幫不上忙,因為工具必須保留它。現代の合成器は算術再構成エンジンを備えており、タイミングが要求すれば加算の連鎖を自ら再平衡する。したがって括弧は常に必要というわけではない。それでも書く価値が三つある。フローや演算子の組合せによっては再構成が無効化されたり制限されたりする。とくに符号付きと符号なしのオペランドが混ざる場合や、中間結果が他所でも使われる場合である。括弧付きの形は意図した構造を読み手に文書化する。そして中間値自体が設計で使われる名前付き信号である場合、ツールはそれを保持せねばならないので再構成はまったく助けにならない。
For summing many operands there is a better structure still. A chain or tree of carry-propagate adders pays the carry propagation $k-1$ or $\log_2 k$ times. A carry-save tree instead reduces $k$ operands to two using 3:2 compressors (full adders used as counters, with no carry propagation at all), and pays for a single carry-propagate adder at the very end. For eight 16-bit operands this is roughly half the area and a third of the delay of a binary tree of ripple adders. Writing it in RTL means instantiating the compressors or relying on the tool's own multi-operand-addition recognition, which most modern tools have.要把很多個運算元加起來,還有更好的結構。進位傳播加法器的鏈或樹會付出 $k-1$ 次或 $\log_2 k$ 次的進位傳播代價。carry-save 樹則改用 3:2 壓縮器(當成計數器用的全加器,完全不傳播進位)把 $k$ 個運算元縮減成兩個,最後只付一次進位傳播加法器的代價。對八個 16 位元運算元而言,這大約是漣波加法器二元樹的一半面積與三分之一延遲。要在 RTL 裡寫出它,就得實例化那些壓縮器,或仰賴工具自己的多運算元加法辨識 — 多數現代工具都有。多数のオペランドを加算するには、さらに良い構造がある。キャリー伝播加算器の鎖や木はキャリー伝播の代価を $k-1$ 回あるいは $\log_2 k$ 回払う。キャリーセーブ木は代わりに 3:2 圧縮器(カウンタとして使う全加算器で、キャリー伝播はまったくない)で $k$ 個のオペランドを 2 個に縮約し、最後にキャリー伝播加算器 1 個分だけを払う。16 ビットのオペランド 8 個なら、リップル加算器の二分木に比べておよそ半分の面積と 3 分の 1 の遅延になる。RTL で書くには圧縮器をインスタンス化するか、ツール自身の多オペランド加算の認識に頼ることになる。現代のツールの多くはこれを備えている。
The following table collects everything from sections 4 through 6 in one place. $n$ is the operand width in bits. Area figures are rough two-input gate equivalents on a generic standard-cell library and should be read as scaling laws, not as data-sheet values.下表把第 4 到 6 節的內容集中在一處。$n$ 是運算元的位寬。面積數字是泛用標準元件庫上粗略的二輸入等效閘數,應該當成尺度律來讀,而不是規格書上的數值。次の表は第 4 節から第 6 節までの内容を一箇所に集めたものである。$n$ はオペランドのビット幅である。面積の数値は汎用スタンダードセルライブラリにおけるおおまかな 2 入力ゲート等価であり、データシートの値ではなくスケーリング則として読むべきものである。
| Operator運算子演算子 | Hardware built建出的硬體生成されるハードウェア | Area面積面積 | Depth深度深さ | Constant-operand shortcut常數運算元的捷徑定数オペランドの近道 |
|---|---|---|---|---|
+ - | Adder / subtractor, ripple or prefix加法器/減法器,漣波式或前綴式加算器/減算器、リップルまたはプレフィックス | $6n$ to $16n$$6n$ 到 $16n$$6n$〜$16n$ | $O(n)$ or $O(\log n)$$O(n)$ 或 $O(\log n)$$O(n)$ または $O(\log n)$ | +1 / −1 collapses to an incrementer, ~$4n$+1/−1 塌縮成遞增/遞減器,約 $4n$+1/−1 はインクリメンタに縮退、約 $4n$ |
* | Partial-product array + CSA tree + final adder部分積陣列 + CSA 樹 + 最終加法器部分積アレイ + CSA 木 + 最終加算器 | $O(n^2)$, ~450 at $n=8$$O(n^2)$,$n=8$ 時約 450$O(n^2)$、$n=8$ で約 450 | $O(n)$ or $O(\log n)$$O(n)$ 或 $O(\log n)$$O(n)$ または $O(\log n)$ | Power of two → wiring; otherwise shift-add, ~1 adder per CSD digit2 的冪 → 接線;否則移位相加,每個 CSD 數字約 1 個加法器2 のべき乗 → 配線。それ以外はシフト加算で、CSD 桁ごとにおよそ加算器 1 個 |
/ | Iterative subtract-and-select array迭代式的減法與選擇陣列反復的な減算・選択アレイ | $O(n^2)$, very large$O(n^2)$,非常大$O(n^2)$、きわめて大きい | $O(n^2)$ | Power of two → part select; other constants → magic multiply2 的冪 → 部分選取;其他常數 → 魔術乘法2 のべき乗 → 部分選択。他の定数 → マジック乗算 |
% | Divider, remainder output除法器,取餘數輸出除算器、剰余出力 | $O(n^2)$ | $O(n^2)$ | Power of two → low bits, free (unsigned only)2 的冪 → 低位元,免費(僅限無號)2 のべき乗 → 下位ビット、無料(符号なしのみ) |
<< >> <<< >>> | Barrel shifter, $\log_2 n$ mux stages桶式移位器,$\log_2 n$ 級多工器バレルシフタ、マルチプレクサ $\log_2 n$ 段 | $2.5\,n\log_2 n$ | $\log_2 n$ mux levels$\log_2 n$ 層多工器マルチプレクサ $\log_2 n$ 段 | Constant amount → zero gates常數位移量 → 零個閘定数シフト量 → ゲート 0 |
< > <= >= | Magnitude comparator, or reuse of an adder大小比較器,或重用既有加法器大小比較器、または既存の加算器の再利用 | $2n$ to $4n$$2n$ 到 $4n$$2n$〜$4n$ | $O(\log n)$ | Against $2^k$ → one bit, free; against 0 → reduction與 $2^k$ 比較 → 一個位元,免費;與 0 比較 → 歸約$2^k$ との比較 → 1 ビット、無料。0 との比較 → リダクション |
== != | Bitwise XOR + NOR reduction逐位元 XOR + NOR 歸約ビットごとの XOR + NOR リダクション | $3n$ | $1+\log_2 n$ | Against a constant → AND of selected bits, $n$與常數比較 → 選定位元的 AND,$n$定数との比較 → 選ばれたビットの AND、$n$ |
=== !== | None Not synthesisable無 不可合成なし 合成不可 | — | — | — |
& | ^ ~ | $n$ independent two-input gates$n$ 顆獨立的二輸入閘独立した 2 入力ゲート $n$ 個 | $n$ (AND/OR), $2.2n$ (XOR)$n$(AND/OR)、$2.2n$(XOR)$n$(AND/OR)、$2.2n$(XOR) | 1 level1 層1 段 | — |
&a |a ^a | Tree of $n-1$ two-input gates$n-1$ 顆二輸入閘組成的樹2 入力ゲート $n-1$ 個の木 | $n$ or $2.2n$$n$ 或 $2.2n$$n$ または $2.2n$ | $\lceil\log_2 n\rceil$ | — |
&& || ! | OR-reduce each operand, then one gate對每個運算元做 OR 歸約,再接一顆閘各オペランドを OR リダクションし、ゲート 1 個で結合 | $2n+1$ | $\log_2 n + 1$ | On 1-bit operands, same as bitwise對 1 位元運算元,與位元運算子相同1 ビットオペランドではビット演算子と同じ |
?: | $n$ two-to-one muxes + condition logic$n$ 顆二對一多工器 + 條件邏輯2 対 1 マルチプレクサ $n$ 個 + 条件論理 | $2.5n$ + cond$2.5n$ + 條件$2.5n$ + 条件 | 1 mux level per chain step鏈上每一步 1 層多工器連鎖の 1 段ごとにマルチプレクサ 1 段 | Constant condition → folded away entirely常數條件 → 整個被摺疊掉条件が定数 → まるごと畳み込まれる |
{ } {k{ }} | Wiring only只有接線配線のみ | 0 | 0 | — |
a[i] constant ia[i],i 為常數a[i]、i が定数 | Wire線配線 | 0 | 0 | — |
a[i] variable ia[i],i 為變數a[i]、i が変数 | $n$-to-1 multiplexer$n$ 對 1 多工器$n$ 対 1 マルチプレクサ | $2.5(n-1)$ | $\lceil\log_2 n\rceil$ | Constant index → free常數索引 → 免費定数インデックス → 無料 |
Problem: d is [31:0]. Give the gate count and depth for each, and say what it computes.問題:d 是 [31:0]。請給出每一項的閘數與深度,並說明它計算什麼。問題:d は [31:0] とする。各項についてゲート数と深さを示し、何を計算しているかを述べよ。
(a) assign z = ~|d;
(b) assign p = ^d;
(c) assign f = &d[7:0];
(d) assign g = (d == 32'b0);
(e) assign h = |(d & mask);
(f) assign k = ^(d ^ {d[30:0], 1'b0});
(a) Zero detect. 31 OR2 plus an inverter, about 32 GE, five levels balanced.(a) 零值偵測。31 顆 OR2 加一顆反相器,約 32 GE,平衡樹五層。(a) ゼロ検出。OR2 31 個とインバータ 1 個で約 32 GE、平衡させれば 5 段。
(b) Parity of 32 bits. 31 XOR2, about 68 GE, five levels. Note the area is roughly twice (a)'s for the same gate count, because XOR cells are larger — this is the one place in the table where gate count and area diverge noticeably.(b) 32 位元的同位元。31 顆 XOR2,約 68 GE,五層。注意在閘數相同的情況下面積大約是 (a) 的兩倍,因為 XOR 元件比較大 — 這是整張表裡閘數與面積明顯分歧的唯一一處。(b) 32 ビットのパリティ。XOR2 31 個で約 68 GE、5 段。ゲート数が同じでも面積は (a) のおよそ 2 倍である点に注意してほしい。XOR セルが大きいからで、表の中でゲート数と面積が目に見えて乖離する唯一の箇所である。
(c) "The low byte is all ones". 7 AND2, about 7 GE, three levels. Only eight of the thirty-two bits are in the cone; the rest are not loaded at all.(c)「低位元組全為 1」。7 顆 AND2,約 7 GE,三層。三十二個位元中只有八個在邏輯錐裡,其餘完全沒有被載入。(c)「下位バイトがオール 1」。AND2 7 個で約 7 GE、3 段。32 ビットのうちコーンに入るのは 8 ビットだけで、残りはまったく負荷にならない。
(d) Identical to (a). Every tool recognises comparison against the zero constant and emits a NOR tree, not a 32-bit comparator. Writing d == 0 is therefore perfectly efficient and arguably more readable than ~|d.(d) 與 (a) 完全相同。每個工具都認得與零常數的比較,產生的是 NOR 樹,而不是一個 32 位元比較器。所以寫 d == 0 完全有效率,而且可以說比 ~|d 更好讀。(d) (a) と同一。どのツールもゼロ定数との比較を認識し、32 ビット比較器ではなく NOR 木を出力する。したがって d == 0 と書くのは完全に効率的で、~|d より読みやすいとも言える。
(e) "Any masked bit set". 32 AND2 for the mask plus a 31-gate OR tree, about 63 GE, six levels. This is the canonical interrupt-pending expression.(e)「有任何被遮罩的位元被設定」。遮罩用的 32 顆 AND2,加上 31 顆閘的 OR 樹,約 63 GE,六層。這是中斷待處理訊號的典範運算式。(e)「マスクされたビットのいずれかが立っている」。マスク用の AND2 32 個と 31 ゲートの OR 木で約 63 GE、6 段。割り込みペンディングの定石的な式である。
(f) A trick question worth thinking about. {d[30:0],1'b0} is d << 1, free wiring; the XOR with d gives a 32-bit transition map (bit $i$ is 1 where $d_i \ne d_{i-1}$), 32 XOR2; the outer reduction then gives the parity of the number of transitions. Total 32 + 31 = 63 XOR2, about 140 GE, six levels. Structures like this appear in Gray-code and run-length logic.(f) 一道值得思考的機巧題。{d[30:0],1'b0} 就是 d << 1,免費的接線;與 d 做 XOR 得到一張 32 位元的轉態圖(第 $i$ 位為 1 代表 $d_i \ne d_{i-1}$),用掉 32 顆 XOR2;外層的歸約則給出轉態次數的同位元。總計 32 + 31 = 63 顆 XOR2,約 140 GE,六層。這類結構會出現在格雷碼與 run-length 邏輯中。(f) 考える価値のあるひっかけ問題である。{d[30:0],1'b0} は d << 1 であり無料の配線。d との XOR は 32 ビットの遷移マップ(ビット $i$ が 1 なら $d_i \ne d_{i-1}$)を与え、XOR2 を 32 個使う。外側のリダクションは遷移回数のパリティを与える。合計 32 + 31 = 63 個の XOR2、約 140 GE、6 段。この種の構造はグレイコードやランレングスの論理に現れる。
Problem: The following line is on the critical path of a receiver's symbol accumulator. All signals are unsigned [11:0]; the target acc is [15:0]. Estimate the adder delay, then rewrite it to minimise the path without changing the result, and estimate again. Assume ripple adders of about 16 levels each at this width.問題:下面這一行位在某接收機符元累加器的關鍵路徑上。所有訊號都是無號 [11:0];目標 acc 是 [15:0]。請估算加法器延遲,然後在不改變結果的前提下改寫它以最小化路徑,再估算一次。假設在這個位寬下每個漣波加法器約十六層。問題:次の行はある受信機のシンボル累算器のクリティカルパス上にある。すべての信号は符号なしの [11:0]、代入先 acc は [15:0] である。加算器の遅延を見積もり、結果を変えずに経路を最短化するよう書き換えて、もう一度見積もれ。この幅ではリップル加算器 1 個あたり約 16 段とする。
assign acc = w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7;
As written. Left-associative, so seven 16-bit adders in a chain: seven adder delays, about 112 levels of logic. Area about $7 \times 96 = 670$ GE. This will never meet a gigahertz clock.照原樣。左結合,所以是七個 16 位元加法器串成一鏈:七個加法器延遲,約 112 層邏輯。面積約 $7 \times 96 = 670$ GE。這絕對達不到 GHz 等級的時脈。そのままの形。左結合なので 16 ビット加算器 7 個が鎖状に並ぶ。加算器遅延 7 段、約 112 段の論理である。面積は約 $7 \times 96 = 670$ GE。これではギガヘルツのクロックに決して間に合わない。
Balanced tree. ((w0+w1)+(w2+w3)) + ((w4+w5)+(w6+w7)). Still seven adders, still about 670 GE, but only three adder delays — about 48 levels. A 57 % reduction for free.平衡樹。((w0+w1)+(w2+w3)) + ((w4+w5)+(w6+w7))。仍然是七個加法器、仍然約 670 GE,但只剩三個加法器延遲 — 約 48 層。免費換來 57 % 的縮減。平衡木。((w0+w1)+(w2+w3)) + ((w4+w5)+(w6+w7))。加算器は依然 7 個、面積も約 670 GE のままだが、加算器遅延は 3 段 — 約 48 段になる。無料で 57 % の削減である。
Carry-save. Better still, reduce the eight operands to two with 3:2 compressors — four compressors in the first rank reducing 8 to 6, then 6 to 4, then 4 to 3, then 3 to 2, which is four compressor levels, each one full-adder delay (about three levels) — and finish with a single 16-bit carry-propagate adder. Total about $12 + 16 = 28$ levels, and the area drops because a compressor rank costs one full adder per bit while a carry-propagate adder costs one full adder per bit plus the carry chain. Roughly 550 GE and 28 levels: a quarter of the original delay.Carry-save。更好的做法是用 3:2 壓縮器把八個運算元縮減成兩個 — 第一排四顆壓縮器把 8 縮成 6,再 6 縮成 4、4 縮成 3、3 縮成 2,共四層壓縮器,每層一個全加器延遲(約三層)— 最後再接一個 16 位元進位傳播加法器收尾。總計約 $12 + 16 = 28$ 層,而且面積會下降,因為一排壓縮器每位元只花一顆全加器,而進位傳播加法器每位元是一顆全加器加上進位鏈。大約 550 GE、28 層:原來延遲的四分之一。キャリーセーブ。さらによいのは、3:2 圧縮器で 8 個のオペランドを 2 個に縮約する方法である。第 1 列の圧縮器 4 個で 8 を 6 に、次に 6 を 4、4 を 3、3 を 2 へと縮め、圧縮器 4 段、各段が全加算器 1 個分の遅延(約 3 段)となる。最後に 16 ビットのキャリー伝播加算器 1 個で締める。合計およそ $12 + 16 = 28$ 段で、面積も下がる。圧縮器の 1 列はビットあたり全加算器 1 個で済むのに対し、キャリー伝播加算器はビットあたり全加算器 1 個に加えてキャリーチェーンを要するからである。およそ 550 GE、28 段で、元の遅延の 4 分の 1 である。
Pipelining. If 28 levels is still too deep, insert a register after the compressor tree. One extra cycle of latency, 32 extra flip-flops, and the critical path becomes whichever of the two halves is longer — about 16 levels. In a streaming receiver, latency is usually cheap and throughput is not, so this is normally the right answer.管線化。如果 28 層還是太深,就在壓縮器樹之後插入一級暫存器。多一個週期的延遲、多 32 顆正反器,關鍵路徑就變成兩半之中較長的那一半 — 約 16 層。在串流式接收機裡,延遲通常很便宜而吞吐量不是,所以這通常才是正確答案。パイプライン化。28 段でもまだ深すぎるなら、圧縮器ツリーの後にレジスタを 1 段入れる。レイテンシが 1 サイクル増え、フリップフロップが 32 個増え、クリティカルパスは二つの半分のうち長いほう — 約 16 段 — になる。ストリーミング受信機ではレイテンシは安くスループットはそうでないので、通常はこれが正解である。
The exam point. All four versions compute the same function. Only the first is what you get by writing the line the obvious way, and the difference between it and the last is a factor of four in frequency.考試重點。四個版本計算的是同一個函式。只有第一個是你照直覺寫那一行所得到的,而它和最後一個之間差了四倍的頻率。試験上の要点。4 つの版はすべて同じ関数を計算する。素直に 1 行書いて得られるのは最初のものだけであり、それと最後のものとでは周波数が 4 倍違う。
A continuous assignment drives a net from an expression, permanently. Slide 18 says it is "translated to Boolean equations which are then optimized and synthesized into combinational gates", and the only thing to add is how literally that should be read. The right-hand side is an expression tree; the tool walks it, emits a generic operator node for each operator according to the table in section 6.9, then hands the whole network to the optimiser, which flattens what it can into Boolean equations and covers the result with library cells.連續指定會以一個運算式永久驅動一條 net。第 18 張投影片說它「translated to Boolean equations which are then optimized and synthesized into combinational gates」,唯一要補充的是這句話該讀得多字面。右手邊是一棵運算式樹;工具走過它,依 6.9 節的表為每個運算子產生一個泛用運算子節點,然後把整個網路交給最佳化器,由它把能攤平的攤平成布林方程式,再用庫元件覆蓋結果。連続代入はネットを式によって恒久的に駆動する。スライド 18 は「translated to Boolean equations which are then optimized and synthesized into combinational gates」と述べており、付け加えるべきはこれをどれほど文字どおりに読むべきかということだけである。右辺は式の木であり、ツールはそれをたどって 6.9 節の表に従い演算子ごとにジェネリック演算子ノードを出し、ネットワーク全体を最適化器へ渡す。最適化器は平坦化できるものをブール式に平坦化し、結果をライブラリセルで覆う。
Several consequences follow that students often treat as separate rules:由此推出好幾個結果,而學生常把它們當成各自獨立的規則:ここからいくつかの帰結が導かれるが、学生はしばしばそれらを別々の規則として扱う。
always block is not — the entire latch-inference discussion of Lecture 5 simply does not apply here.連續指定永遠不可能推論出儲存元件。沒有任何條件能讓它「沒有執行到」,所以沒有東西需要保持。這就是為什麼連續指定不會產生閂鎖,而 always 區塊會 — 第 5 講整個閂鎖推論的討論在這裡根本不適用。連続代入は記憶素子を推論しえない。「実行されない」状況が存在しないので、保持すべきものがない。連続代入がラッチを生まず always ブロックが生むのはこのためであり、第 5 講のラッチ推論の議論はここにはまったく当てはまらない。assign y = t; assign t = a & b; behaves identically to the reverse order.連續指定在檔案中的順序無關緊要。它們不是敘述,而是聯立方程式。assign y = t; assign t = a & b; 與相反順序的行為完全相同。連続代入のファイル中での順序は無関係である。それらは文ではなく連立方程式である。assign y = t; assign t = a & b; は逆順に書いたものとまったく同じふるまいをする。reg cannot, because a reg is written procedurally. This is the only remaining practical reason to care about the net/variable distinction in RTL, and SystemVerilog's logic type removes even that by allowing either, provided there is exactly one driver.只有 net 能當目標。reg 不行,因為 reg 是以程序方式寫入的。這是在 RTL 中還需要在意 net/變數區別的唯一實際理由,而 SystemVerilog 的 logic 型態連這個理由都消除了 — 只要恰好只有一個驅動源,兩者皆可。代入先になれるのは net だけである。reg はなれない。reg は手続き的に書き込まれるものだからである。これが RTL で net と変数の区別を気にする唯一の実務的理由であり、SystemVerilog の logic 型は、ドライバがちょうど一つであればどちらでもよいとすることでその理由さえ取り除いた。wand/wor (section 2.2) or the drivers are tri-states whose enables are provably exclusive.對同一條 net 做兩次連續指定就是多驅動源狀況。在模擬中由 net 型態來解析;在合成中則是錯誤,除非該 net 是 wand/wor(2.2 節),或那些驅動源是致能可被證明互斥的三態。同じ net への連続代入が 2 つあればマルチドライバ状態である。シミュレーションでは net の型が解決するが、合成ではエラーになる。ただしその net が wand/wor(2.2 節)である場合、またはドライバがイネーブルの排他性を証明できるトライステートである場合を除く。The second bullet on slide 18 — "no structural feedback allowed (no combinational loops)" — deserves more than its one line, because it is the rule that most often turns a design that simulates into a design that cannot be built.第 18 張投影片的第二條 —「no structural feedback allowed (no combinational loops)」— 值得多過一行的篇幅,因為它是最常把「模擬得出來的設計」變成「建不出來的設計」的規則。スライド 18 の二つ目の項目 —「no structural feedback allowed (no combinational loops)」— は 1 行以上の説明に値する。シミュレーションは通る設計を、構築できない設計に変えてしまう規則としてもっとも頻出するからである。
A combinational loop is a cycle in the network of gates with no storage element in it. assign y = y ^ a; is the simplest example. In the real world such a circuit is an oscillator, a latch or a metastable mess depending on the loop's delay and the gate functions, and which of those you get depends on process, voltage, temperature and layout — none of which the RTL expresses. A static timing analyser cannot even analyse it, because there is no path start and no path end; the path goes round forever. So synthesis tools break the loop arbitrarily, warn, and produce something that is probably not what you meant, or they error out.組合迴路是閘的網路中一個不含任何儲存元件的環。assign y = y ^ a; 是最簡單的例子。在真實世界裡,這樣的電路會是一個振盪器、一個閂鎖,或一團亞穩態的混亂,取決於迴路延遲與閘的功能,而究竟是哪一種又取決於製程、電壓、溫度與佈局 — 這些 RTL 都沒有表達。靜態時序分析器甚至無法分析它,因為沒有路徑起點也沒有路徑終點;路徑永遠繞下去。所以合成工具會任意打斷迴路、發出警告,產生出大概不是你想要的東西,或者乾脆報錯。組合せループとは、ゲートのネットワーク中にある記憶素子を含まない閉路のことである。assign y = y ^ a; がもっとも単純な例である。現実にはこうした回路はループ遅延とゲート関数に応じて発振器になったり、ラッチになったり、メタステーブルな混沌になったりする。そのどれになるかはプロセス、電圧、温度、レイアウトに依存し、そのどれも RTL は表現していない。静的タイミング解析器はこれを解析することすらできない。経路の始点も終点もなく、経路が永遠に回り続けるからである。したがって合成ツールはループを任意の場所で断ち切って警告を出し、おそらく意図とは違うものを生成するか、エラーで止まる。
The loops that bite in practice are not the obvious one-liners but the ones that appear through a chain of modules, or inside a case where one branch reads a variable the other branch writes:實務上會咬人的迴路,不是那種顯而易見的一行式,而是經由一串模組出現的迴路,或出現在 case 裡 — 其中一個分支讀取另一個分支所寫的變數:実務で噛みつくループは、分かりやすい 1 行ものではなく、モジュールの連鎖を通って現れるものや、一方の分岐が他方の分岐の書く変数を読む case の内部に現れるものである。
always @* begin
if (mode) y = a & b;
else y = y | c; // y on both sides, no edge: combinational loop
end
Note that this particular example is also a latch inference, and the two diagnoses are related: a latch is a deliberately controlled combinational loop (two cross-coupled gates with an enable). When a tool reports "latch inferred" on a line like this, the deeper problem is the feedback, and adding the missing else fixes both.請注意這個特定的例子同時也是一次閂鎖推論,而這兩個診斷是相關的:閂鎖就是一個被刻意控制的組合迴路(兩顆交叉耦合的閘加一個致能)。當工具對這樣的一行回報「latch inferred」時,更深層的問題是回授,而補上缺少的 else 會同時修好兩者。この例が同時にラッチ推論でもある点に注意してほしい。この二つの診断は関係している。ラッチとは意図的に制御された組合せループ(交差結合した 2 つのゲートとイネーブル)だからである。ツールがこのような行に「latch inferred」を報告するとき、より深い問題はフィードバックであり、欠けている else を補えば両方が直る。
There is one legitimate case: an intentional loop through a cell that the tool recognises as storage, such as a hand-instantiated latch, or a clock-gating cell. Those are instantiated, not described, exactly so that the loop lives inside a cell boundary where timing analysis can model it.有一個正當的例外:刻意讓迴路穿過一個工具認得是儲存元件的 cell,例如手動實例化的閂鎖或時脈閘控元件。那些是實例化而非描述出來的,正是為了讓迴路活在一個 cell 邊界之內,使時序分析能夠對它建模。正当なケースが一つある。ツールが記憶素子と認識するセル — 手でインスタンス化したラッチやクロックゲーティングセルなど — を通る意図的なループである。それらは記述ではなくインスタンス化される。まさにループをセル境界の内側に閉じ込め、タイミング解析がモデル化できるようにするためである。
Slide 19 shows one example of case and if together, and leaves the most important distinction implicit. An if/else if/else if chain describes a priority structure: conditions are tested in order, and an earlier true condition wins over a later one even if both are true. A case statement describes a selection, and if the items are mutually exclusive — which they are whenever the case expression is a single signal and the items are distinct constants — the structure is a balanced multiplexer. The two cost differently:第 19 張投影片把 case 與 if 放在同一個範例裡,卻把最重要的區別留在暗處。if/else if/else if 鏈描述的是一個優先權結構:條件依序測試,就算兩個條件同時為真,較早的那個也勝出。case 敘述描述的是一個選擇,而如果各個 item 互斥 — 只要 case 運算式是單一訊號而 item 是相異常數,它們就互斥 — 結構就是一個平衡多工器。兩者的成本不同:スライド 19 は case と if を一つの例にまとめ、もっとも重要な区別を暗黙のままにしている。if/else if/else if の連鎖は優先順位構造を記述する。条件は順に評価され、両方が真でも先の条件が後の条件に勝つ。case 文は選択を記述し、アイテムが互いに排他であれば — ケース式が単一の信号でアイテムが相異なる定数であれば常にそうなる — 構造は平衡したマルチプレクサになる。両者のコストは異なる。
| Construct構件構文 | Structure結構構造 | Depth for $k$ branches$k$ 個分支的深度$k$ 分岐での深さ | When to use何時使用使いどころ |
|---|---|---|---|
if/else if chainif/else if 鏈if/else if の連鎖 | Priority mux chain優先權多工器鏈優先順位マルチプレクサの連鎖 | $k-1$ mux levels$k-1$ 層多工器マルチプレクサ $k-1$ 段 | When the conditions really do have priority — interrupts, arbitration, error overrides條件真的有優先次序時 — 中斷、仲裁、錯誤覆寫条件に本当に優先順位があるとき — 割り込み、調停、エラーによる上書き |
case on one signal, distinct items對單一訊號做 case,item 相異単一信号への case、相異なるアイテム | Decoder + balanced mux解碼器 + 平衡多工器デコーダ + 平衡マルチプレクサ | $\lceil\log_2 k\rceil$ | Decoding an opcode, a state, a selector解碼 opcode、狀態或選擇子オペコード、状態、セレクタのデコード |
casez with overlapping itemsitem 重疊的 casezアイテムが重なる casez | Priority mux chain優先權多工器鏈優先順位マルチプレクサの連鎖 | $k-1$ | Priority encoders written compactly精簡地寫出優先編碼器プライオリティエンコーダを簡潔に書く |
The lecture's own identity_if_4bit example sits in an interesting middle position. It is written as an if/else if chain, which would normally imply priority, but every condition is an equality test on the same two-bit signal against a distinct constant, so the conditions are provably mutually exclusive. The optimiser proves it and produces a balanced 4-to-1 multiplexer — the slide labels the schematic "4-to-1 MUX" — and a second tool run produced the same function as plain AND-OR gates with sel[1], sel[0] and their complements. Same function, two mappings, both two levels deep. This is a good illustration of why you should not read a schematic as the answer; it is one covering among many.這一講自己的 identity_if_4bit 範例處在一個有趣的中間位置。它寫成 if/else if 鏈,照理應該隱含優先權,但每個條件都是拿同一個兩位元訊號去與相異常數做相等測試,所以這些條件可被證明互斥。最佳化器證明了這點,產生一個平衡的 4 對 1 多工器 — 投影片把那張電路圖標成「4-to-1 MUX」— 而第二次執行工具則把同一個函式做成單純的 AND-OR 閘,用的是 sel[1]、sel[0] 與它們的補數。同一個函式、兩種對應,都是兩層深。這很好地說明了為什麼你不該把一張電路圖讀成那個答案;它只是眾多覆蓋方式之一。講義自身の identity_if_4bit の例は興味深い中間的な位置にある。if/else if の連鎖として書かれており本来なら優先順位を含意するが、どの条件も同じ 2 ビット信号と相異なる定数との等価判定なので、条件は排他だと証明できる。最適化器はそれを証明して平衡した 4 対 1 マルチプレクサを生成し、スライドは回路図に「4-to-1 MUX」と記している。そして 2 回目のツール実行では、sel[1]、sel[0] とその補数を使った素の AND-OR ゲートとして同じ関数が得られた。同じ関数、二つのマッピング、どちらも深さ 2 段である。回路図を唯一の答えとして読むべきでない理由をよく示している。それは数ある被覆の一つにすぎない。
module identity_if_4bit (data_out, data_a, data_b, data_c, data_d, sel);
input [3:0] data_a, data_b, data_c, data_d;
input [1:0] sel;
output [3:0] data_out;
reg [3:0] data_out;
always @ (data_a or data_b or data_c or data_d or sel)
begin
if (sel == 2'b00) data_out = data_a;
else if (sel == 2'b01) data_out = data_b;
else if (sel == 2'b10) data_out = data_c;
else data_out = data_d;
end
endmodule
Two incidental points about this code, both worth noting because they date it. The sensitivity list is written out by hand with or separators; since IEEE 1364-2001 you should write always @* (or always @(*)), which cannot be incomplete and therefore cannot accidentally infer a latch or produce a pre-synthesis/post-synthesis mismatch — the standard hazard covered in Lecture 5. And the final else with no condition is what makes the block complete, so no latch is inferred; had the author written else if (sel == 2'b11) instead, data_out would need to hold its value when sel is x, and a latch would appear.關於這段程式碼還有兩個附帶的點,都值得一提,因為它們透露了它的年代。感度列表是用 or 分隔手寫出來的;自 IEEE 1364-2001 起你應該寫 always @*(或 always @(*)),它不可能不完整,因此不會意外推論出閂鎖,也不會造成合成前後的不匹配 — 那是第 5 講談過的標準風險。另外,最後那個沒有條件的 else 讓整個區塊變得完整,所以不會推論出閂鎖;如果作者寫的是 else if (sel == 2'b11),那麼當 sel 為 x 時 data_out 就必須保持原值,閂鎖就會出現。このコードについて付随する指摘が二つある。どちらも年代を物語るので触れておく価値がある。センシティビティリストが or 区切りで手書きされている。IEEE 1364-2001 以降は always @*(または always @(*))と書くべきである。これは不完全になりえないので、うっかりラッチを推論することも、合成前後の不一致を生むこともない — 第 5 講で扱った標準的な落とし穴である。また、条件のない最後の else がブロックを完全にしているのでラッチは推論されない。著者が代わりに else if (sel == 2'b11) と書いていたら、sel が x のとき data_out は値を保持せねばならず、ラッチが現れていたはずである。
Slide 19's module brings together four separate threads of this lecture, which is presumably why it was chosen.第 19 張投影片的模組把這一講四條獨立的脈絡匯聚在一起,想必這正是它被挑中的原因。スライド 19 のモジュールは、この講義の四つの別個の筋をまとめて扱っている。おそらくそれが選ばれた理由である。
module alu_reg_with_z1 (alu_out, en, a, b, s0, s1, s2, clk, reset);
output [1:0] alu_out;
input [1:0] a, b;
input en, s0, s1, s2, clk, reset;
reg [1:0] out_int;
assign alu_out = (en == 1) ? out_int : 2'bz;
always @ (posedge clk)
if (reset) out_int = 2'b0;
else
case ({s0, s1, s2})
3'b111: out_int = a & b;
3'b011: out_int = a | b;
3'b001: out_int = a ^ b;
default: out_int = 2'bx;
endcase
endmodule
Thread one: the tri-state. The continuous assignment with 2'bz in the else branch gives two tri-state output buffers between the internal register and the port, enabled by en, exactly as section 3.6 described.脈絡一:三態。else 分支為 2'bz 的連續指定,在內部暫存器與埠之間放了兩顆由 en 致能的三態輸出緩衝器,正如 3.6 節所述。筋その一:トライステート。else 分岐が 2'bz の連続代入は、内部レジスタとポートの間に en でイネーブルされるトライステート出力バッファを 2 個置く。3.6 節で述べたとおりである。
Thread two: the synchronous reset. reset is tested inside a block sensitive only to posedge clk, so it is a synchronous reset: the flip-flops are ordinary DFFs and the reset is a term in the combinational logic driving their D pins. Had the event control been @(posedge clk or posedge reset) the tool would have used the flip-flops' asynchronous reset pins instead and saved the datapath logic. Section 9.4 returns to this choice.脈絡二:同步重置。reset 是在一個只對 posedge clk 敏感的區塊內被測試的,所以它是同步重置:正反器是普通的 DFF,而 reset 只是驅動其 D 腳的組合邏輯裡的一項。如果事件控制寫成 @(posedge clk or posedge reset),工具就會改用正反器的非同步重置腳,省下資料路徑上的邏輯。9.4 節會回到這個選擇。筋その二:同期リセット。reset は posedge clk にのみ反応するブロックの内部で評価されるので、これは同期リセットである。フリップフロップは通常の DFF で、リセットはその D ピンを駆動する組合せ論理の一項にすぎない。イベント制御が @(posedge clk or posedge reset) であれば、ツールはフリップフロップの非同期リセットピンを使い、データパスの論理を節約していただろう。9.4 節でこの選択に立ち返る。
Thread three: the don't care. The three listed items are 111, 011 and 001, and the default is 2'bx. Of the eight possible codes, five are don't cares. Now look at the listed items: all three have s2 = 1 in the concatenation {s0,s1,s2}, meaning the last bit — which is s2 — is 1 in every case that matters. The optimiser is therefore free to choose, for each of the five unlisted codes, whatever output makes the logic smallest, and the smallest choice ignores s2 entirely and decodes on s0 and s1 alone: s0 s1 = 11 gives AND, 01 gives OR, 00 gives XOR, and 10 is free. That is why the slide's schematic shows a two-level AOI network driven by s0 and s1 and by nothing from s2.脈絡三:don't care。列出的三個 item 是 111、011 與 001,而 default 是 2'bx。八個可能的碼中有五個是 don't care。現在看看列出的 item:在串接 {s0,s1,s2} 中三者都是 s2 = 1,也就是說在每一個有意義的 case 裡,最後一個位元 — 也就是 s2 — 都是 1。因此最佳化器可以為那五個未列出的碼各自自由選擇讓邏輯最小的輸出,而最小的選擇是完全忽略 s2,只用 s0 與 s1 解碼:s0 s1 = 11 給 AND、01 給 OR、00 給 XOR,而 10 是自由的。這就是為什麼投影片的電路圖顯示一個由 s0 與 s1 驅動、完全沒有 s2 參與的兩層 AOI 網路。筋その三:ドントケア。列挙されたアイテムは 111、011、001 で、デフォルトは 2'bx である。8 通りのコードのうち 5 つがドントケアになる。列挙されたアイテムを見ると、連結 {s0,s1,s2} において 3 つとも s2 = 1 である。つまり意味のあるすべてのケースで最後のビット — すなわち s2 — は 1 である。したがって最適化器は、列挙されていない 5 つのコードそれぞれについて論理が最小になる出力を自由に選べ、その最小の選択は s2 を完全に無視して s0 と s1 だけでデコードすることである。s0 s1 = 11 なら AND、01 なら OR、00 なら XOR、10 は自由である。スライドの回路図が s0 と s1 に駆動され s2 からは何も受け取らない 2 段の AOI ネットワークを示しているのはこのためである。
Thread four: the retained port. And yet s2 is still there on the left edge of the schematic, as a port with an ESD pad cell attached and no load. This is the rule of section 2.1 in action, and the slide flags it explicitly — "note that unused input s2 is retained". The port is part of the interface; the interface is a contract; the contract survives optimisation.脈絡四:被保留的埠。然而 s2 仍然在電路圖的左緣,是一個接了 ESD pad 元件、卻沒有任何負載的埠。這就是 2.1 節那條規則在運作,而投影片也明確標出了它 —「note that unused input s2 is retained」。埠是介面的一部分;介面是契約;契約在最佳化中存活下來。筋その四:残されるポート。それでも s2 は回路図の左端にあり、ESD パッドセルが付いて負荷のないポートとして存在する。これは 2.1 節の規則が働いている姿であり、スライドも明示的に指摘している —「note that unused input s2 is retained」。ポートはインタフェースの一部であり、インタフェースは契約であり、契約は最適化を生き延びる。
Finding a dangling port in a synthesis report usually means one of two things: you wrote a don't care that was more generous than you intended, or you forgot to use a signal. The first is fine; the second is a bug. Check which before accepting the netlist, because the symptom is identical. The usual tell is whether the port appears in the RTL at all — s2 does, which points at the don't care; a port that appears only in the port list points at a missing connection.在合成報告裡看到懸空的埠,通常代表兩件事之一:你寫的 don't care 比你原本打算的更寬鬆,或者你忘了用某個訊號。前者沒問題,後者是錯誤。在接受 netlist 之前先確認是哪一種,因為兩者的症狀完全相同。通常的判別方式是看那個埠有沒有出現在 RTL 裡 — s2 有出現,指向 don't care;只出現在埠清單裡的埠則指向漏掉的連接。合成レポートで宙に浮いたポートを見つけたら、たいてい二つのうちのどちらかである。意図したより寛大なドントケアを書いたか、信号を使い忘れたかである。前者は問題なく、後者はバグである。症状は同一なので、ネットリストを受け入れる前にどちらかを確かめること。見分け方は、そのポートが RTL 中にそもそも現れるかどうかである。s2 は現れており、それはドントケアを示す。ポートリストにしか現れないポートは接続漏れを示す。
Problem: A three-bit selector s chooses among four operations, with four codes unused.問題:一個三位元選擇子 s 在四種運算之間做選擇,有四個碼未被使用。問題:3 ビットのセレクタ s が 4 つの演算を選択し、4 つのコードが未使用である。
always @* case (s) 3'b000: y = a + b; 3'b010: y = a - b; 3'b100: y = a & b; 3'b110: y = a | b; default: y = ???; endcase
(i) With default: y = 8'bx, which select bits can the optimiser drop, and why? (ii) With default: y = 8'b0, what extra logic appears? (iii) What is the risk of (i) if s comes from an asynchronous interface? (iv) Rewrite so that both the area of (i) and the determinism of (ii) are obtained.(i) 若 default: y = 8'bx,最佳化器可以捨棄哪些選擇位元,為什麼?(ii) 若 default: y = 8'b0,會多出什麼邏輯?(iii) 若 s 來自非同步介面,(i) 的風險是什麼?(iv) 請改寫,使 (i) 的面積與 (ii) 的確定性同時取得。(i) default: y = 8'bx のとき、最適化器はどの選択ビットを落とせるか、またその理由は。(ii) default: y = 8'b0 のとき、どんな論理が追加されるか。(iii) s が非同期インタフェースから来る場合、(i) のリスクは何か。(iv) (i) の面積と (ii) の決定性を同時に得られるよう書き換えよ。
(i) Every listed code has s[0] = 0, so the four codes with s[0] = 1 are all don't cares. The optimiser can therefore make the output independent of s[0] and decode on s[2:1] alone: a two-bit decoder and a 4-to-1 mux instead of a three-bit decode. On eight-bit data that saves roughly 8 muxes and a level of decode, perhaps 25 GE, and s[0] becomes a dangling port.(i) 列出的每個碼都有 s[0] = 0,所以 s[0] = 1 的那四個碼全是 don't care。因此最佳化器可以讓輸出與 s[0] 無關,只用 s[2:1] 解碼:一個兩位元解碼器加一顆 4 對 1 多工器,而不是三位元解碼。在八位元資料上,這省下大約 8 顆多工器與一層解碼、大概 25 GE,而 s[0] 變成懸空的埠。(i) 列挙されたコードはすべて s[0] = 0 なので、s[0] = 1 の 4 つのコードはすべてドントケアである。したがって最適化器は出力を s[0] に依存させず、s[2:1] だけでデコードできる。3 ビットのデコードではなく 2 ビットデコーダと 4 対 1 マルチプレクサになる。8 ビットのデータなら、マルチプレクサ約 8 個とデコード 1 段、およそ 25 GE を節約でき、s[0] は宙に浮いたポートになる。
(ii) The four unlisted codes must now produce exactly zero, so the logic needs s[0] after all: an extra AND term gating the whole output, about 8 GE on eight bits plus the decode of s[0], and a level of delay on the output path.(ii) 四個未列出的碼現在必須產生恰好為零的輸出,所以邏輯終究還是需要 s[0]:多出一個把整個輸出閘控住的 AND 項,八位元上約 8 GE,外加 s[0] 的解碼,以及輸出路徑上一層延遲。(ii) 列挙されていない 4 つのコードはちょうどゼロを出さねばならないので、論理は結局 s[0] を必要とする。出力全体をゲートする AND 項が追加され、8 ビットで約 8 GE、加えて s[0] のデコードと出力経路に 1 段の遅延が乗る。
(iii) If s crosses a clock domain or comes from a pad, it can transiently take an illegal value — including during the metastable settling of a synchroniser. With don't cares, the output during that window is whatever the optimiser chose, which may be a half-formed arithmetic result that propagates into an accumulator. With a specified default it is a known zero. For a control bus crossing a domain, specify the default.(iii) 如果 s 跨越時脈域或來自 pad,它可能短暫地取到非法值 — 包括在同步器的亞穩態安定過程中。有 don't care 時,那段時間窗內的輸出是最佳化器挑的任何東西,可能是一個半成形的算術結果,傳播進累加器裡。有指定 default 時,它是一個已知的零。對跨域的控制匯流排,請指定 default。(iii) s がクロックドメインをまたぐか、パッドから来る場合、一時的に不正な値を取りうる。シンクロナイザがメタステーブルから収束する途中も含まれる。ドントケアがあると、その窓の間の出力は最適化器が選んだ何かであり、中途半端な算術結果が累算器へ伝播することもある。デフォルトを指定してあれば既知のゼロである。ドメインをまたぐ制御バスにはデフォルトを指定すること。
(iv) Recode the selector so the unused codes do not exist. Use a two-bit s with four codes and no default needed, and do the decode-and-validate at the source. Then the case is complete by construction, nothing is wasted and nothing is undefined. This is almost always the right answer when you find yourself trading don't cares against determinism: the real fix is upstream, in the encoding.(iv) 重新編碼選擇子,讓未使用的碼根本不存在。用一個兩位元的 s 配四個碼,就不需要 default,並在來源端做好解碼與驗證。這樣 case 在結構上就是完整的,沒有浪費、也沒有未定義。當你發現自己在 don't care 與確定性之間做取捨時,這幾乎永遠是正確答案:真正的修法在上游,在編碼方式上。(iv) セレクタを符号化し直し、未使用のコードが存在しないようにする。2 ビットの s に 4 コードならデフォルトは不要で、デコードと妥当性検査は発生源側で行う。こうすれば case は構造上完全になり、無駄も未定義もない。ドントケアと決定性を天秤にかけている自分に気づいたとき、ほぼ常にこれが正解である。本当の修正は上流の符号化にある。
Slides 20 to 22 present three modules that look almost identical and synthesise to three different circuits. They are the clearest demonstration in the course of the fact that Verilog's procedural statements describe simulation scheduling, and that the hardware is inferred from the scheduling rather than from the text.第 20 到 22 張投影片呈現三個看起來幾乎一模一樣、卻合成出三種不同電路的模組。它們是本課程中最清楚的示範,說明 Verilog 的程序式敘述描述的是模擬排程,而硬體是從排程而非從文字推論出來的。スライド 20 から 22 は、ほとんど同一に見えて三つの異なる回路に合成される三つのモジュールを提示する。Verilog の手続き文がシミュレーションのスケジューリングを記述しており、ハードウェアはテキストではなくそのスケジューリングから推論されることを、本科目でもっとも明快に示す例である。
Strip it to one sentence each.各用一句話剝到見骨。それぞれ一文に削ぎ落とす。
x = e evaluates e and updates x immediately, before the next statement in the block runs. Subsequent reads of x in the same block, in the same pass, see the new value.阻塞式指定 x = e 會求值 e 並立刻更新 x,在區塊中下一道敘述執行之前就完成。同一區塊、同一次執行中後續對 x 的讀取會看到新值。ブロッキング代入 x = e は e を評価し、ブロック内の次の文が走る前にただちに x を更新する。同じブロックの同じパス内での以降の x の読み出しは新しい値を見る。x <= e evaluates e immediately but schedules the update of x for the end of the current time step. Every read of x anywhere during this time step, including later statements in the same block, sees the old value.非阻塞式指定 x <= e 會立刻求值 e,但把 x 的更新排程到當前時間步的結尾。在這個時間步內任何地方對 x 的讀取 — 包括同一區塊中較後面的敘述 — 看到的都是舊值。ノンブロッキング代入 x <= e は e を直ちに評価するが、x の更新は現在の時間ステップの終わりにスケジュールする。この時間ステップ中のどこで x を読んでも — 同じブロックの後続の文も含めて — 見えるのは古い値である。Translated into hardware terms: a blocking assignment in a clocked block behaves like a wire inside the clock period — the value flows through combinationally — while a non-blocking assignment behaves like the output of a flip-flop that will not change until the edge completes. That mapping is the whole of the following analysis.翻譯成硬體的說法:時脈區塊裡的阻塞式指定在一個時脈週期之內表現得像一條 wire — 值以組合方式流過去 — 而非阻塞式指定表現得像正反器的輸出,在邊緣完成之前不會改變。接下來的整個分析就建立在這個對應上。ハードウェアの言葉に訳せば、クロックブロック内のブロッキング代入はクロック周期の内側では wire のようにふるまい — 値が組合せ的に流れる — ノンブロッキング代入はエッジが完了するまで変わらないフリップフロップの出力のようにふるまう。以下の分析はすべてこの対応の上に立っている。
There is a second rule, less often stated, that explains the pipe1_alt case: assignments in different always blocks triggered by the same edge have no ordering guarantee at all. If one block writes a variable with = and another reads it at the same edge, the language does not say which happens first. That is a race, and the fact that synthesis resolves it one particular way while simulation may resolve it either way is precisely why the convention exists.還有第二條較少被明說的規則,它解釋了 pipe1_alt 的情況:由同一個邊緣觸發的不同 always 區塊之間,指定的先後完全沒有保證。如果一個區塊用 = 寫入某個變數,而另一個區塊在同一個邊緣讀它,語言並沒有規定誰先誰後。那就是一場競爭,而合成會以某一種特定方式解決它、模擬卻可能以任一種方式解決它 — 這正是那條慣例存在的理由。あまり明言されない第二の規則があり、これが pipe1_alt のケースを説明する。同じエッジで起動される異なる always ブロック間の代入には、順序の保証がまったくない。あるブロックが = で変数を書き、別のブロックが同じエッジでそれを読むとき、どちらが先かを言語は定めていない。それが競合であり、合成が特定の一通りに解決する一方でシミュレーションはどちらにも解決しうる。この慣習が存在する理由はまさにそこにある。
module pipe1 (data, rega, regb, regc, clk, reset);
input [1:0] data;
input clk, reset;
output [1:0] rega, regb, regc;
reg [1:0] rega, regb, regc;
always @ (posedge clk or posedge reset)
if (reset)
begin
rega = 2'b0;
regb <= 2'b0;
regc <= 2'b0;
end
else
begin
rega = data; // blocking: rega is now data
regb <= rega + 1; // reads the NEW rega = data
regc <= regb + 1; // reads the OLD regb
end
endmodule
Walk the else branch. rega = data updates rega at once. The next line reads rega, and because the previous assignment was blocking it sees data, so the scheduled value of regb is data + 1. The third line reads regb, and because regb was assigned non-blocking its update has not happened yet, so it sees the previous cycle's regb.走一遍 else 分支。rega = data 立刻更新 rega。下一行讀 rega,因為前一次指定是阻塞式的,所以它看到 data,於是 regb 被排程的值是 data + 1。第三行讀 regb,因為 regb 是非阻塞式指定、更新尚未發生,所以它看到的是前一週期的 regb。else 分岐をたどる。rega = data は rega を直ちに更新する。次の行は rega を読むが、直前の代入がブロッキングだったので data を見る。したがって regb にスケジュールされる値は data + 1 である。3 行目は regb を読むが、regb はノンブロッキングで代入され更新がまだ起きていないので、前サイクルの regb を見る。
The inferred hardware, which is what the slide's figure shows: reg_a is a register loaded directly from data; reg_b is a register loaded from an adder whose input is data, not from reg_a's output; reg_c is a register loaded from an adder on reg_b's output. So rega and regb are fed in parallel from data, and only regc is genuinely a pipeline stage behind. The design has three registers but only two pipeline depths.推論出來的硬體,也就是投影片圖上畫的:reg_a 是一個直接從 data 載入的暫存器;reg_b 是一個從加法器載入的暫存器,而那個加法器的輸入是 data,不是 reg_a 的輸出;reg_c 則是從作用在 reg_b 輸出上的加法器載入。所以 rega 與 regb 是並行地由 data 餵進去,只有 regc 真的落後一級。這個設計有三個暫存器,卻只有兩層管線深度。推論されるハードウェア、すなわちスライドの図が示すものはこうである。reg_a は data から直接ロードされるレジスタ。reg_b は加算器からロードされるレジスタだが、その加算器の入力は reg_a の出力ではなく data である。reg_c は reg_b の出力に作用する加算器からロードされる。つまり rega と regb は data から並列に供給され、本当に 1 段遅れているのは regc だけである。この設計はレジスタ 3 個を持つが、パイプライン深さは 2 段しかない。
module pipe1_alt (data, rega, regb, regc, clk, reset);
input [1:0] data;
input clk, reset;
output [1:0] rega, regb, regc;
reg [1:0] rega, regb, regc;
always @ (posedge clk or posedge reset)
if (reset) rega = 2'b0; else rega = data;
always @ (posedge clk or posedge reset)
if (reset)
begin
regb <= 2'b0;
regc <= 2'b0;
end
else
begin
regb <= rega + 1; // rega from the PREVIOUS cycle
regc <= regb + 1;
end
endmodule
The statements are textually the same, but rega now lives in a different block. The second block reads rega at the same edge at which the first block writes it. In hardware there is no ambiguity: rega is a flip-flop output, the adder feeding regb is fed from that output, and a flip-flop output at a clock edge is the value stored before the edge. So regb gets the previous cycle's rega plus one, and regc gets the previous cycle's regb plus one. This is a true three-stage pipeline, and the slide's annotation says exactly that: "rega is from the previous cycle, not the current one!"敘述在文字上完全相同,但 rega 現在住在另一個區塊裡。第二個區塊在第一個區塊寫 rega 的同一個邊緣讀它。在硬體上沒有任何歧義:rega 是一個正反器輸出,餵給 regb 的加法器接的是那個輸出,而正反器輸出在時脈邊緣上的值,是邊緣之前所儲存的值。所以 regb 得到的是前一週期的 rega 加一,regc 得到的是前一週期的 regb 加一。這是一條真正的三級管線,而投影片的註記說的正是這件事:「rega is from the previous cycle, not the current one!」文は字面上まったく同じだが、rega はいま別のブロックに属している。2 番目のブロックは、最初のブロックが rega を書くのと同じエッジでそれを読む。ハードウェアには曖昧さがない。rega はフリップフロップの出力であり、regb を駆動する加算器はその出力から供給される。クロックエッジにおけるフリップフロップ出力とは、エッジの前に保持されていた値である。したがって regb は前サイクルの rega に 1 を足した値を、regc は前サイクルの regb に 1 を足した値を得る。これは本物の 3 段パイプラインであり、スライドの注記もまさにそう述べている。「rega is from the previous cycle, not the current one!」
In RTL simulation the two always blocks are both triggered by posedge clk and the language does not define their order. If the first block runs first, its blocking assignment updates rega immediately and the second block reads the new value, producing pipe1's behaviour. If the second block runs first, it reads the old value and produces the pipeline. Both are legal simulator behaviour; IEEE 1364 explicitly permits either.在 RTL 模擬中,兩個 always 區塊都由 posedge clk 觸發,而語言並未定義它們的順序。如果第一個區塊先跑,它的阻塞式指定會立刻更新 rega,第二個區塊讀到的就是新值,產生 pipe1 的行為。如果第二個區塊先跑,它讀到舊值,產生管線的行為。兩者都是合法的模擬器行為;IEEE 1364 明確允許任一種。RTL シミュレーションでは 2 つの always ブロックがともに posedge clk で起動されるが、言語はその順序を定義していない。最初のブロックが先に走れば、そのブロッキング代入が rega を直ちに更新し、2 番目のブロックは新しい値を読んで pipe1 のふるまいになる。2 番目が先に走れば古い値を読んでパイプラインのふるまいになる。どちらもシミュレータとして合法であり、IEEE 1364 は明示的に両方を許している。
Synthesis is not ambiguous — it always builds the flip-flop, so the gate netlist always behaves like the pipeline. So this module can simulate one way at RTL and behave another way in silicon, depending on which simulator you use. Writing rega <= data; instead of rega = data; removes the race entirely and makes RTL and gates agree. This is the single strongest practical argument for the "non-blocking in clocked blocks" rule, and it is worth being able to state it.合成則沒有歧義 — 它永遠建出正反器,所以閘級 netlist 永遠表現得像管線。於是這個模組可能在 RTL 上以一種方式模擬,在矽上卻以另一種方式運作,端看你用哪個模擬器。把 rega = data; 改寫成 rega <= data; 就完全消除這場競爭,讓 RTL 與閘級一致。這是「時脈區塊用非阻塞式」規則最有力的實務論據,值得練到能說得出來。合成に曖昧さはない — 常にフリップフロップを作るので、ゲートネットリストは常にパイプラインとしてふるまう。したがってこのモジュールは、使うシミュレータ次第で RTL では一方のふるまいを示し、シリコンでは他方のふるまいをしうる。rega = data; を rega <= data; に書き換えれば競合は完全に消え、RTL とゲートが一致する。これが「クロックブロックではノンブロッキング」という規則の実務上もっとも強い論拠であり、言えるようにしておく価値がある。
module pipe2 (data, rega, regb, regc, clk, reset);
input [1:0] data;
input clk, reset;
output [1:0] rega, regb, regc;
reg [1:0] rega, regb, regc;
always @ (posedge clk or posedge reset)
if (reset)
begin rega = 2'b0; regb = 2'b0; regc = 2'b0; end
else
begin
rega = data;
regb = rega + 1;
regc = regb + 1;
end
endmodule
Every assignment is blocking, so every read sees the value just written. The slide draws out the reasoning as expression substitution: substituting forward gives rega = data, regb = data + 1, regc = data + 1 + 1, and once the statements are in that form the order no longer matters, so they are equivalent to three non-blocking assignments with those right-hand sides.每個指定都是阻塞式的,所以每次讀取看到的都是剛剛寫入的值。投影片把推理攤開成運算式代換:往前代換得到 rega = data、regb = data + 1、regc = data + 1 + 1,而一旦敘述變成這個形式,順序就不再重要,所以它們等價於三個以這些右手邊為值的非阻塞式指定。どの代入もブロッキングなので、どの読み出しも直前に書かれた値を見る。スライドはその推論を式の代入として展開する。前向きに代入していくと rega = data、regb = data + 1、regc = data + 1 + 1 となり、この形になれば順序はもはや関係しないので、これらはその右辺を持つ 3 つのノンブロッキング代入と等価である。
The hardware is three registers all loaded from data through different amounts of combinational logic: reg_a directly, reg_b through one incrementer, reg_c through two incrementers in series. All three are at pipeline depth one; the circuit is not a pipeline at all. Critically, the two incrementers are now in series within one clock period, so the critical path is twice as long as in pipe1_alt, where each incrementer sat in its own stage. For two-bit arithmetic that is irrelevant; for a thirty-two-bit datapath it would halve the achievable clock frequency.硬體是三個暫存器,全都從 data 經過不同數量的組合邏輯載入:reg_a 是直接接,reg_b 經過一個遞增器,reg_c 經過串聯的兩個遞增器。三者都在管線深度一;這個電路根本不是管線。關鍵在於,兩個遞增器現在是在同一個時脈週期內串聯,所以關鍵路徑是 pipe1_alt 的兩倍長 — 在 pipe1_alt 裡每個遞增器各自待在自己的一級。對兩位元算術來說這無關緊要;對三十二位元資料路徑來說,這會把可達成的時脈頻率砍半。ハードウェアは、いずれも data から異なる量の組合せ論理を経てロードされる 3 つのレジスタである。reg_a は直結、reg_b はインクリメンタ 1 個経由、reg_c は直列のインクリメンタ 2 個経由である。3 つともパイプライン深さ 1 であり、この回路はパイプラインですらない。重要なのは、2 つのインクリメンタがいまや同一クロック周期内で直列になっていることで、各インクリメンタが自分の段にいた pipe1_alt に比べてクリティカルパスは 2 倍になる。2 ビットの算術なら無関係だが、32 ビットのデータパスなら達成可能なクロック周波数が半分になる。
| Module模組モジュール | rega getsrega 得到rega が得る値 | regb getsregb 得到regb が得る値 | regc getsregc 得到regc が得る値 | Structure結構構造 | Longest combinational path最長組合路徑最長の組合せ経路 |
|---|---|---|---|---|---|
| pipe1pipe1pipe1 | data | data + 1 | old regb + 1舊的 regb + 1古い regb + 1 | rega, regb in parallel; regc one stage behindrega 與 regb 並行;regc 落後一級rega と regb が並列、regc が 1 段遅れ | 1 incrementer1 個遞增器インクリメンタ 1 個 |
| pipe1_altpipe1_altpipe1_alt | data | old rega + 1舊的 rega + 1古い rega + 1 | old regb + 1舊的 regb + 1古い regb + 1 | True three-stage pipeline真正的三級管線本物の 3 段パイプライン | 1 incrementer1 個遞增器インクリメンタ 1 個 |
| pipe2pipe2pipe2 | data | data + 1 | data + 2 | Three parallel registers, no pipeline三個並行暫存器,不是管線並列な 3 つのレジスタ、パイプラインではない | 2 incrementers in series2 個遞增器串聯直列のインクリメンタ 2 個 |
Three modules, essentially the same six lines, three different circuits. The lesson that matters more than any individual result is the method: to work out what a clocked block builds, rewrite every blocking assignment by forward substitution until no variable appears on both sides, then read the remaining right-hand sides as the D inputs of flip-flops. Variables that disappear entirely under substitution were wires, not registers; variables that still appear on a right-hand side after substitution are flip-flop outputs read from the previous cycle.三個模組、本質上同樣的六行、三種不同的電路。比任何單一結果都更重要的教訓是那個方法:要算出一個時脈區塊建出什麼,就把每個阻塞式指定以往前代換的方式重寫,直到沒有變數同時出現在兩側,然後把剩下的右手邊讀成正反器的 D 輸入。在代換下完全消失的變數是 wire 而不是暫存器;代換後仍出現在某個右手邊的變數,則是從前一週期讀到的正反器輸出。三つのモジュール、本質的に同じ 6 行、三つの異なる回路。個々の結果以上に重要な教訓はその方法である。クロックブロックが何を作るかを知るには、どの変数も両辺に現れなくなるまで各ブロッキング代入を前向き代入で書き換え、残った右辺をフリップフロップの D 入力として読めばよい。代入の過程で完全に消える変数はレジスタではなく配線であり、代入後もなお右辺に現れる変数は前サイクルから読まれるフリップフロップ出力である。
<= for everything that is to be a register.在時脈(邊緣敏感)區塊裡,凡是要變成暫存器的東西都用 <=。クロック(エッジ敏感)ブロックでは、レジスタになるべきものにはすべて <= を使う。@*) block, use =.在組合(位準敏感或 @*)區塊裡用 =。組合せ(レベル敏感または @*)ブロックでは = を使う。always blocks.絕不要在兩個不同的 always 區塊裡指定同一個變數。同じ変数を 2 つの異なる always ブロックで代入してはならない。= and <= on the same variable.絕不要對同一個變數混用 = 與 <=。同じ変数に対して = と <= を混用してはならない。Problem: After reset all three registers are 0. data takes the values 1, 2, 3 at clock edges 1, 2, 3. Give (rega, regb, regc) after each edge for pipe1, pipe1_alt and pipe2. Arithmetic is two-bit and wraps modulo 4. Then say which module you would write if the incrementers were thirty-two bits wide and the target frequency were aggressive.問題:重置後三個暫存器都是 0。data 在時脈邊緣 1、2、3 分別取值 1、2、3。請給出 pipe1、pipe1_alt 與 pipe2 在每個邊緣之後的 (rega, regb, regc)。算術是兩位元,對 4 取模回繞。然後說明:如果那些遞增器是三十二位元寬、目標頻率又很激進,你會寫哪一個模組。問題:リセット後、3 つのレジスタはすべて 0 である。data はクロックエッジ 1、2、3 でそれぞれ 1、2、3 の値を取る。pipe1、pipe1_alt、pipe2 のそれぞれについて、各エッジ後の (rega, regb, regc) を示せ。算術は 2 ビットで、法 4 でラップする。そのうえで、インクリメンタが 32 ビット幅で目標周波数が厳しい場合、どのモジュールを書くかを述べよ。
| Edge邊緣エッジ | datadatadata | pipe1pipe1pipe1 | pipe1_altpipe1_altpipe1_alt | pipe2pipe2pipe2 |
|---|---|---|---|---|
| 1 | 1 | (1, 2, 1) | (1, 1, 1) | (1, 2, 3) |
| 2 | 2 | (2, 3, 3) | (2, 2, 2) | (2, 3, 0) |
| 3 | 3 | (3, 0, 0) | (3, 3, 3) | (3, 0, 1) |
pipe1, edge 1: rega takes data = 1. regb takes the new rega + 1 = 2. regc takes the old regb (0) + 1 = 1.pipe1,邊緣 1:rega 取 data = 1。regb 取新的 rega + 1 = 2。regc 取舊的 regb(0)+ 1 = 1。pipe1、エッジ 1:rega は data = 1 を取る。regb は新しい rega + 1 = 2 を取る。regc は古い regb(0)+ 1 = 1 を取る。
pipe1_alt, edge 1: rega takes 1. regb takes the old rega (0) + 1 = 1. regc takes the old regb (0) + 1 = 1. Each subsequent edge shifts one stage along, which is why all three columns are equal from edge 2 onward for this particular input sequence — a coincidence of data incrementing by one each cycle, not a general property.pipe1_alt,邊緣 1:rega 取 1。regb 取舊的 rega(0)+ 1 = 1。regc 取舊的 regb(0)+ 1 = 1。之後每個邊緣都往前推一級,這就是為什麼對這個特定輸入序列而言,從邊緣 2 起三欄都相等 — 那是 data 每週期加一所造成的巧合,不是普遍性質。pipe1_alt、エッジ 1:rega は 1 を取る。regb は古い rega(0)+ 1 = 1 を取る。regc は古い regb(0)+ 1 = 1 を取る。以降のエッジごとに 1 段ずつずれていく。この特定の入力系列でエッジ 2 以降に 3 列すべてが一致するのはそのためだが、それは data が毎サイクル 1 ずつ増えることによる偶然であり、一般的な性質ではない。
pipe2, edge 1: everything is computed from the current data: 1, 2, 3. At edge 2, data = 2 gives 2, 3, 4 mod 4 = 0.pipe2,邊緣 1:一切都由當前的 data 算出:1、2、3。在邊緣 2,data = 2 給出 2、3、4 mod 4 = 0。pipe2、エッジ 1:すべてが現在の data から計算される。1、2、3 である。エッジ 2 では data = 2 なので 2、3、4 mod 4 = 0 となる。
Which to write. pipe1_alt, rewritten with <= throughout and the two blocks merged into one. It has one incrementer per stage, so the critical path is a single thirty-two-bit incrementer (about 25 levels for a ripple form, fewer for a prefix form); pipe2 would have two in series and roughly half the frequency; pipe1 is a confusing hybrid that no reader would correctly predict. The merged, all-non-blocking version is also race-free.該寫哪一個。pipe1_alt,但全部改寫成 <= 並把兩個區塊合併成一個。它每一級只有一個遞增器,所以關鍵路徑是單一個三十二位元遞增器(漣波形式約 25 層,前綴形式更少);pipe2 會有兩個串聯,頻率大約砍半;pipe1 則是一個沒有讀者能正確預測的混淆混合體。合併後的全非阻塞式版本同時也沒有競爭。どれを書くか。pipe1_alt を、全体を <= に書き換え、2 つのブロックを 1 つに統合したものである。段ごとにインクリメンタが 1 個なのでクリティカルパスは 32 ビットのインクリメンタ 1 個分(リップル形式で約 25 段、プレフィックス形式ならより少ない)になる。pipe2 は直列に 2 個あり周波数はおよそ半分、pipe1 はどんな読み手も正しく予測できない紛らわしい折衷である。統合したオールノンブロッキング版は競合も起こさない。
always @(posedge clk or posedge reset) if (reset) begin rega <= 0; regb <= 0; regc <= 0; end else begin rega <= data; regb <= rega + 1; regc <= regb + 1; end
Slide 23 makes three statements about # delays in synthesisable code, and all three point the same way.第 23 張投影片針對可合成程式碼中的 # 延遲講了三句話,三句都指向同一個方向。スライド 23 は合成可能なコードにおける # 遅延について三つのことを述べ、三つとも同じ方向を指している。
They are not advisable. A delay in Verilog is measured in time units set by `timescale, and the synthesiser has no mapping from time units to gate delays — the gate delays do not exist until after technology mapping, and they change with every process corner. So a delay cannot be honoured in any meaningful sense.不建議使用。Verilog 的延遲以 `timescale 設定的時間單位計量,而合成器沒有從時間單位到閘延遲的對應 — 閘延遲要到製程對應之後才存在,而且每個製程角落都不一樣。所以延遲在任何有意義的意義上都無法被遵守。推奨されない。Verilog の遅延は `timescale が定める時間単位で測られるが、合成器は時間単位からゲート遅延への対応を持たない。ゲート遅延はテクノロジマッピング後まで存在せず、プロセスコーナーごとに変わる。したがって遅延を意味ある形で守ることはできない。
Delays shorter than a clock cycle are ignored. This is the usual case and the merciful one. a <= #1 b; or #2 a = b; simply loses the # and becomes a <= b;. The RTL simulation shows a one-unit delay, the gate simulation shows the real cell delay, and the function is the same. The idiom q <= #1 d; is in fact deliberately used in some houses to make waveforms readable by separating the clock edge from the data change, and it is harmless precisely because synthesis ignores it. Lecture 3 covers the simulation semantics of inertial and transport delay in detail.短於一個時脈週期的延遲會被忽略。這是常見而且仁慈的情況。a <= #1 b; 或 #2 a = b; 就只是丟掉 #,變成 a <= b;。RTL 模擬顯示一個單位的延遲,閘級模擬顯示真實的元件延遲,而功能相同。q <= #1 d; 這個慣用寫法事實上在某些公司是刻意使用的,用來把時脈邊緣與資料變化分開以便看波形,而它之所以無害,正是因為合成會忽略它。第 3 講詳細講了慣性延遲與傳輸延遲的模擬語意。クロック周期より短い遅延は無視される。これが通常のケースであり、慈悲深いケースでもある。a <= #1 b; や #2 a = b; は単に # が落ちて a <= b; になる。RTL シミュレーションは 1 単位の遅延を示し、ゲートシミュレーションは実際のセル遅延を示すが、機能は同じである。q <= #1 d; という慣用句は、クロックエッジとデータ変化を分離して波形を読みやすくするために、一部の現場で意図的に使われている。合成がそれを無視するからこそ無害なのである。慣性遅延と伝送遅延のシミュレーション意味論は第 3 講が詳しく扱う。
Delays longer than a clock cycle may be turned into clock cycles. This is the dangerous case, and the slide's "Avoid!!" is earned. Some behavioural-synthesis-capable tools will interpret a long delay as a scheduling instruction and place the assignment in a later clock cycle, inserting state to do so. The result is that the circuit's latency depends on the timescale directive and on a tool option, and that changing `timescale 1ns/1ps to `timescale 1ps/1ps silently changes the hardware. Nothing about that is acceptable in a design you have to maintain.長於一個時脈週期的延遲可能被轉成時脈週期。這是危險的情況,投影片那句「Avoid!!」是實至名歸的。某些具備行為合成能力的工具會把長延遲解讀成排程指令,把該指定放到較晚的時脈週期,並為此插入狀態。結果是電路的延遲取決於 timescale 指令與某個工具選項,而把 `timescale 1ns/1ps 改成 `timescale 1ps/1ps 就會悄悄改變硬體。這在一個你必須維護的設計裡,沒有一點是可以接受的。クロック周期より長い遅延はクロックサイクルに変換されることがある。これが危険なケースで、スライドの「Avoid!!」はもっともである。振る舞い合成が可能な一部のツールは、長い遅延をスケジューリング指示と解釈し、その代入をより後のクロックサイクルに置き、そのために状態を挿入する。結果として回路のレイテンシが timescale 指令とツールのオプションに依存し、`timescale 1ns/1ps を `timescale 1ps/1ps に変えるだけでハードウェアが黙って変わる。保守しなければならない設計において、これはどこをとっても受け入れられない。
The rule is therefore simple: no # anywhere inside a module that will be synthesised. Delays belong in the test bench, where they are the entire mechanism for generating stimulus, and in gate-level library models, where they come from the SDF back-annotation rather than from the source.所以規則很簡單:任何將被合成的模組內部,任何地方都不要有 #。延遲屬於測試平台 — 在那裡它是產生激勵的全部機制 — 以及閘級庫模型,在那裡延遲來自 SDF 反標註而不是來自原始碼。したがって規則は単純である。合成されるモジュールの内部には、どこにも # を書かないこと。遅延はテストベンチに属し — そこでは刺激を生成する仕組みそのものである — またゲートレベルのライブラリモデルに属する。そこでの遅延はソースではなく SDF の逆アノテーションから来る。
Slides 24 and 25 give six rules governing the event control expression of a behaviour that is to become sequential logic. They are reproduced here in full because they are the most exam-tested part of the lecture, and then explained in the following subsection.第 24 與 25 張投影片給出六條規則,規範一個要變成循序邏輯的行為其事件控制運算式。這裡完整重現它們,因為這是這一講最常被考的部分,接著在下一小節解釋。スライド 24 と 25 は、順序論理になるべき振る舞いのイベント制御式を規定する 6 つの規則を与える。この講義でもっとも試験に出る部分なのでここに全文を再掲し、続く小節で説明する。
if chain or a ?:.與事件控制關聯的敘述應該是一個條件分支敘述 — 一條 if 鏈或一個 ?:。イベント制御に結び付く文は条件分岐文 — if の連鎖または ?: — であるべきである。posedge/negedge qualifiers there is exactly one clock edge; any number of further signals may appear, each individually edge-qualified, and all of them are asynchronous controls.在 posedge/negedge 修飾下恰好有一個時脈邊緣;可以再出現任意數量的其他訊號,每個各自帶邊緣修飾,而它們全都是非同步控制。posedge/negedge 修飾のもとではクロックエッジはちょうど一つである。他の信号はいくつ現れてもよく、各々個別にエッジ修飾されるが、それらはすべて非同期制御である。posedge and negedge in the same event control expression.同一個事件控制運算式中,沒有任何訊號可以同時被 posedge 與 negedge 修飾。同一のイベント制御式の中で、同じ信号を posedge と negedge の両方で修飾してはならない。The lecture's own example exercises rules 3 and 4:這一講自己的範例操演了規則 3 與規則 4:講義自身の例は規則 3 と規則 4 を実演している。
always @ (posedge x or posedge y or posedge z)
begin
if (x)
a <= 0; // x appears in the event control -> asynchronous clear
else if (y)
a <= 1; // y appears in the event control -> asynchronous set
// z is not mentioned here, and is assumed synchronous
else a <= b & c; // the one synchronous branch: z is the clock
end
Note what has happened: nothing in the code says "z is the clock". The tool deduces it, by elimination, from rule 3. The signal in the event control that is not tested in a branch condition must be the synchronising signal, because every other signal has declared itself asynchronous by appearing in a condition. This is why a signal listed in the sensitivity list but never referenced in the body is normally an error — the tool would have two candidate clocks and no way to choose.注意這裡發生了什麼:程式碼裡沒有任何一句說「z 是時脈」。工具是靠規則 3 以排除法推論出來的。事件控制中那個沒有被任何分支條件測試的訊號,必定是同步訊號,因為其餘每個訊號都因為出現在某個條件裡而宣告了自己是非同步的。這就是為什麼一個出現在感度列表、卻從未在主體中被引用的訊號通常是錯誤 — 工具會有兩個候選時脈,卻無從選擇。ここで何が起きたかに注目してほしい。コードのどこにも「z はクロックである」とは書かれていない。ツールは規則 3 によって消去法で推論する。イベント制御の中で分岐条件に現れない信号こそが同期用の信号に違いない。他のすべての信号は条件に現れることで自らを非同期だと宣言しているからである。センシティビティリストに挙げられていながら本体で一度も参照されない信号が通常エラーになるのはこのためである。ツールはクロックの候補を二つ持ち、選ぶ手立てがなくなる。
Every one of the six rules is a direct consequence of what a standard-cell flip-flop is. A library D flip-flop has a D input, a clock input, optionally an asynchronous set pin, optionally an asynchronous clear pin, and optionally a scan input and enable. That is the entire menu. The rules exist to make sure the Verilog you write can be expressed in that menu.這六條規則每一條都是「標準元件正反器是什麼」的直接結果。庫裡的 D 型正反器有一個 D 輸入、一個時脈輸入,選配一個非同步 set 腳、選配一個非同步 clear 腳,以及選配一個掃描輸入與致能。整張菜單就這些。這些規則的存在,是為了確保你寫的 Verilog 能用這張菜單表達出來。6 つの規則はどれも、スタンダードセルのフリップフロップが何であるかから直接導かれる。ライブラリの D フリップフロップは D 入力、クロック入力、任意で非同期セットピン、任意で非同期クリアピン、任意でスキャン入力とイネーブルを持つ。メニューはそれですべてである。規則は、書いた Verilog がそのメニューで表現できることを保証するために存在する。
| Rule規則規則 | The hardware fact behind it背後的硬體事實背後にあるハードウェアの事実 |
|---|---|
| 1. Must be a conditional branch1. 必須是條件分支1. 条件分岐でなければならない | The flip-flop's behaviour is a conditional: if clear, 0; else if set, 1; else D. Any other statement shape has no mapping.正反器的行為本身就是一個條件式:若 clear 則為 0;否則若 set 則為 1;否則為 D。任何其他敘述形狀都沒有對應。フリップフロップのふるまいそのものが条件である。クリアなら 0、そうでなくセットなら 1、そうでなければ D。他の文の形には対応物がない。 |
| 2. Exactly one synchronous part2. 恰好一個同步部分2. 同期部分はちょうど一つ | A flip-flop has one D input. Two synchronous branches would need two D inputs or two clocks.正反器只有一個 D 輸入。兩個同步分支會需要兩個 D 輸入或兩個時脈。フリップフロップの D 入力は一つである。同期分岐が二つあれば D 入力が二つ、あるいはクロックが二つ必要になる。 |
| 3. Unmentioned signal is the clock3. 沒被提到的訊號就是時脈3. 言及されない信号がクロック | Set and clear pins are level-sensitive once asserted, so they must be tested; the clock is not tested, it is the trigger.set 與 clear 腳一旦被設定就是位準敏感的,所以必須被測試;時脈不被測試,它是觸發源。セットとクリアのピンはアサートされればレベル敏感なので検査されねばならない。クロックは検査されず、トリガである。 |
| 4. Synchronous branch last4. 同步分支放最後4. 同期分岐は最後 | In the cell, asynchronous set and clear override the clocked path. Putting the synchronous branch earlier would describe a cell where D beats clear, which does not exist.在元件內部,非同步 set 與 clear 壓過時脈路徑。把同步分支放前面,描述的會是一顆 D 勝過 clear 的元件,那種元件不存在。セルの内部では非同期のセットとクリアがクロック経路を上書きする。同期分岐を先に置くと、D がクリアに勝つセルを記述することになるが、そんなセルは存在しない。 |
| 5. One clock edge only5. 只能有一個時脈邊緣5. クロックエッジは一つだけ | There is one clock pin. Two clock edges would be a dual-clock element, which no ordinary library contains.時脈腳只有一個。兩個時脈邊緣就是一個雙時脈元件,一般的庫裡沒有這種東西。クロックピンは一つである。クロックエッジが二つならデュアルクロック素子になるが、通常のライブラリにそのようなものはない。 |
| 6. Not both edges of one signal6. 不可同時用同一訊號的兩個邊緣6. 同じ信号の両エッジは不可 | A dual-edge-triggered flip-flop is a specialist cell present in almost no standard library. Describing one gives the tool nothing to map onto.雙邊緣觸發正反器是專門的元件,幾乎沒有標準庫會有。描述出一顆這樣的元件,工具沒有東西可以對應上去。両エッジトリガのフリップフロップは特殊なセルで、標準ライブラリにはほぼ存在しない。そうしたセルを記述しても、ツールにはマップする相手がない。 |
Rule 4 is the one students violate most often, usually by writing the enable first:規則 4 是學生最常違反的一條,通常是把致能寫在前面:規則 4 は学生がもっともよく破る規則で、たいていはイネーブルを先に書いてしまうことによる。
always @ (posedge clk or negedge rst_n) if (en) q <= d; else if (!rst_n) q <= 0;
This says: when the reset falls, if en is high, load d. No cell does that. Tools reject it or infer a gated clock.這句話說的是:當 reset 下降時,若 en 為高就載入 d。沒有任何元件會這樣做。工具會拒絕它,或推論出一個時脈閘控。これは「リセットが立ち下がったとき、en が High なら d をロードする」と言っている。そんなことをするセルはない。ツールはこれを拒否するか、ゲーテッドクロックを推論する。
always @ (posedge clk or negedge rst_n) if (!rst_n) q <= 0; else if (en) q <= d;
Reset always wins; the enable lives inside the single synchronous branch, where it becomes a mux on D or a clock-enable pin.reset 永遠勝出;致能住在那唯一的同步分支裡,在那裡它會變成 D 上的一顆多工器,或變成 clock-enable 腳。リセットが常に勝つ。イネーブルは唯一の同期分岐の中にあり、そこで D のマルチプレクサになるか、クロックイネーブルピンになる。
Rule 6 has a modern footnote. Dual-edge-triggered flip-flops do exist — they are used in double-data-rate interfaces — but they are instantiated as library primitives or built from two flip-flops and a multiplexer, never inferred from @(posedge clk or negedge clk). If you need DDR behaviour, write two separate always blocks, one on each edge, and combine their outputs with a mux on the clock; or instantiate the vendor's DDR primitive.規則 6 有一個現代的註腳。雙邊緣觸發正反器確實存在 — 它們用在雙倍資料率介面上 — 但它們是以庫原始元件的形式被實例化,或是用兩顆正反器加一顆多工器搭出來,絕不會從 @(posedge clk or negedge clk) 推論出來。如果你需要 DDR 行為,請寫兩個分開的 always 區塊,一邊一個邊緣,再用一顆由時脈控制的多工器把輸出合併;或者實例化廠商的 DDR 原始元件。規則 6 には現代的な脚注が付く。両エッジトリガのフリップフロップは実在し — ダブルデータレートのインタフェースで使われる — だがライブラリのプリミティブとしてインスタンス化されるか、フリップフロップ 2 個とマルチプレクサで組まれるかであって、@(posedge clk or negedge clk) から推論されることは決してない。DDR のふるまいが必要なら、各エッジに一つずつ計 2 つの always ブロックを書き、クロックで選ぶマルチプレクサで出力を合成するか、ベンダの DDR プリミティブをインスタンス化すること。
Slide 26 makes two points. The first — that an asynchronous reset is modelled inside the event control expression that detects the synchronising signal — is really just rule 5 restated: the reset earns its asynchronous status by being in the sensitivity list with an edge qualifier, which is what tells the tool to use the flip-flop's reset pin rather than build logic on D.第 26 張投影片講了兩點。第一點 — 非同步重置是在偵測同步訊號的那個事件控制運算式裡建模的 — 其實只是規則 5 的重述:重置是靠帶著邊緣修飾出現在感度列表裡,才取得它的非同步身分,而那正是告訴工具去用正反器的重置腳、而不是在 D 上建邏輯的依據。スライド 26 は二点を述べる。第一点 — 非同期リセットは同期信号を検出するイベント制御式の内部でモデル化される — は実のところ規則 5 の言い換えである。リセットはエッジ修飾付きでセンシティビティリストに現れることで非同期という身分を得る。それこそが、D 側に論理を作るのではなくフリップフロップのリセットピンを使えとツールに伝えるものである。
The choice between asynchronous and synchronous reset is a real engineering decision and worth laying out, because it is not on the slide and it comes up in every project review.非同步與同步重置之間的選擇是一個真實的工程決策,值得攤開來講,因為投影片上沒有,而它在每一次專案審查中都會出現。非同期リセットと同期リセットの選択は現実の設計判断であり、整理しておく価値がある。スライドには載っておらず、しかもあらゆるプロジェクトレビューで話題になるからである。
| Asynchronous reset非同步重置非同期リセット | Synchronous reset同步重置同期リセット | |
|---|---|---|
| RTL formRTL 寫法RTL の書き方 | @(posedge clk or negedge rst_n), tested first@(posedge clk or negedge rst_n),最先測試@(posedge clk or negedge rst_n)、最初に検査 | @(posedge clk), tested inside@(posedge clk),在內部測試@(posedge clk)、内部で検査 |
| Hardware硬體ハードウェア | Flip-flop's dedicated clear pin正反器的專用 clear 腳フリップフロップ専用のクリアピン | Extra AND/mux in the D pathD 路徑上多一顆 AND/多工器D 経路に AND/マルチプレクサを追加 |
| Datapath area資料路徑面積データパス面積 | None無なし | About 1 GE per flip-flop每顆正反器約 1 GEフリップフロップあたり約 1 GE |
| Flip-flop area正反器面積フリップフロップ面積 | A reset flip-flop is ~10 % larger than a plain one帶重置的正反器比普通的大約 10 %リセット付きフリップフロップは素のものより約 10 % 大きい | Plain flip-flop普通正反器素のフリップフロップ |
| Works with no clock?沒有時脈也能動?クロックなしで動くか | Yes — the chip resets before the PLL locks可以 — 晶片在 PLL 鎖定之前就完成重置可能 — PLL がロックする前にチップはリセットされる | No — needs a running clock不行 — 需要正在跑的時脈不可 — 動作中のクロックが必要 |
| Timing risk時序風險タイミングのリスク | Recovery and removal checks on de-assertion; must be released synchronously解除時要做 recovery 與 removal 檢查;必須同步釋放解除時にリカバリ/リムーバル検査が必要。同期的に解放せねばならない | Ordinary setup/hold, nothing special一般的 setup/hold,沒什麼特別通常の setup/hold のみ、特別なことはない |
| Glitch risk突波風險グリッチのリスク | A glitch on the reset net resets the chip重置線上的一個突波就會重置整顆晶片リセット線上のグリッチ一つでチップがリセットされる | A glitch is filtered by the clock突波會被時脈濾掉グリッチはクロックで濾過される |
| Typical choice典型選擇典型的な選択 | ASIC, where the clock may not be running at power-upASIC,上電時時脈可能還沒在跑ASIC。電源投入時にクロックが動いていない可能性がある | FPGA, and inside blocks where a glitch-free reset cannot be guaranteedFPGA,以及無法保證重置無突波的區塊內部FPGA、およびグリッチのないリセットを保証できないブロックの内部 |
The standard compromise, used in most ASIC flows including communication chips, is the asynchronous assert, synchronous de-assert reset synchroniser: the reset is applied asynchronously so that it works with no clock, and released through two flip-flops clocked by the destination clock so that the release edge is synchronous and the recovery/removal checks are trivially met. It costs two flip-flops per clock domain and removes an entire class of start-up failures.大多數 ASIC 流程(包括通訊晶片)採用的標準折衷,是非同步設定、同步解除的重置同步器:重置以非同步方式施加,因此沒有時脈也能作用;而解除則經過兩顆由目的端時脈驅動的正反器,使釋放邊緣是同步的,recovery/removal 檢查也就輕鬆滿足。它每個時脈域只花兩顆正反器,卻消除了一整類的啟動失效。通信チップを含むほとんどの ASIC フローで使われる標準的な妥協は、非同期アサート・同期デアサートのリセットシンクロナイザである。リセットは非同期にかけられるのでクロックがなくても効き、解放は宛先クロックで駆動される 2 個のフリップフロップを通るので解放エッジが同期的になり、リカバリ/リムーバル検査は自明に満たされる。クロックドメインあたりフリップフロップ 2 個のコストで、起動時故障の一群をまるごと取り除ける。
The second point on slide 26 — that unused set/reset pins on the selected library cells are tied to power or ground — is a small but real detail of the mapping pass. If the tool picks a DFFRPQ (D flip-flop with asynchronous reset and preset) because that is the only cell of the right drive strength available, and your code uses only the reset, then the preset pin must be tied inactive. The tie is to a rail through a tie-cell, not directly to the supply, because direct connections to power and ground from a signal pin cause antenna and ESD problems during manufacture. You will see TIEHI/TIELO cells in the netlist for exactly this reason, and they are why the gate count in a report is sometimes slightly higher than you expect.第 26 張投影片的第二點 — 所選庫元件上未使用的 set/reset 腳會被綁到電源或地 — 是對應階段一個小而真實的細節。如果工具因為那是唯一具備合適驅動能力的元件而挑了一顆 DFFRPQ(帶非同步 reset 與 preset 的 D 型正反器),而你的程式碼只用到 reset,那麼 preset 腳就必須被綁成無作用。綁定是透過 tie-cell 接到電源軌,而不是直接接到電源,因為訊號腳直接連到電源與地會在製造時造成天線效應與 ESD 問題。你會在 netlist 裡看到 TIEHI/TIELO 元件,原因正是如此,而它們也是報告中閘數有時比你預期略高的理由。スライド 26 の第二点 — 選ばれたライブラリセルの未使用のセット/リセットピンは電源またはグラウンドに固定される — はマッピングパスの小さいが現実的な細部である。適切な駆動力を持つセルがそれしかないという理由でツールが DFFRPQ(非同期リセットとプリセットを備えた D フリップフロップ)を選び、コードがリセットしか使っていなければ、プリセットピンは非アクティブに固定されねばならない。固定は電源に直結するのではなくタイセルを介して電源レールにつなぐ。信号ピンを電源やグラウンドに直結すると製造時にアンテナ効果と ESD の問題が生じるからである。ネットリストに TIEHI/TIELO セルが見えるのはまさにこの理由であり、レポートのゲート数が予想より少し多くなることがあるのもこのためである。
Problem: For each block, say whether it is acceptable for synthesis, identify the clock and any asynchronous controls, and name the broken rule where applicable.問題:對每一個區塊,說明它是否可被合成,指出時脈與任何非同步控制,並在適用時指出違反了哪一條規則。問題:各ブロックについて、合成可能かどうかを述べ、クロックと非同期制御を指摘し、該当する場合は破られている規則を挙げよ。
(a) always @ (posedge clk or negedge rst_n)
if (!rst_n) q <= 0; else q <= d;
(b) always @ (posedge clk or negedge rst_n)
if (en) q <= d; else if (!rst_n) q <= 0;
(c) always @ (posedge clk or negedge clk) q <= d;
(d) always @ (posedge clk or posedge set or posedge clr)
if (clr) q <= 0; else if (set) q <= 1; else q <= d;
(e) always @ (posedge clk or posedge rst) q <= d;
(f) always @ (posedge clk_a or posedge clk_b)
if (sel) q <= d1; else q <= d2;
(g) always @ (posedge clk or negedge rst_n)
if (!rst_n) q <= 0;
else if (load) q <= d;
else if (shift) q <= {q[6:0], 1'b0};
(a) Legal. Clock clk, asynchronous active-low reset rst_n. One flip-flop with a clear pin. The textbook form.(a) 合法。時脈 clk,低態有效的非同步重置 rst_n。一顆帶 clear 腳的正反器。教科書式的寫法。(a) 合法。クロックは clk、負論理の非同期リセット rst_n。クリアピン付きフリップフロップ 1 個。教科書どおりの形である。
(b) Illegal — rule 4. The synchronous branch (en, which is not in the event control) comes first and the asynchronous one second. The description implies a cell in which en overrides the asynchronous clear, which does not exist. Tools reject it, or in some cases infer a gated clock, which is worse because it passes.(b) 非法 — 違反規則 4。同步分支(en,它不在事件控制裡)排在前面,非同步分支排第二。這個描述隱含一顆讓 en 壓過非同步 clear 的元件,那並不存在。工具會拒絕它,某些情況下則會推論出一個時脈閘控 — 那更糟,因為它會通過。(b) 不正 — 規則 4 違反。同期分岐(イベント制御に現れない en)が先、非同期分岐が後になっている。この記述は en が非同期クリアを上書きするセルを含意するが、そんなものは存在しない。ツールは拒否するか、場合によってはゲーテッドクロックを推論する。通ってしまう分だけ後者のほうが悪い。
(c) Illegal — rule 6. clk is qualified by both edges. A dual-edge flip-flop is not in a normal library.(c) 非法 — 違反規則 6。clk 同時被兩個邊緣修飾。雙邊緣正反器不在正常的庫裡。(c) 不正 — 規則 6 違反。clk が両方のエッジで修飾されている。両エッジのフリップフロップは通常のライブラリにない。
(d) Legal. Clock clk; two asynchronous controls, with clr at higher priority than set because it is tested first. Maps to a DFF with both set and clear pins, with a small gate to enforce the priority when both assert.(d) 合法。時脈 clk;兩個非同步控制,其中 clr 因為先被測試而優先於 set。對應到一顆同時具備 set 與 clear 腳的 DFF,再加一顆小閘在兩者同時成立時強制優先次序。(d) 合法。クロックは clk。非同期制御が 2 つあり、先に検査される clr が set より優先される。セットとクリア両方のピンを持つ DFF にマップされ、両方がアサートされたときに優先順位を強制する小さなゲートが付く。
(e) Illegal — rule 3 has nothing to work with. rst is in the event control but never tested in a branch, so the tool has two candidate clocks and no asynchronous branch at all. Every tool errors here. Either remove rst from the list or test it first.(e) 非法 — 規則 3 無從施力。rst 在事件控制裡卻從未在任何分支中被測試,所以工具有兩個候選時脈,而且完全沒有非同步分支。每個工具都會在這裡報錯。請把 rst 從列表中移除,或把它放在最前面測試。(e) 不正 — 規則 3 が働く余地がない。rst はイベント制御にありながらどの分岐でも検査されないので、ツールはクロック候補を二つ抱え、非同期分岐が一つもない。どのツールもここでエラーを出す。rst をリストから外すか、最初に検査すること。
(f) Illegal — rule 5. Two clock edges on two different signals, neither tested in a condition, so both look like clocks. This is the classic failed attempt at a clock multiplexer; the correct structure is a glitch-free clock mux cell, instantiated, not described.(f) 非法 — 違反規則 5。兩個不同訊號上各有一個時脈邊緣,而且都沒有在條件中被測試,所以兩個看起來都像時脈。這是典型失敗的時脈多工器嘗試;正確的結構是一顆無突波的 clock mux 元件,用實例化而非描述的方式放進去。(f) 不正 — 規則 5 違反。異なる 2 つの信号にそれぞれクロックエッジがあり、どちらも条件で検査されないので両方がクロックに見える。クロックマルチプレクサを作ろうとして失敗する典型例である。正しい構造はグリッチフリーのクロックマルチプレクサセルを記述ではなくインスタンス化することである。
(g) Legal but incomplete, and that is fine. Clock clk, asynchronous rst_n. There is no final else, so when neither load nor shift is asserted q holds — which in an edge-sensitive block is exactly what a flip-flop does anyway, so no latch is inferred and nothing is wrong. The incompleteness rule of Lecture 5 applies only to combinational blocks. The inferred hardware is eight flip-flops with a 3-to-1 mux on each D pin, or an enable pin plus a 2-to-1 mux if the library has enabled flip-flops.(g) 合法但不完整,而這沒有問題。時脈 clk,非同步 rst_n。沒有最後的 else,所以當 load 與 shift 都未設定時 q 保持原值 — 而在邊緣敏感區塊裡,那本來就正是正反器會做的事,所以不會推論出閂鎖,也沒有任何問題。第 5 講的完整性規則只適用於組合區塊。推論出來的硬體是八顆正反器,每個 D 腳上一顆 3 對 1 多工器;若庫裡有帶致能的正反器,就是一個致能腳加一顆 2 對 1 多工器。(g) 合法だが不完全、そしてそれで問題ない。クロックは clk、非同期の rst_n。最後の else がないので load も shift も立っていないとき q は値を保持する — エッジ敏感ブロックではそれこそフリップフロップが本来行うことなので、ラッチは推論されず何の問題もない。第 5 講の完全性の規則は組合せブロックにのみ適用される。推論されるハードウェアはフリップフロップ 8 個で、各 D ピンに 3 対 1 マルチプレクサが付く。イネーブル付きフリップフロップがライブラリにあれば、イネーブルピンと 2 対 1 マルチプレクサになる。
The loop taxonomy on slide 27 is the single most quotable thing in Lecture 7, and the four counting-ones modules that follow it are the best worked example in the course. The taxonomy has two independent axes, which is why there are four types: is the loop static, and does it contain an internal timing control?第 27 張投影片的迴圈分類法是第 7 講裡最值得引用的東西,而緊接其後的四個「數 1 的個數」模組是本課程最好的實作範例。這個分類法有兩個獨立的軸,所以才有四種型別:迴圈是不是靜態的,以及它有沒有內部時序控制?スライド 27 のループ分類は第 7 講でもっとも引用に値するもので、それに続く 4 つの「1 の個数を数える」モジュールは本科目で最良の実例である。この分類には独立な軸が二つあり、だから 4 つの型になる。ループが静的か、そして内部タイミング制御を含むか、である。
A loop is static, or data-independent, when the compiler can determine the number of iterations at elaboration time. Operationally: the initial value, the termination condition and the increment must all be computable from constants and parameters, and the loop variable must not be modified anywhere inside the body. for (i = 0; i < 4; i = i + 1) is static. for (i = 0; i < n; i = i + 1) is static if n is a parameter and non-static if n is a signal. while (temp) where the body shifts temp is non-static, because the trip count depends on the data.當編譯器能在展開期判定迭代次數時,這個迴圈就是靜態的,也就是與資料無關。操作上來說:初始值、終止條件與增量都必須能從常數與參數算出來,而且迴圈變數不能在主體內任何地方被修改。for (i = 0; i < 4; i = i + 1) 是靜態的。for (i = 0; i < n; i = i + 1) 在 n 是參數時是靜態的,在 n 是訊號時是非靜態的。主體會移位 temp 的 while (temp) 是非靜態的,因為迭代次數取決於資料。ループが静的、すなわちデータ非依存であるとは、コンパイラがエラボレーション時に反復回数を決定できることをいう。実務的には、初期値、終了条件、増分がすべて定数とパラメータから計算可能で、かつループ変数が本体のどこでも変更されないことである。for (i = 0; i < 4; i = i + 1) は静的である。for (i = 0; i < n; i = i + 1) は n がパラメータなら静的、n が信号なら非静的である。本体が temp をシフトする while (temp) は、反復回数がデータに依存するので非静的である。
The significance of staticness is that only a static loop can be unrolled, and unrolling is the only way a loop can become combinational logic. A circuit has a fixed number of gates; a loop that runs a data-dependent number of times would need a data-dependent number of gates. There is no way around this, and it is not a tool limitation — it is a fact about physical objects.靜態性之所以重要,是因為只有靜態迴圈能被展開,而展開是迴圈變成組合邏輯的唯一途徑。電路的閘數是固定的;一個執行次數取決於資料的迴圈會需要取決於資料的閘數。這沒有任何繞道可走,而且這不是工具的限制 — 這是關於物理物體的事實。静的性が重要なのは、展開できるのは静的ループだけであり、展開こそがループが組合せ論理になる唯一の道だからである。回路のゲート数は固定である。実行回数がデータに依存するループは、データに依存する数のゲートを必要とする。これを回避する方法はなく、ツールの制限でもない — 物理的な物体についての事実である。
An internal timing control is an @(posedge clk), an @(some_signal) or a #delay inside the loop body. Its effect is to break the loop's work across time rather than across space: each iteration happens in a different clock cycle. This is behavioural synthesis — the tool must build an implicit state machine to sequence the iterations — and it is supported by only some tools and configurations, which is why the slide marks types II and IV "not recommended".內部時序控制指的是迴圈主體內部的 @(posedge clk)、@(some_signal) 或 #delay。它的效果是把迴圈的工作攤在時間上而不是空間上:每次迭代發生在不同的時脈週期。這就是行為合成 — 工具必須建一個隱含的狀態機來把迭代排序 — 而只有部分工具與部分設定支援它,這就是投影片把第 II 與第 IV 型標成「not recommended」的原因。内部タイミング制御とは、ループ本体の内部にある @(posedge clk)、@(some_signal)、#delay のことである。その効果はループの仕事を空間ではなく時間に広げることで、各反復が別のクロックサイクルで起こる。これは振る舞い合成であり — ツールは反復を順序付ける暗黙の状態機械を作らねばならない — 一部のツールと設定でしか対応されていない。スライドがタイプ II と IV を「not recommended」と記すのはそのためである。
| Type型別型 | Static?靜態?静的か | Internal timing?內部時序?内部タイミングは | Result結果結果 | Verdict評語評価 |
|---|---|---|---|---|
| I | Yes是はい | No否いいえ | Combinational logic by unrolling藉展開變成組合邏輯展開により組合せ論理になる | Recommended建議使用推奨 |
| IIIIII | Yes是はい | Yes是はい | Sequential logic, fixed cycle count循序邏輯,週期數固定順序論理、サイクル数は固定 | Not recommended不建議非推奨 |
| IIIIIIIII | No否いいえ | No否いいえ | Nothing Not synthesisable什麼也沒有 不可合成何もできない 合成不可 | Illegal in RTL在 RTL 中非法RTL では不正 |
| IVIVIV | No否いいえ | Yes是はい | Sequential logic, data-dependent cycle count循序邏輯,週期數取決於資料順序論理、サイクル数はデータ依存 | Not recommended不建議非推奨 |
Unrolling is textual substitution performed at elaboration. The tool replaces the loop with $N$ copies of the body, substituting the literal value of the loop index into each copy, and then discards the loop variable entirely. After unrolling there is no loop and no index; there is a straight-line sequence of assignments that the rest of the flow treats like any other.展開是在展開期執行的文字替換。工具把迴圈換成 $N$ 份主體複本,把迴圈索引的字面值代入每一份,然後把迴圈變數整個丟掉。展開之後沒有迴圈也沒有索引;剩下的是一串直線式的指定敘述,流程其餘部分就把它當成一般的敘述處理。展開とはエラボレーション時に行われるテキスト置換である。ツールはループを本体の $N$ 個のコピーに置き換え、ループインデックスのリテラル値を各コピーに代入し、ループ変数を完全に捨てる。展開後にはループもインデックスも存在せず、残るのは一直線の代入の並びで、以降のフローはそれを他と同様に扱う。
module for_and_loop_comb (out, a, b);
input [3:0] a, b;
output [3:0] out;
reg [2:0] i;
reg [3:0] out;
always @ (a or b)
begin
for (i = 0; i < 4; i = i + 1)
out[i] = a[i] & b[i];
end
endmodule
a[i] turns into free wiring rather than into a multiplexer.圖 5 — 迴圈展開。迭代次數已知,所以回邊被移除,主體以常數索引複製出來。每個陣列索引都變成字面常數,這就是為什麼 a[i] 變成免費的接線而不是多工器。図 5 — ループ展開。反復回数が既知なのでバックエッジが取り除かれ、本体が定数インデックスで複製される。配列インデックスはすべてリテラルになり、だからこそ a[i] はマルチプレクサではなく無料の配線になる。Three observations about this unrolling that generalise.關於這次展開有三點觀察,而且可以推廣。この展開について三つの観察があり、いずれも一般化できる。
The loop variable produces no hardware. i is declared reg [2:0], which in any other context would mean three flip-flops. Here it is consumed entirely at elaboration and does not appear in the netlist. That is why the counting-ones example can declare several apparently expensive registers and still synthesise to a handful of gates.迴圈變數不產生任何硬體。i 宣告成 reg [2:0],在其他任何情境下那都代表三顆正反器。這裡它在展開期就被完全消耗掉,不會出現在 netlist 裡。這就是為什麼數 1 的個數的範例可以宣告好幾個看似昂貴的暫存器,卻仍然合成成寥寥幾個閘。ループ変数はハードウェアを生まない。i は reg [2:0] と宣言されており、他の文脈ならフリップフロップ 3 個を意味する。ここではエラボレーション時に完全に消費され、ネットリストには現れない。1 の個数を数える例が高価そうなレジスタをいくつも宣言しながら数ゲートに合成される理由はここにある。
The index must be wide enough. i is three bits, not two, because the loop terminates by testing i < 4, which requires i to be able to hold 4. With reg [1:0] i the increment from 3 wraps to 0, the condition is always true, and the loop never terminates — in simulation it hangs, and at elaboration the tool either hangs or reports an iteration limit. This is an exam favourite and a real bug; use an integer for loop indices and the question never arises.索引必須夠寬。i 是三位元而不是兩位元,因為迴圈靠測試 i < 4 來終止,這要求 i 能夠保存 4。若用 reg [1:0] i,從 3 遞增會回繞成 0,條件永遠為真,迴圈永不終止 — 在模擬中它會卡住,在展開期工具要嘛卡住、要嘛回報迭代次數上限。這是考試的常客,也是真實的錯誤;迴圈索引用 integer,這個問題就永遠不會出現。インデックスは十分な幅が要る。i が 2 ビットではなく 3 ビットなのは、ループが i < 4 の判定で終了し、i が 4 を保持できる必要があるからである。reg [1:0] i では 3 からのインクリメントが 0 にラップし、条件は常に真となり、ループは終了しない。シミュレーションではハングし、エラボレーションではツールがハングするか反復回数の上限を報告する。試験の定番であり、現実のバグでもある。ループインデックスに integer を使えばこの問題は生じない。
Constant indices are free. After substitution, a[i] has become a[0], a[1], a[2], a[3] — four constant part selects, which are wires. Had the index remained a signal, each a[i] would have been a 4-to-1 multiplexer (see the last row of the table in section 6.9). The difference between a free wire and a mux per access is the main reason unrolling matters so much for area.常數索引是免費的。代換之後,a[i] 變成了 a[0]、a[1]、a[2]、a[3] — 四個常數部分選取,也就是線。如果索引仍然是訊號,每一個 a[i] 就會是一顆 4 對 1 多工器(見 6.9 節表格的最後一列)。免費的線與每次存取一顆多工器之間的差距,正是展開對面積如此重要的主因。定数インデックスは無料である。代入後、a[i] は a[0]、a[1]、a[2]、a[3] になる。4 つの定数部分選択、すなわち配線である。インデックスが信号のままなら、各 a[i] は 4 対 1 マルチプレクサになっていた(6.9 節の表の最終行を参照)。無料の配線とアクセスごとのマルチプレクサの差こそ、展開が面積にとってこれほど重要である主な理由である。
The slide's final note — that replacing @(a or b) with @(posedge clk) yields for_and_loop_reg — shows that unrolling is orthogonal to storage inference. Unrolling produces four AND gates either way; the event control then decides whether those gates drive outputs directly or drive four flip-flops. The slide's synthesis result shows exactly that: four AND gates in part (a), and the same four ANDs feeding four scan-enabled flip-flops in part (b).投影片最後那個註記 — 把 @(a or b) 換成 @(posedge clk) 就得到 for_and_loop_reg — 顯示展開與儲存推論是正交的。不論哪一種,展開都產生四顆 AND 閘;接著由事件控制決定那些閘是直接驅動輸出,還是驅動四顆正反器。投影片的合成結果正好顯示這點:(a) 部分是四顆 AND 閘,(b) 部分是同樣四顆 AND 餵進四顆帶掃描致能的正反器。スライドの最後の注記 — @(a or b) を @(posedge clk) に替えると for_and_loop_reg になる — は、展開が記憶の推論と直交することを示している。どちらにせよ展開は AND ゲートを 4 個生み、そのゲートが出力を直接駆動するかフリップフロップ 4 個を駆動するかはイベント制御が決める。スライドの合成結果はまさにそれを示す。(a) では AND ゲート 4 個、(b) では同じ AND 4 個がスキャンイネーブル付きフリップフロップ 4 個を駆動している。
The counting-ones example is where unrolling becomes genuinely interesting, because the body is not independent per iteration — each iteration reads what the previous one wrote.數 1 的個數這個範例是展開真正變得有趣的地方,因為它的主體不是每次迭代各自獨立 — 每次迭代都讀取前一次寫下的東西。1 の個数を数える例は展開が本当に面白くなる場面である。本体が反復ごとに独立ではなく、各反復が直前の反復の書いたものを読むからである。
module count_ones_a (bit_count, data, clk, reset);
parameter data_width = 4;
parameter count_width = 3;
output [count_width-1:0] bit_count;
input [data_width-1:0] data;
input clk, reset;
reg [count_width-1:0] count, bit_count, index;
reg [data_width-1:0] temp;
always @ (posedge clk)
if (reset) begin count = 0; bit_count = 0; end
else begin
count = 0;
bit_count = 0;
temp = data;
for (index = 0; index < data_width; index = index + 1) begin
count = count + temp[0];
temp = temp >> 1;
end
bit_count = count;
end
endmodule
Unroll it by hand. Substituting index = 0, 1, 2, 3 and then forward-substituting every blocking assignment:動手把它展開。代入 index = 0、1、2、3,再把每個阻塞式指定往前代換:手で展開してみよう。index = 0、1、2、3 を代入し、各ブロッキング代入を前向きに代入していく。
// after unrolling count = 0; temp = data; count = count + temp[0]; temp = temp >> 1; // iteration 0 count = count + temp[0]; temp = temp >> 1; // iteration 1 count = count + temp[0]; temp = temp >> 1; // iteration 2 count = count + temp[0]; temp = temp >> 1; // iteration 3 bit_count = count; // after forward substitution: every temp[0] resolves to a constant bit of data bit_count = data[0] + data[1] + data[2] + data[3];
The shifts vanish, because each one is a shift by a literal and the temp[0] that follows it is therefore a constant bit index into data. The intermediate count values vanish too, because each is written before it is read in the same pass. What is left is the sum of four one-bit numbers into a three-bit result — a population count, or in adder-tree language a (4,3) counter.移位消失了,因為每一次都是對字面常數的移位,於是其後的 temp[0] 就是對 data 的一個常數位元索引。中間的 count 值也消失了,因為每一個都在同一次執行中先寫後讀。剩下來的是把四個一位元數字加總成一個三位元結果 — 一個population count,用加法樹的語言說就是一個 (4,3) 計數器。シフトは消える。どれもリテラルによるシフトなので、続く temp[0] は data への定数ビットインデックスになるからである。途中の count の値も消える。どれも同じパス内で読まれる前に書かれているからである。残るのは 1 ビットの数 4 個を 3 ビットの結果に加算するもの — ポピュレーションカウント、加算器ツリーの言葉で言えば (4,3) カウンタである。
The cheapest construction is one full adder and two half adders. Let $\text{FA}(d_0,d_1,d_2)$ give sum $s$ and carry $c$; then add $d_3$ to $s$ with a half adder giving sum $u$ and carry $v$; the result is $\text{bit\_count} = \{c\wedge v,\ c\oplus v,\ u\}$, which needs one more half adder. Total: 1 FA + 2 HA $\approx$ 6 + 2×2 = 10 GE and three levels of logic. The slide's synthesis schematic shows precisely this shape — a handful of XOR, NAND and NOR gates — feeding three flip-flops.最便宜的構造是一顆全加器加兩顆半加器。令 $\text{FA}(d_0,d_1,d_2)$ 給出和 $s$ 與進位 $c$;接著用半加器把 $d_3$ 加到 $s$,得到和 $u$ 與進位 $v$;結果是 $\text{bit\_count} = \{c\wedge v,\ c\oplus v,\ u\}$,這還需要再一顆半加器。總計:1 顆 FA + 2 顆 HA $\approx$ 6 + 2×2 = 10 GE,三層邏輯。投影片的合成電路圖顯示的正是這個形狀 — 幾顆 XOR、NAND 與 NOR 閘 — 餵進三顆正反器。もっとも安い構成は全加算器 1 個と半加算器 2 個である。$\text{FA}(d_0,d_1,d_2)$ が和 $s$ と桁上げ $c$ を与えるとし、半加算器で $d_3$ を $s$ に加えて和 $u$ と桁上げ $v$ を得る。結果は $\text{bit\_count} = \{c\wedge v,\ c\oplus v,\ u\}$ で、これにもう 1 個半加算器が要る。合計で FA 1 個 + HA 2 個 $\approx$ 6 + 2×2 = 10 GE、論理 3 段である。スライドの合成回路図はまさにこの形 — XOR、NAND、NOR がいくつか — がフリップフロップ 3 個を駆動する姿を示している。
The module declares count, bit_count, index (three bits each) and temp (four bits): thirteen bits of apparent storage. The netlist contains three flip-flops. index is consumed by unrolling. temp and count are written before they are read on every path, so they are wires. Only bit_count is read by the outside world without being rewritten first in every pass — it is the module's output — so only it needs to hold a value across the edge. This is the Lecture 6 inference rule applied to an unrolled loop, and it is the single most useful thing to be able to do quickly in an exam.這個模組宣告了 count、bit_count、index(各三位元)與 temp(四位元):表面上有十三位元的儲存。而 netlist 裡只有三顆正反器。index 被展開吃掉了。temp 與 count 在每一條路徑上都先寫後讀,所以它們是線。只有 bit_count 會被外界讀取而不是在每次執行中先被改寫 — 它是模組的輸出 — 所以只有它需要跨越邊緣保存值。這是第 6 講的推論規則套用在展開後的迴圈上,而這也是考試中最值得能快速做出來的一件事。このモジュールは count、bit_count、index(各 3 ビット)と temp(4 ビット)を宣言しており、見かけ上 13 ビットの記憶がある。ネットリストに含まれるフリップフロップは3 個である。index は展開で消費される。temp と count はあらゆるパスで読まれる前に書かれるので配線である。毎パスで先に書き換えられることなく外部から読まれるのは bit_count だけ — モジュールの出力である — なので、エッジをまたいで値を保持する必要があるのはそれだけである。これは第 6 講の推論規則を展開済みループに適用したものであり、試験で素早くできるようにしておく価値がもっとも高い作業である。
Scaling matters here. For a general $n$-bit population count the best-known construction is a Wallace-style tree of full adders, costing roughly $n - \nu(n)$ full adders where $\nu(n)$ counts the ones in the binary representation of $n$, with depth $O(\log n)$. For $n=8$ that is about seven full adders, 45 GE, four levels; for $n=32$, about 31 full adders, 190 GE, five levels. So population count scales as $O(n)$ in area and $O(\log n)$ in delay — cheap. Writing it as the unrolled loop above gets you that automatically, provided the tool's arithmetic restructuring is enabled; a naive left-to-right chain of additions would instead give $O(n)$ depth, which is why the balanced-tree discussion of section 6.8 applies here too.這裡的尺度很重要。對一般 $n$ 位元的 population count,目前已知最好的構造是 Wallace 式的全加器樹,約花 $n - \nu(n)$ 顆全加器,其中 $\nu(n)$ 是 $n$ 的二進位表示中 1 的個數,深度為 $O(\log n)$。$n=8$ 時約七顆全加器、45 GE、四層;$n=32$ 時約 31 顆全加器、190 GE、五層。所以 population count 在面積上是 $O(n)$、延遲上是 $O(\log n)$ — 很便宜。寫成上面那個展開迴圈就會自動得到這個結果,前提是工具的算術重構有開啟;一條天真的由左到右加法鏈反而會給出 $O(n)$ 深度,這就是為什麼 6.8 節關於平衡樹的討論在這裡同樣適用。ここではスケーリングが重要である。一般の $n$ ビットのポピュレーションカウントで知られる最良の構成は Wallace 風の全加算器ツリーで、$n$ の 2 進表現中の 1 の個数を $\nu(n)$ とすると、およそ $n - \nu(n)$ 個の全加算器を要し、深さは $O(\log n)$ である。$n=8$ なら全加算器約 7 個、45 GE、4 段。$n=32$ なら約 31 個、190 GE、5 段である。つまりポピュレーションカウントは面積が $O(n)$、遅延が $O(\log n)$ — 安い。上記の展開ループとして書けばこれが自動的に得られる。ただしツールの算術再構成が有効であることが条件で、素朴な左から右への加算の連鎖では深さが $O(n)$ になる。6.8 節の平衡木の議論がここでも当てはまる理由である。
Put an @(posedge clk) inside a static loop and the meaning changes completely: the iterations are no longer replicated in space, they are sequenced in time.在靜態迴圈裡放一個 @(posedge clk),意義就完全改變了:迭代不再被複製到空間中,而是被排序到時間中。静的ループの中に @(posedge clk) を置くと意味はまったく変わる。反復はもはや空間に複製されず、時間の中に順序付けられる。
module count_ones_b (bit_count, data, clk, reset);
parameter data_width = 4;
parameter count_width = 3;
output [count_width-1:0] bit_count;
input [data_width-1:0] data;
input clk, reset;
reg [count_width-1:0] count, bit_count, index;
reg [data_width-1:0] temp;
always @ (posedge clk)
if (reset) begin count = 0; bit_count = 0; end
else begin
count = 0;
temp = data;
for (index = 0; index < data_width; index = index + 1)
@ (posedge clk)
begin
if (temp[0]) count = count + 1;
temp = temp >> 1;
end
@ (posedge clk)
bit_count = count;
end
endmodule
Now the behaviour is: on the triggering edge, clear count and load temp; then on each of the next four edges, add the low bit of temp to count and shift temp; then on a fifth further edge, publish count into bit_count. The total latency is five clocks after the starting edge, fixed, independent of the data.現在的行為是:在觸發邊緣上清除 count 並載入 temp;接著在之後四個邊緣的每一個上,把 temp 的最低位加到 count 並把 temp 移位;然後在再下一個第五個邊緣上,把 count 發布到 bit_count。總延遲是起始邊緣之後五個時脈,固定不變,與資料無關。ふるまいはこうなる。起動エッジで count をクリアし temp をロードする。続く 4 つのエッジそれぞれで temp の最下位ビットを count に加え temp をシフトする。さらに 5 つ目のエッジで count を bit_count に出す。総レイテンシは起動エッジから 5 クロックで固定、データには依存しない。
The hardware is a small sequential machine: a three-bit counter for count with an incrementer and an enable, a four-bit shift register for temp, a three-bit output register, and an implicit state machine with six states that sequences them. That state machine is not written anywhere in the source; the tool must synthesise it from the control flow. This is behavioural synthesis rather than RTL synthesis, and it is why the slide says "not recommended": many RTL synthesisers reject the construct outright, those that accept it differ in how they encode the implicit states, and the resulting latency is invisible in the source text.硬體是一台小型循序機器:count 用一個帶遞增器與致能的三位元計數器,temp 用一個四位元移位暫存器,一個三位元輸出暫存器,再加一台有六個狀態、負責把它們排序的隱含狀態機。那台狀態機在原始碼裡任何地方都沒有被寫出來;工具必須從控制流推論出來。這是行為合成而不是 RTL 合成,也是投影片說「not recommended」的原因:許多 RTL 合成器會直接拒絕這個構件,而接受它的工具在隱含狀態如何編碼上各不相同,產生的延遲在原始碼中也看不出來。ハードウェアは小さな順序機械である。count のためのインクリメンタとイネーブル付き 3 ビットカウンタ、temp のための 4 ビットシフトレジスタ、3 ビットの出力レジスタ、そしてそれらを順序付ける 6 状態の暗黙の状態機械である。その状態機械はソースのどこにも書かれていない。ツールが制御フローから合成せねばならない。これは RTL 合成ではなく振る舞い合成であり、スライドが「not recommended」と言う理由でもある。多くの RTL 合成器はこの構文を端から拒否し、受け付けるツールも暗黙の状態の符号化が異なり、生じるレイテンシはソーステキストから見えない。
Compare the two implementations honestly. Type I: 10 GE of combinational logic plus three flip-flops, one clock of latency, one result per clock. Type II: an incrementer, a shift register, a state machine — perhaps 60 GE — five clocks of latency, one result every six clocks. For four bits, Type I wins on every axis. The trade only begins to favour the sequential form at large widths, where the unrolled adder tree becomes genuinely big, and even then you would write the state machine explicitly rather than hope the tool infers the one you wanted.誠實地比較這兩種實作。第 I 型:10 GE 的組合邏輯加三顆正反器,延遲一個時脈,每個時脈出一個結果。第 II 型:一個遞增器、一個移位暫存器、一台狀態機 — 大概 60 GE — 延遲五個時脈,每六個時脈出一個結果。對四位元來說,第 I 型在每一個面向上都贏。只有在位寬很大、展開後的加法樹真的變大時,這個取捨才開始偏向循序形式,而即使那時,你也會自己明確寫出狀態機,而不是期待工具推論出你想要的那一台。二つの実装を正直に比べよう。タイプ I は組合せ論理 10 GE とフリップフロップ 3 個、レイテンシ 1 クロック、毎クロック 1 結果。タイプ II はインクリメンタ、シフトレジスタ、状態機械 — おそらく 60 GE — レイテンシ 5 クロック、6 クロックごとに 1 結果である。4 ビットではあらゆる軸でタイプ I が勝つ。このトレードオフが順序形式に傾き始めるのは、展開した加算器ツリーが本当に大きくなる広い幅のときだけで、しかもそのときでさえ、ツールが望みどおりの状態機械を推論してくれることを期待するより、自分で明示的に書くことになる。
module count_ones_c (bit_count, data, clk, reset);
parameter data_width = 4;
parameter count_width = 3;
output [count_width-1:0] bit_count;
input [data_width-1:0] data;
input clk, reset;
reg [count_width-1:0] count, bit_count, index;
reg [data_width-1:0] temp;
always @ (posedge clk)
if (reset) begin count = 0; bit_count = 0; end
else begin
count = 0;
temp = data;
for (index = 0; | temp; index = index + 1) begin // stops when temp == 0
if (temp[0]) count = count + 1;
temp = temp >> 1;
end
bit_count = count;
end
endmodule
One character has changed in substance: the termination condition is now |temp, the OR-reduction of a variable that the body modifies. The loop exits as soon as temp becomes zero, so it runs between zero and four times depending on the position of the highest set bit in data. This is a genuine optimisation in software — it skips the leading zeros — and it is completely unsynthesisable.實質上只改了一個字元:終止條件現在是 |temp,也就是對一個主體會修改的變數做 OR 歸約。迴圈一旦 temp 變成零就離開,所以它會依 data 中最高位 1 的位置執行零到四次。這在軟體裡是真正的最佳化 — 它跳過前導零 — 而且它完全不可合成。実質的に変わったのは 1 文字である。終了条件がいまや |temp、すなわち本体が書き換える変数の OR リダクションになった。temp がゼロになり次第ループを抜けるので、data の最上位の 1 の位置に応じて 0〜4 回実行される。ソフトウェアではこれは本物の最適化であり — 先頭のゼロを飛ばす — そしてまったく合成できない。
The slide's phrasing is exact and worth memorising: it is "essentially an iterative combinational circuit of data-dependent size". There is no clock in the loop, so all the iterations must happen within one clock period, which means they must all exist simultaneously as gates. But the number of iterations depends on data, which is a run-time value. A circuit cannot grow and shrink at run time. There is nothing to build.投影片的措辭很精確,值得背下來:它是「essentially an iterative combinational circuit of data-dependent size」。迴圈裡沒有時脈,所以所有迭代都必須發生在一個時脈週期之內,也就是說它們全都必須同時以閘的形式存在。但迭代次數取決於 data,那是個執行期的值。電路無法在執行時長大縮小。根本沒有東西可以建。スライドの言い回しは正確で、暗記する価値がある。それは「essentially an iterative combinational circuit of data-dependent size」である。ループ内にクロックがないので、すべての反復は 1 クロック周期内で起こらねばならず、つまりすべてが同時にゲートとして存在せねばならない。しかし反復回数は実行時の値である data に依存する。回路は実行時に伸び縮みできない。作るべきものが存在しないのである。
Note carefully what the tool can and cannot do here. It cannot unroll, because the trip count is unknown. It cannot build a state machine, because there is no timing control to give it clock boundaries. It cannot conservatively unroll four times and mask, because in general the bound is not known either — here temp is four bits so four is an upper bound, but the compiler is not required to perform that reasoning and standard RTL synthesis does not. So it reports an error, usually along the lines of "loop iteration limit exceeded" or "non-constant loop condition". The module simulates perfectly, which is what makes this trap worth remembering: a testbench will not catch it.請仔細看工具在這裡能做與不能做什麼。它不能展開,因為迭代次數未知。它不能建狀態機,因為沒有時序控制給它時脈邊界。它也不能保守地展開四次再加遮罩,因為一般而言上界同樣未知 — 這裡 temp 是四位元所以四是一個上界,但編譯器沒有義務做這種推理,標準 RTL 合成也不做。所以它會報錯,通常是「loop iteration limit exceeded」或「non-constant loop condition」之類。這個模組模擬得完美無缺,這正是這個陷阱值得記住的原因:測試平台抓不到它。ここでツールにできること・できないことをよく見てほしい。反復回数が不明なので展開できない。クロック境界を与えるタイミング制御がないので状態機械も作れない。保守的に 4 回展開してマスクすることもできない。一般には上限も不明だからである — ここでは temp が 4 ビットなので 4 が上限だが、コンパイラにその推論を行う義務はなく、標準的な RTL 合成はそれをしない。したがってエラーを出す。たいていは「loop iteration limit exceeded」や「non-constant loop condition」といった類である。このモジュールは完璧にシミュレーションされる。だからこそこの罠は覚えておく価値がある。テストベンチでは捕まらないのである。
Make it Type I by bounding the trip count and guarding the body: for (index = 0; index < data_width; index = index + 1) if (index <= highest) ..., or more simply drop the early exit entirely, since in hardware the "saved" iterations cost nothing to perform — the gates exist regardless. A software optimisation that avoids work has no analogue in combinational logic, where all the work happens in parallel anyway. Recognising that software instincts invert in hardware is half of learning RTL.把它變成第 I 型:限定迭代次數並為主體加上守衛,for (index = 0; index < data_width; index = index + 1) if (index <= highest) ...;或者更簡單,乾脆把提早離開整個拿掉,因為在硬體裡那些「省下來」的迭代執行起來根本不花成本 — 閘無論如何都存在。一個避免做工的軟體最佳化,在組合邏輯裡沒有對應物,因為所有工作反正都是平行發生的。認清軟體直覺在硬體裡會反轉,就學會了一半的 RTL。タイプ I にすればよい。反復回数を上限で縛り、本体をガードする。for (index = 0; index < data_width; index = index + 1) if (index <= highest) ... のようにするか、もっと単純に早期脱出をまるごと捨てる。ハードウェアでは「節約した」反復を実行してもコストはかからない — ゲートはいずれにせよ存在する。作業を省くソフトウェアの最適化は、すべての作業がどのみち並列に起こる組合せ論理には対応物を持たない。ソフトウェアの直感がハードウェアでは反転すると気づくことが、RTL を学ぶことの半分である。
module count_ones_d (bit_count, data, clk, reset);
parameter data_width = 4;
parameter count_width = 3;
output [count_width-1:0] bit_count;
input [data_width-1:0] data;
input clk, reset;
reg [count_width-1:0] count, bit_count;
reg [data_width-1:0] temp;
always @ (posedge clk)
if (reset) begin count = 0; bit_count = 0; end
else begin: bit_counter
count = 0;
temp = data;
while (temp)
@ (posedge clk)
if (reset) begin
count = 2'b0;
disable bit_counter;
end
else begin
count = count + temp[0]; // notice: no if needed
temp = temp >> 1;
end
@ (posedge clk)
if (reset) begin
count = 0;
disable bit_counter;
end
else bit_count = count;
end
endmodule
Adding the timing control rescues the non-static loop from impossibility, because the iterations are now sequenced in time and a state machine can run a data-dependent number of cycles. The circuit is finite; only its schedule varies. So Type IV is synthesisable in principle, by the same behavioural-synthesis machinery as Type II.加上時序控制,就把非靜態迴圈從「不可能」裡救了出來,因為迭代現在是在時間中排序,而狀態機可以執行與資料相關的週期數。電路是有限的;變動的只是它的排程。所以第 IV 型原則上可以合成,用的是和第 II 型相同的行為合成機制。タイミング制御を加えると非静的ループは不可能から救われる。反復が時間の中で順序付けられ、状態機械がデータ依存の回数だけサイクルを回せるからである。回路は有限で、変わるのはそのスケジュールだけである。したがってタイプ IV は原理的に合成可能であり、タイプ II と同じ振る舞い合成の機構によるものである。
Several details of this module repay study. The begin: bit_counter gives the block a name so that disable can target it; disable terminates the named block and, in this context, restarts the behaviour from the top at the next trigger. The reset test is repeated at every internal @(posedge clk), because once the behaviour has entered the loop, the outer if (reset) is no longer being evaluated — control is suspended inside the loop, waiting for an edge. Without those inner tests the module would ignore reset for up to five cycles. And the body uses count = count + temp[0] rather than if (temp[0]) count = count + 1, which the slide flags with "notice missing if" — the two are equivalent, and the arithmetic form is one gate cheaper since adding a bit is the same as conditionally adding one.這個模組有好幾個細節值得研究。begin: bit_counter 給這個區塊一個名字,好讓 disable 能指向它;disable 會終止這個具名區塊,在這個脈絡下則是在下一次觸發時讓行為從頭重新開始。重置測試在每一個內部 @(posedge clk) 處都重複出現,因為行為一旦進入迴圈,外層的 if (reset) 就不再被求值了 — 控制權懸停在迴圈內部,等待邊緣。沒有那些內層測試,這個模組最多會有五個週期忽略重置。另外主體寫的是 count = count + temp[0] 而不是 if (temp[0]) count = count + 1,投影片以「notice missing if」標出這點 — 兩者等價,而算術形式少一顆閘,因為加上一個位元就等於有條件地加一。このモジュールにはじっくり見る価値のある細部がいくつもある。begin: bit_counter はブロックに名前を与え、disable がそれを対象にできるようにする。disable は名前付きブロックを終了させ、この文脈では次の起動で振る舞いを先頭から再開させる。リセットの判定はすべての内部 @(posedge clk) で繰り返される。振る舞いがいったんループに入ると、外側の if (reset) はもはや評価されないからである — 制御はループ内部で停止し、エッジを待っている。これら内側の判定がなければ、モジュールは最大 5 サイクルのあいだリセットを無視する。また本体は if (temp[0]) count = count + 1 ではなく count = count + temp[0] と書かれており、スライドは「notice missing if」と注記している — 両者は等価で、ビットを足すことは条件付きで 1 を足すことと同じなので、算術形式のほうがゲート 1 個分安い。
The reason this is "not recommended" is the same as for Type II, plus one more: the latency is now data-dependent, so the block needs a handshake. Any consumer must be told when the result is valid, which means a done output and a protocol, none of which the implicit state machine provides. In practice you would write the FSM explicitly, with a busy and a valid, and then everything is visible and verifiable.這個型別「not recommended」的理由和第 II 型相同,再加一個:延遲現在取決於資料,所以這個區塊需要一套交握。任何消費端都必須被告知結果何時有效,也就是需要一個 done 輸出與一套協定,而隱含狀態機一個都沒提供。實務上你會把 FSM 明確寫出來,附上 busy 與 valid,這樣一切就都看得見、也驗得了。これが「not recommended」である理由はタイプ II と同じで、さらに一つ加わる。レイテンシがデータ依存になったので、このブロックにはハンドシェイクが必要である。消費側は結果がいつ有効かを知らされねばならず、つまり done 出力とプロトコルが要るが、暗黙の状態機械はそのどちらも提供しない。実務では FSM を busy と valid とともに明示的に書く。そうすればすべてが見え、検証もできる。
| Module模組モジュール | Type型別型 | Latency after the start edge起始邊緣後的延遲起動エッジ後のレイテンシ | Approx. hardware約略硬體概算ハードウェア | Synthesisable?可合成?合成可能か |
|---|---|---|---|---|
count_ones_a | I | 0 extra edges (result at the same edge)0 個額外邊緣(結果在同一邊緣出來)追加エッジ 0(同じエッジで結果が出る) | 1 FA + 2 HA + 3 FF, ~28 GE | Yes, everywhere是,到處都可以はい、どこでも |
count_ones_b | IIIIII | 5 extra edges, always5 個額外邊緣,固定常に追加エッジ 5 | Counter + shift reg + FSM, ~60 GE計數器 + 移位暫存器 + FSM,約 60 GEカウンタ + シフトレジスタ + FSM、約 60 GE | Only with behavioural synthesis只有行為合成才行振る舞い合成でのみ可能 |
count_ones_c | IIIIIIIII | — | — | No否いいえ |
count_ones_d | IVIVIV | 1 to 5 extra edges, data-dependent1 到 5 個額外邊緣,取決於資料追加エッジ 1〜5、データ依存 | Counter + shift reg + FSM + disable logic計數器 + 移位暫存器 + FSM + disable 邏輯カウンタ + シフトレジスタ + FSM + disable 論理 | Only with behavioural synthesis只有行為合成才行振る舞い合成でのみ可能 |
Problem: Give the type (I to IV), say whether it is synthesisable, and describe the hardware.問題:請給出型別(I 到 IV),說明它是否可合成,並描述其硬體。問題:型(I〜IV)を示し、合成可能かどうかを述べ、ハードウェアを説明せよ。
(a) for (k = 0; k < 8; k = k + 1) y[k] = x[7-k]; (b) while (q != 0) begin q = q - 1; n = n + 1; end (c) repeat (4) @(posedge clk) acc = acc + in; (d) while (busy) @(posedge clk) busy = check(status); (e) for (k = 0; k < WIDTH; k = k + 1) p = p ^ d[k]; // WIDTH is a parameter (f) for (k = 0; k < n; k = k + 1) s = s + d[k]; // n is an input port (g) forever @(posedge clk) q <= d;
(a) Type I, synthesisable. A bit reversal. After unrolling every index is a literal, so this is pure wiring: zero gates.(a) 第 I 型,可合成。一次位元反轉。展開之後每個索引都是字面常數,所以這純粹是接線:零個閘。(a) タイプ I、合成可能。ビット反転である。展開後はどのインデックスもリテラルなので、純粋な配線であり、ゲートはゼロ。
(b) Type III, not synthesisable. The trip count depends on q, which the body modifies, and there is no timing control. This is the archetype. Incidentally it computes n = n + q, which an adder does in one gate delay — a good illustration of how a software loop often hides a one-line hardware operation.(b) 第 III 型,不可合成。迭代次數取決於主體會修改的 q,而且沒有時序控制。這是原型。順帶一提它計算的是 n = n + q,而加法器一個閘延遲就做完了 — 很好地說明了一個軟體迴圈常常藏著一行硬體運算。(b) タイプ III、合成不可。反復回数は本体が書き換える q に依存し、タイミング制御もない。原型的な例である。ちなみにこれは n = n + q を計算しており、加算器ならゲート遅延 1 段で終わる。ソフトウェアのループがしばしば 1 行のハードウェア演算を隠していることのよい例である。
(c) Type II, synthesisable only by some tools. repeat (4) is static, and the body has an internal edge, so four accumulate cycles are sequenced. The hardware is an accumulator plus a two-bit cycle counter and a small FSM.(c) 第 II 型,只有部分工具能合成。repeat (4) 是靜態的,而主體有一個內部邊緣,所以四個累加週期被排序起來。硬體是一個累加器加一個兩位元週期計數器與一台小 FSM。(c) タイプ II、一部のツールでのみ合成可能。repeat (4) は静的で、本体に内部エッジがあるので 4 回の累算サイクルが順序付けられる。ハードウェアはアキュムレータと 2 ビットのサイクルカウンタ、そして小さな FSM である。
(d) Type IV. Non-static (busy is modified in the body) with timing control. Behavioural synthesis only; latency is data-dependent. Note also that check would have to be a function with no timing controls for this to have any chance.(d) 第 IV 型。非靜態(busy 在主體內被修改)且帶時序控制。只有行為合成才行;延遲取決於資料。另外請注意,check 必須是一個不含時序控制的 function,這件事才有一點機會成立。(d) タイプ IV。非静的(busy が本体で変更される)でタイミング制御あり。振る舞い合成でのみ可能で、レイテンシはデータ依存である。なお、これに少しでも望みがあるためには check がタイミング制御を含まない関数でなければならない。
(e) Type I, synthesisable. WIDTH is a parameter and so is known at elaboration. Unrolls into an XOR tree of WIDTH-1 gates: a parity generator. This is the correct and portable way to write a parameterised parity tree.(e) 第 I 型,可合成。WIDTH 是參數,所以在展開期就已知。展開成一棵有 WIDTH-1 顆閘的 XOR 樹:一個同位元產生器。這是撰寫參數化同位元樹正確而且可攜的方式。(e) タイプ I、合成可能。WIDTH はパラメータなのでエラボレーション時に既知である。WIDTH-1 個のゲートからなる XOR ツリー、すなわちパリティ生成器に展開される。パラメータ化されたパリティツリーを書く正しく可搬な方法である。
(f) Type III, not synthesisable. The only difference from (e) is that n is a port rather than a parameter, and that is the whole difference between a circuit and an error. If you need a variable-length sum, build a fixed-width adder tree and mask the unwanted terms with k < n, which is Type I plus $n$ AND gates.(f) 第 III 型,不可合成。與 (e) 的唯一差別是 n 是埠而不是參數,而那就是「一個電路」與「一個錯誤」之間的全部差別。如果你需要長度可變的加總,就建一棵固定位寬的加法樹,用 k < n 把不要的項遮掉,那就是第 I 型加上 $n$ 顆 AND 閘。(f) タイプ III、合成不可。(e) との唯一の違いは n がパラメータではなくポートであることで、それが回路とエラーを分ける差のすべてである。可変長の総和が必要なら、固定幅の加算器ツリーを作り、不要な項を k < n でマスクすればよい。それはタイプ I に AND ゲート $n$ 個を足したものである。
(g) Special case. forever with an internal timing control is the one unbounded loop that is accepted, because it is simply another spelling of always: the loop never terminates, it just waits on the edge each time round. Each iteration is one clock. In practice write always @(posedge clk).(g) 特例。帶內部時序控制的 forever 是唯一會被接受的無界迴圈,因為它不過是 always 的另一種拼法:迴圈永不終止,它只是每繞一圈就等一次邊緣。每次迭代就是一個時脈。實務上請寫 always @(posedge clk)。(g) 特殊ケース。内部タイミング制御付きの forever は、受理される唯一の非有界ループである。これは always の別の綴りにすぎない。ループは終了せず、毎周エッジを待つだけである。1 反復が 1 クロックに当たる。実務では always @(posedge clk) と書くこと。
Problem: Unroll the following and state the resulting hardware, the gate count and the number of flip-flops. d is [3:0], y is [3:0].問題:請展開下面這段並說明產生的硬體、閘數與正反器數量。d 是 [3:0],y 是 [3:0]。問題:次を展開し、生じるハードウェア、ゲート数、フリップフロップ数を述べよ。d は [3:0]、y は [3:0] である。
integer i;
reg [3:0] t, y;
always @(posedge clk) begin
t = d;
for (i = 0; i < 4; i = i + 1)
t[i] = t[i] ^ (i == 0 ? 1'b0 : t[i-1]);
y <= t;
end
Unroll with i = 0, 1, 2, 3, remembering that the assignments are blocking so each iteration sees the previous one's result:用 i = 0、1、2、3 展開,記得這些指定是阻塞式的,所以每次迭代都看得到前一次的結果:i = 0、1、2、3 で展開する。代入はブロッキングなので各反復が直前の反復の結果を見ることに注意する。
t = d; t[0] = t[0] ^ 1'b0; // = d[0] t[1] = t[1] ^ t[0]; // = d[1] ^ d[0] t[2] = t[2] ^ t[1]; // t[1] is ALREADY the new value: = d[2] ^ d[1] ^ d[0] t[3] = t[3] ^ t[2]; // = d[3] ^ d[2] ^ d[1] ^ d[0] y <= t;
The result is a prefix-XOR, also known as a Gray-to-binary converter: y[k] is the parity of d[k:0]. The trap is iteration 2 — because the assignments are blocking, t[1] on the right-hand side is the value written by iteration 1, not the original d[1]. A student who assumes the original value gets a per-bit XOR of neighbours (binary-to-Gray) instead, which is the inverse function.結果是一個前綴 XOR,也就是格雷碼轉二進位的轉換器:y[k] 是 d[k:0] 的同位元。陷阱在迭代 2 — 因為指定是阻塞式的,右手邊的 t[1] 是迭代 1 寫入的值,而不是原始的 d[1]。假設它是原始值的學生會得到相鄰位元的逐位元 XOR(二進位轉格雷碼),那是反函式。結果はプレフィックス XOR、すなわちグレイコードから 2 進への変換器である。y[k] は d[k:0] のパリティになる。罠は反復 2 にある — 代入がブロッキングなので、右辺の t[1] は元の d[1] ではなく反復 1 が書いた値である。元の値だと思い込んだ学生は隣接ビットごとの XOR(2 進からグレイへの変換)を得てしまうが、それは逆関数である。
Hardware. Three XOR2 gates in a chain (bit 0 is a wire), about 7 GE, three levels. Flip-flops: y is non-blocking and read outside, so four flip-flops; t is written before it is read on every path, so it is wires; i is consumed by unrolling. Total: 3 XOR2 + 4 DFF, about 31 GE.硬體。三顆 XOR2 閘串成一鏈(第 0 位是一條線),約 7 GE,三層。正反器方面:y 是非阻塞式指定且被外界讀取,所以是四顆正反器;t 在每條路徑上都先寫後讀,所以是線;i 被展開吃掉了。總計:3 顆 XOR2 + 4 顆 DFF,約 31 GE。ハードウェア。鎖状に並んだ XOR2 3 個(ビット 0 は配線)で約 7 GE、3 段。フリップフロップについては、y はノンブロッキング代入で外部から読まれるのでフリップフロップ 4 個。t はあらゆるパスで読まれる前に書かれるので配線。i は展開で消費される。合計:XOR2 3 個 + DFF 4 個、約 31 GE。
Follow-up worth noting. The XOR chain is three levels deep; for a 32-bit version it would be 31 levels, which is almost certainly too slow. Because XOR is associative, a prefix-XOR can be built as a Kogge–Stone-style prefix tree in $\log_2 n$ levels at $O(n\log n)$ gates — the same transformation that turns a ripple carry into a look-ahead carry. The RTL loop does not say which you get; the timing constraint does.值得一提的延伸。這條 XOR 鏈深三層;32 位元版本會是 31 層,那幾乎一定太慢。因為 XOR 可結合,前綴 XOR 可以建成 Kogge–Stone 式的前綴樹,$\log_2 n$ 層、$O(n\log n)$ 顆閘 — 這和把漣波進位變成超前進位是同一個變換。RTL 迴圈並沒有說你會拿到哪一種;決定的是時序限制。付け加えておく価値のある話。この XOR の連鎖は深さ 3 段である。32 ビット版なら 31 段になり、ほぼ確実に遅すぎる。XOR は結合的なので、プレフィックス XOR は Kogge–Stone 風のプレフィックスツリーとして $\log_2 n$ 段、$O(n\log n)$ ゲートで構築できる — リップルキャリーをキャリー先見に変えるのと同じ変換である。どちらが得られるかを決めるのは RTL のループではなくタイミング制約である。
Tasks and functions are Verilog's two subprogram forms, and slides 36 to 40 give their synthesis rules in six bullets. Underneath all six is one fact: both are expanded inline at elaboration, and nothing survives of the subprogram abstraction into the netlist.task 與 function 是 Verilog 的兩種副程式形式,第 36 到 40 張投影片以六條要點給出它們的合成規則。六條底下都是同一個事實:兩者都在展開期被內聯展開,副程式這層抽象沒有任何東西留進 netlist。タスクと関数は Verilog の二つのサブプログラム形式で、スライド 36〜40 がその合成規則を 6 項目で与える。6 つすべての底にあるのは一つの事実である。どちらもエラボレーション時にインライン展開され、サブプログラムという抽象はネットリストに何も残さない。
A task may have input, output and inout arguments, may contain timing controls, and may contain any number of statements. For synthesis the rules narrow that considerably:task 可以有 input、output 與 inout 引數,可以含時序控制,也可以含任意多道敘述。對合成而言,規則把這些收窄了不少:タスクは input、output、inout の引数を持て、タイミング制御を含んでよく、任意個数の文を含んでよい。合成に関しては規則がこれをかなり狭める。
inout arguments. An inout would require bidirectional dataflow through what, after inlining, is just a set of assignments. There is no way to express "this wire is both read and written by the same construct" without inferring either storage or a tri-state, and the tool will not guess which.不可有 inout 引數。inout 會要求資料在一個內聯後只是一組指定敘述的東西裡雙向流動。要表達「這條線被同一個構件同時讀又寫」,不推論出儲存元件就得推論出三態,而工具不會去猜是哪一種。inout 引数は不可。inout は、インライン展開後には単なる代入の集まりでしかないものの中に双方向のデータフローを要求する。「この配線は同じ構文によって読まれかつ書かれる」を表現するには記憶かトライステートのどちらかを推論するしかなく、ツールはどちらかを推測しない。@ and no # completes within the invoking process's time step, so it contributes only combinational logic. A task with timing controls carries the same implicit-state-machine problem as a Type II loop and the same "not recommended" status.沒有時序控制就代表是組合的。主體不含 @ 也不含 # 的 task 會在呼叫它的程序的同一個時間步內完成,所以它只貢獻組合邏輯。含有時序控制的 task 則帶著和第 II 型迴圈一樣的隱含狀態機問題,以及一樣的「not recommended」地位。タイミング制御がなければ組合せ回路になる。本体に @ も # も含まないタスクは、呼び出し元プロセスの同じ時間ステップ内で完了するので、組合せ論理しか生まない。タイミング制御を含むタスクはタイプ II ループと同じ暗黙の状態機械の問題を抱え、同じく「not recommended」の立場になる。The lecture's example wraps an addition in a task:這一講的範例把一次加法包進一個 task 裡:講義の例は加算をタスクに包んでいる。
module adder_task (c_out, sum, clk, reset, c_in, data_a, data_b);
input [3:0] data_a, data_b;
input clk, reset, c_in;
output [3:0] sum;
output c_out;
reg [3:0] sum;
reg c_out;
always @ (posedge clk or posedge reset)
if (reset) {c_out, sum} = 0;
else add_values (sum, c_out, data_a, data_b, c_in);
task add_values;
output [3:0] sum;
output c_out;
input [3:0] data_a, data_b;
input c_in;
reg [3:0] sum;
reg c_out;
begin
{c_out, sum} = data_a + (data_b + c_in);
end
endtask
endmodule
After inlining, the always block contains {c_out, sum} = data_a + (data_b + c_in); and nothing else. The parenthesisation is significant and is the grouping lesson of section 6.8 in miniature: data_b + c_in is computed first, then added to data_a, giving two adders in series. A smarter formulation would feed c_in into the carry-in of a single adder, which is free, instead of spending a whole second adder on it — and indeed the slide's synthesis result shows two five-bit adder symbols in series, exactly as written. Five flip-flops with asynchronous reset follow. So the module costs about 60 GE of adder where 30 would do.內聯之後,always 區塊裡就只有 {c_out, sum} = data_a + (data_b + c_in);,沒有別的。那個括號很重要,是 6.8 節分組教訓的縮影:data_b + c_in 先算,再加到 data_a,於是是兩個加法器串聯。更聰明的寫法會把 c_in 餵進單一個加法器的進位輸入,那是免費的,而不是為它花掉一整個第二個加法器 — 而投影片的合成結果確實顯示兩個串聯的五位元加法器符號,就照著寫的樣子。後面接著五顆帶非同步重置的正反器。所以這個模組在加法器上花了約 60 GE,而 30 GE 就夠了。インライン展開後、always ブロックに残るのは {c_out, sum} = data_a + (data_b + c_in); だけである。この括弧は重要で、6.8 節のグループ化の教訓の縮図になっている。data_b + c_in が先に計算され、それが data_a に加えられるので、加算器が 2 個直列になる。より賢い定式化は、2 つ目の加算器をまるごと費やす代わりに c_in を単一の加算器のキャリー入力へ入れることで、それは無料である。実際スライドの合成結果は書かれたとおり 5 ビット加算器のシンボルを 2 個直列に示している。その後に非同期リセット付きフリップフロップが 5 個続く。したがってこのモジュールは 30 GE で済むところに約 60 GE の加算器を費やしている。
Note also the asynchronous reset here — @(posedge clk or posedge reset) with reset tested first — which satisfies all six event-control rules of section 9.2, and which uses the flip-flops' clear pins rather than datapath logic.另外請注意這裡的非同步重置 — @(posedge clk or posedge reset) 且 reset 最先被測試 — 它滿足 9.2 節全部六條事件控制規則,而且使用的是正反器的 clear 腳而不是資料路徑邏輯。ここでの非同期リセットにも注目してほしい。@(posedge clk or posedge reset) で reset が最初に検査されており、9.2 節の 6 つのイベント制御規則をすべて満たし、データパス論理ではなくフリップフロップのクリアピンを使っている。
A function is more restricted than a task and correspondingly better behaved for synthesis. It has exactly one return value, returned by assigning to the function's own name; it may have only input arguments; it may not contain timing controls at all; and it must execute in zero simulation time. Those restrictions mean a function is necessarily combinational — there is no way to write one that is not.function 比 task 受限,也因此對合成而言表現得更好。它恰好有一個回傳值,透過指定給 function 自己的名字來回傳;它只能有 input 引數;它完全不能含時序控制;而且必須在零模擬時間內執行完畢。這些限制意味著 function 必然是組合的 — 根本沒有辦法寫出一個不是組合的 function。関数はタスクより制約が強く、その分だけ合成に対して行儀がよい。戻り値はちょうど一つで、関数自身の名前への代入によって返す。引数は input のみ。タイミング制御は一切含められない。そしてシミュレーション時間ゼロで実行を終えねばならない。これらの制約により関数は必然的に組合せ回路になる。そうでない関数を書く方法は存在しない。
The two synthesis rules on slide 38 follow:第 38 張投影片的兩條合成規則隨之而來:スライド 38 の二つの合成規則はここから導かれる。
case and if statements are not allowed. This is the latch rule from Lecture 5, applied with no escape hatch. In an ordinary combinational always block, an incomplete conditional infers a latch; in a function, a latch is impossible by definition, so the incompleteness is simply an error. The practical requirement is that the function name must be assigned on every path through the body.不允許不完整指定的 case 與 if 敘述。這是第 5 講的閂鎖規則,而且沒有任何逃生口。在一般的組合 always 區塊裡,不完整的條件式會推論出閂鎖;在 function 裡,閂鎖依定義不可能存在,所以不完整就只是個錯誤。實務上的要求是:function 的名字必須在主體的每一條路徑上都被指定。不完全に指定された case と if は許されない。これは第 5 講のラッチ規則を逃げ道なしで適用したものである。通常の組合せ always ブロックでは不完全な条件がラッチを推論するが、関数では定義上ラッチが不可能なので、不完全さは単なるエラーになる。実務上の要件は、関数名が本体のすべてのパスで代入されていることである。module arithmetic_unit (operand_1, operand_2, result_1, result_2);
input [3:0] operand_1, operand_2;
output [4:0] result_1;
output [3:0] result_2;
assign result_1 = sum_of_operands (operand_1, operand_2);
assign result_2 = largest_operand (operand_1, operand_2);
function [4:0] sum_of_operands;
input [3:0] operand_1, operand_2;
sum_of_operands = operand_1 + operand_2;
endfunction
function [3:0] largest_operand;
input [3:0] operand_1, operand_2;
largest_operand = (operand_1 >= operand_2) ? operand_1 : operand_2;
endfunction
endmodule
Both functions assign their name unconditionally, so both are complete. sum_of_operands inlines to a five-bit addition — note that the function's own declared width, [4:0], sets the context width, so the carry is kept, which is why result_1 can be five bits. largest_operand inlines to a comparator driving four two-to-one multiplexers. The slide's synthesis result shows exactly that: a block of comparison gates on the left feeding four mux_2a cells for result_2, and a separate adder symbol for result_1. Total cost roughly 30 GE for the adder, 10 GE for the comparator and 10 GE for the muxes.兩個 function 都無條件地指定自己的名字,所以兩者都是完整的。sum_of_operands 內聯成一次五位元加法 — 注意 function 自己宣告的位寬 [4:0] 設定了情境位寬,所以進位被保留下來,這就是 result_1 可以是五位元的原因。largest_operand 內聯成一個比較器驅動四顆二對一多工器。投影片的合成結果正是如此:左邊一團比較閘餵進四顆 mux_2a 元件給 result_2,另外一個獨立的加法器符號給 result_1。總成本大約是加法器 30 GE、比較器 10 GE、多工器 10 GE。どちらの関数も自分の名前を無条件に代入しているので、どちらも完全である。sum_of_operands は 5 ビットの加算にインライン展開される — 関数自身の宣言幅 [4:0] が文脈幅を決めるのでキャリーが保たれ、result_1 が 5 ビットでありうる理由になっている。largest_operand は比較器が 2 対 1 マルチプレクサ 4 個を駆動する形に展開される。スライドの合成結果はまさにそれで、左側の比較ゲートの塊が result_2 用の mux_2a セル 4 個を駆動し、result_1 用には別の加算器シンボルがある。総コストは加算器が約 30 GE、比較器が約 10 GE、マルチプレクサが約 10 GE である。
A safe habit for completeness is to assign a default at the top of the function and then override:確保完整性的一個安全習慣,是在 function 開頭先指定一個預設值,之後再覆寫:完全性を確保する安全な習慣は、関数の先頭でデフォルトを代入し、その後で上書きすることである。
function [1:0] enc;
input [3:0] r;
case (r)
4'b0001: enc = 2'd0;
4'b0010: enc = 2'd1;
4'b0100: enc = 2'd2;
endcase // no default!
endfunctionfunction [1:0] enc;
input [3:0] r;
begin
enc = 2'd0; // default first
case (r)
4'b0010: enc = 2'd1;
4'b0100: enc = 2'd2;
4'b1000: enc = 2'd3;
endcase
end
endfunctionThe consequence of inlining that catches people out is that subprograms do not share hardware. A software function called eight times executes eight times on one piece of silicon; a Verilog function called eight times produces eight copies of its logic.內聯造成的、最容易讓人栽跟頭的後果是:副程式不會共用硬體。一個被呼叫八次的軟體函式,是在同一塊矽上執行八次;一個被呼叫八次的 Verilog function,則產生八份它的邏輯。インライン展開の帰結で人がもっとも足をすくわれるのは、サブプログラムがハードウェアを共有しないことである。8 回呼ばれるソフトウェアの関数は 1 枚のシリコン上で 8 回実行されるが、8 回呼ばれる Verilog の関数はその論理のコピーを 8 個生む。
assign y0 = big_multiply(a0, b0);
assign y1 = big_multiply(a1, b1);
assign y2 = big_multiply(a2, b2);
assign y3 = big_multiply(a3, b3);
// FOUR multipliers, not one.
If the four multiplications can happen in parallel, four multipliers is what you want and the function is simply a clean way to describe them. If they are meant to be sequential uses of one shared unit, a function is exactly the wrong construct: you need a single instance, a multiplexer on its inputs, a register on its output and a controller to sequence the four operations. That is resource sharing, and it is a structural decision you must make yourself — synthesis will share small operators automatically when they are in mutually exclusive branches of a conditional, but it will not invent a controller.如果那四次乘法可以同時發生,四個乘法器就正是你要的,而 function 只是描述它們的一種乾淨寫法。如果它們本來是要循序使用同一個共用單元,那 function 就是完全錯誤的構件:你需要單一個實例、輸入上的一顆多工器、輸出上的一個暫存器,以及一個把四次運算排序的控制器。那叫資源共享,而它是你必須自己做的結構決策 — 當運算位於某個條件式的互斥分支中時,合成會自動共用小型運算子,但它不會憑空發明控制器。4 回の乗算が並列に起こってよいなら、乗算器 4 個こそ望むものであり、関数はそれを簡潔に記述する手段にすぎない。だが一つの共有ユニットを順に使うつもりだったのなら、関数はまったく誤った構文である。必要なのは単一のインスタンス、その入力のマルチプレクサ、出力のレジスタ、そして 4 つの演算を順序付けるコントローラである。それがリソース共有であり、自分で下さねばならない構造上の判断である — 条件文の排他的な分岐にある小さな演算子なら合成が自動で共有するが、コントローラを発明することはない。
A synthesiser will share an expensive operator between branches of the same conditional, because then the operations are provably mutually exclusive: y = sel ? (a*b) : (c*d); can become one multiplier with muxes on its inputs, trading two multipliers (~900 GE at eight bits) for one multiplier plus sixteen muxes (~490 GE). This is the resource_sharing optimisation, on by default in most tools. It cannot cross a clock boundary and it cannot cross separate continuous assignments, which is why the four-call example above does not benefit.合成器會在同一個條件式的分支之間共用昂貴的運算子,因為那時這些運算可被證明互斥:y = sel ? (a*b) : (c*d); 可以變成一個乘法器加上輸入端的多工器,用「一個乘法器加十六顆多工器(八位元時約 490 GE)」換掉「兩個乘法器(約 900 GE)」。這就是 resource_sharing 最佳化,在多數工具中預設開啟。它無法跨越時脈邊界,也無法跨越分開的連續指定,這就是為什麼上面那個四次呼叫的例子得不到好處。合成器は同一の条件文の分岐のあいだでは高価な演算子を共有する。そのときの演算は排他だと証明できるからである。y = sel ? (a*b) : (c*d); は入力にマルチプレクサを付けた乗算器 1 個になりうる。乗算器 2 個(8 ビットで約 900 GE)を乗算器 1 個とマルチプレクサ 16 個(約 490 GE)に置き換える取引である。これが resource_sharing 最適化で、ほとんどのツールで既定で有効である。クロック境界をまたげず、別々の連続代入もまたげない。上の 4 回呼び出しの例が恩恵を受けない理由である。
Problem: For each requirement, choose task, function, module instance or none, and justify in terms of the synthesis rules.問題:對每一項需求,選擇 task、function、模組實例或都不是,並依合成規則說明理由。問題:各要件について、タスク・関数・モジュールインスタンス・いずれでもない、のどれかを選び、合成規則に基づいて理由を述べよ。
1. Function. One return value, no timing, combinational: parity = ^word; inlines to a fifteen-gate XOR tree.1. function。一個回傳值、沒有時序、組合式:parity = ^word; 內聯成一棵十五顆閘的 XOR 樹。1. 関数。戻り値は一つ、タイミング制御なし、組合せ回路である。parity = ^word; は 15 ゲートの XOR ツリーに展開される。
2. Task (two outputs), or two functions. Both results are free: quotient is x >> 3, remainder is x[2:0]. No timing controls, so combinational. A function cannot do it alone because it returns only one value — though you can return a concatenation and part-select it at the call site, which is often neater.2. task(兩個輸出),或兩個 function。兩個結果都是免費的:商是 x >> 3,餘數是 x[2:0]。沒有時序控制,所以是組合的。function 單獨做不到,因為它只回傳一個值 — 不過你可以回傳一個串接,再在呼叫點做部分選取,那往往更俐落。2. タスク(出力 2 つ)、または関数 2 つ。どちらの結果も無料である。商は x >> 3、剰余は x[2:0]。タイミング制御はないので組合せ回路になる。関数単独では戻り値が一つなのでできない — もっとも連結を返して呼び出し側で部分選択する手もあり、そのほうが整っていることも多い。
3. Neither. A task with timing controls is a Type II construct: behavioural synthesis only, and not recommended. Write a three-stage shift register or a counter in an ordinary clocked block.3. 都不是。帶時序控制的 task 是第 II 型構件:只有行為合成才行,而且不建議。請在一般的時脈區塊裡寫一個三級移位暫存器或一個計數器。3. どちらでもない。タイミング制御付きのタスクはタイプ II の構文であり、振る舞い合成でしか扱えず推奨もされない。通常のクロックブロックで 3 段のシフトレジスタかカウンタを書くこと。
4. Function returning a concatenation, or a task with two outputs. {sat_flag, result} as a function return keeps it to one value: compute the wide sum, test for overflow, clamp. All combinational, no timing, so a function is legal and inlines cleanly.4. 回傳串接的 function,或一個有兩個輸出的 task。把 {sat_flag, result} 當成 function 的回傳值就只有一個值:算出加寬後的和、測試溢位、再箝位。全都是組合的、沒有時序,所以 function 合法而且內聯得很乾淨。4. 連結を返す関数、または出力 2 つのタスク。{sat_flag, result} を関数の戻り値にすれば値は一つで済む。広げた和を計算し、オーバーフローを判定し、クランプする。すべて組合せ回路でタイミング制御もないので、関数は合法でありきれいに展開される。
5. Module instance. Neither a task nor a function can share hardware — each call is a copy. You need one instantiated multiplier, input multiplexers, an output register and a four-state controller. This is the one case in the list where the answer is explicitly structural.5. 模組實例。task 與 function 都無法共用硬體 — 每次呼叫都是一份複本。你需要一個實例化的乘法器、輸入多工器、一個輸出暫存器與一台四狀態控制器。這是清單中唯一答案明確屬於結構層面的一題。5. モジュールインスタンス。タスクも関数もハードウェアを共有できない — 呼び出しごとにコピーである。インスタンス化した乗算器 1 個、入力マルチプレクサ、出力レジスタ、4 状態のコントローラが必要である。リスト中で答えが明示的に構造的であるのはこの一題だけである。
6. Neither, and the question hides a trap. A swap needs to read both registers and write both, which as a task would need inout arguments — explicitly forbidden by slide 36. In a clocked block the swap is trivial and needs no subprogram at all: a <= b; b <= a; works precisely because non-blocking assignments read the old values. Writing it with blocking assignments would copy b into both.6. 都不是,而且這題藏了一個陷阱。交換需要同時讀兩個暫存器又寫兩個,用 task 寫就需要 inout 引數 — 那被第 36 張投影片明文禁止。在時脈區塊裡這個交換很平凡,根本不需要副程式:a <= b; b <= a; 能成立,正是因為非阻塞式指定讀到的是舊值。若用阻塞式指定寫,會把 b 複製到兩者裡。6. どちらでもない。そしてこの問いは罠を隠している。入れ替えは両方のレジスタを読み両方に書く必要があり、タスクで書けば inout 引数が要る — スライド 36 が明文で禁じているものである。クロックブロックでは入れ替えは自明で、サブプログラムはまったく要らない。a <= b; b <= a; が成り立つのは、ノンブロッキング代入が古い値を読むからにほかならない。ブロッキング代入で書けば b が両方にコピーされてしまう。
A specify ... endspecify block declares module path delays and timing checks: (a => y) = 1.2; says that a change on a reaches y after 1.2 time units, and $setup, $hold, $recovery and friends declare the timing constraints that the module's inputs must satisfy. This is the mechanism by which a vendor's gate-level library model carries its timing, and it is how SDF back-annotation attaches real post-layout numbers to a netlist.specify ... endspecify 區塊宣告模組的路徑延遲與時序檢查:(a => y) = 1.2; 表示 a 的變化在 1.2 個時間單位後到達 y,而 $setup、$hold、$recovery 等則宣告模組輸入必須滿足的時序限制。這是廠商的閘級庫模型攜帶其時序資訊的機制,也是 SDF 反標註把真實佈局後數值附加到 netlist 上的方式。specify ... endspecify ブロックはモジュールのパス遅延とタイミングチェックを宣言する。(a => y) = 1.2; は a の変化が 1.2 時間単位後に y へ届くことを表し、$setup、$hold、$recovery などはモジュール入力が満たすべきタイミング制約を宣言する。これはベンダのゲートレベルライブラリモデルがタイミング情報を運ぶ仕組みであり、SDF 逆アノテーションが実際のレイアウト後の数値をネットリストに付与する方法でもある。
Slide 41 says it is ignored by synthesis, and the reason is simply that the information flows the other way. Delay in a synthesised design is a result, computed by the static timing analyser from the cell library's characterised data and the extracted parasitics. A delay written in the RTL would be at best a guess and at worst a contradiction of what the implementation actually achieves. So the synthesiser parses the block for syntax and discards it.第 41 張投影片說它被合成忽略,理由很簡單:資訊的流向是相反的。合成後設計的延遲是一個結果,由靜態時序分析器根據元件庫的特徵化資料與萃取出的寄生參數算出來。寫在 RTL 裡的延遲,最好的情況是猜測,最糟的情況是與實作實際達成的結果互相矛盾。所以合成器只檢查這個區塊的語法,然後把它丟掉。スライド 41 はこれが合成で無視されると述べる。理由は単純で、情報の流れが逆だからである。合成後の設計における遅延は結果であり、静的タイミング解析器がセルライブラリの特性データと抽出された寄生成分から計算する。RTL に書かれた遅延はよくて推測、悪ければ実装が実際に達成する値との矛盾である。したがって合成器はこのブロックを構文として解析したうえで捨てる。
The practical consequence is that you cannot use a specify block to tell the synthesiser what timing you want. The mechanism for that is the constraint file: create_clock, set_input_delay, set_output_delay, set_max_delay, set_false_path, set_multicycle_path. Those are SDC commands, not Verilog, and they are what the tool actually optimises against — including, as section 4.2 described, the choice of adder architecture.實務上的後果是:你不能用 specify 區塊去告訴合成器你想要什麼樣的時序。做這件事的機制是限制檔:create_clock、set_input_delay、set_output_delay、set_max_delay、set_false_path、set_multicycle_path。那些是 SDC 指令而不是 Verilog,而它們才是工具真正拿來做最佳化的依據 — 包括 4.2 節講過的加法器架構選擇。実務上の帰結として、specify ブロックで合成器に望むタイミングを伝えることはできない。そのための仕組みは制約ファイルである。create_clock、set_input_delay、set_output_delay、set_max_delay、set_false_path、set_multicycle_path。これらは Verilog ではなく SDC コマンドであり、ツールが実際に最適化の対象とするのはこちらである。4.2 節で述べた加算器アーキテクチャの選択もそこに含まれる。
Compiler directives — the back-tick ones — are processed by the Verilog preprocessor before any analysis happens, so they are not really part of the language at all; they transform the source text. Slide 41's statement that they are "synthesized" means that they take effect normally, because by the time the synthesiser sees the design they have already done their work.編譯指令 — 就是那些反引號開頭的 — 是在任何分析開始之前由 Verilog 前處理器處理的,所以它們其實根本不算語言的一部分;它們只是轉換原始碼文字。第 41 張投影片說它們「被合成」,意思是它們正常生效,因為等合成器看到這個設計時,它們早就把工作做完了。コンパイラ指令 — バッククォートで始まるもの — は、いかなる解析よりも前に Verilog のプリプロセッサが処理する。したがって実際には言語の一部ですらなく、ソーステキストを変換するだけである。スライド 41 がこれらを「合成される」と言うのは、通常どおり効果を持つという意味である。合成器が設計を見る時点で、それらはすでに仕事を終えているからである。
| Directive指令指令 | Effect in synthesis在合成中的作用合成における効果 |
|---|---|
`define / `undef | Textual macro substitution. Fully honoured. A parameter is usually better because it is scoped to the module and can be overridden per instance, whereas a `define is global from its point of definition to the end of the compilation unit.文字巨集替換。完全被遵守。通常用參數比較好,因為參數的作用域限於模組、而且可以逐實例覆寫,而 `define 從定義點到編譯單元結束都是全域的。テキストマクロ置換。完全に守られる。通常はパラメータのほうがよい。パラメータはモジュールにスコープが限られインスタンスごとに上書きできるが、`define は定義地点からコンパイル単位の終わりまでグローバルだからである。 |
`include | Textual inclusion. Fully honoured. Used for shared constant and macro files.文字包含。完全被遵守。用於共用的常數與巨集檔。テキストの取り込み。完全に守られる。共有の定数ファイルやマクロファイルに使う。 |
`ifdef / `ifndef / `else / `endif | Conditional compilation. Honoured, and the standard way to exclude simulation-only code from the synthesisable view — wrapping assertions, $display and testbench hooks in `ifndef SYNTHESIS.條件編譯。被遵守,而且是把只供模擬使用的程式碼排除在可合成視圖之外的標準做法 — 把斷言、$display 與測試平台掛鉤包在 `ifndef SYNTHESIS 裡。条件コンパイル。守られる。シミュレーション専用のコードを合成対象から外す標準的な方法でもある — アサーション、$display、テストベンチのフックを `ifndef SYNTHESIS で包む。 |
`timescale | Parsed and ignored. It sets simulation time units, which have no meaning after mapping.會被解析但忽略。它設定的是模擬時間單位,在對應之後沒有意義。構文解析はされるが無視される。設定するのはシミュレーションの時間単位であり、マッピング後には意味を持たない。 |
`default_nettype none | Honoured, and strongly recommended. It disables the implicit declaration of undeclared identifiers as one-bit wires, turning a typo that would otherwise become a silently truncated signal into a compile error.被遵守,而且強烈建議使用。它停用「把未宣告識別字隱含宣告成一位元 wire」的行為,把一個原本會變成靜默截斷訊號的拼字錯誤變成編譯錯誤。守られる。強く推奨される。未宣言の識別子を 1 ビットの wire として暗黙宣言する動作を無効化し、そのままなら黙って切り詰められた信号になるタイプミスをコンパイルエラーに変える。 |
`celldefine, `resetall, `unconnected_drive | Parsed; mostly relevant to library models rather than to RTL.會被解析;主要與庫模型有關,而不是與 RTL 有關。構文解析される。主に RTL ではなくライブラリモデルに関係する。 |
Of these, `default_nettype none deserves its recommendation. Verilog's implicit net declaration means that asign data_valied = ... — a typo in the target name — creates a brand-new one-bit wire called data_valied and leaves data_valid undriven, which simulates as x and synthesises as a constant. With `default_nettype none at the top of the file, the same typo is an error at compile time.這些之中,`default_nettype none 當得起它那句建議。Verilog 的隱含 net 宣告意味著 asign data_valied = ... — 目標名稱打錯字 — 會建出一條全新的、叫做 data_valied 的一位元 wire,而讓 data_valid 沒有驅動源,模擬起來是 x、合成起來是常數。在檔案開頭加上 `default_nettype none,同樣的錯字在編譯期就是錯誤。このうち `default_nettype none はその推奨に値する。Verilog の暗黙 net 宣言のせいで、asign data_valied = ... — 代入先の名前のタイプミス — は data_valied という新しい 1 ビット wire を作り、data_valid を未駆動のまま残す。シミュレーションでは x、合成では定数になる。ファイル先頭に `default_nettype none を置けば、同じタイプミスはコンパイル時のエラーになる。
Not on the slide, but the third category you will meet, and easy to confuse with compiler directives: synthesis pragmas. These are comments of the form // synopsys full_case parallel_case, /* synthesis keep */ or the newer (* full_case *) attribute syntax. They are invisible to the simulator — they are comments — and meaningful only to the synthesiser.投影片上沒有,但這是你會遇到的第三個類別,而且很容易和編譯指令搞混:合成 pragma。它們是形如 // synopsys full_case parallel_case、/* synthesis keep */ 的註解,或較新的 (* full_case *) 屬性語法。它們對模擬器是隱形的 — 它們是註解 — 只對合成器有意義。スライドにはないが、遭遇する第三のカテゴリであり、コンパイラ指令と混同しやすい。合成プラグマである。// synopsys full_case parallel_case、/* synthesis keep */ のようなコメント、あるいは新しい (* full_case *) という属性構文である。シミュレータからは見えず — コメントだからである — 合成器にだけ意味を持つ。
That asymmetry is the whole danger. A pragma that changes what the tool builds, while leaving the simulation unchanged, is a mechanism for creating RTL-to-gate mismatches on purpose. The two notorious ones:那個不對稱就是全部的危險所在。一個會改變工具所建硬體、卻讓模擬維持不變的 pragma,就是一套刻意製造 RTL 與閘級不匹配的機制。兩個惡名昭彰的例子:この非対称性こそが危険のすべてである。ツールが作るものを変えながらシミュレーションを変えないプラグマは、RTL とゲートの不一致を意図的に作り出す仕掛けである。悪名高い二つを挙げる。
full_case promises that every unlisted case item is a don't care. If the promise is false, the gates will do something the RTL simulation never showed — and you have deliberately disabled the tool's ability to tell you. Writing an explicit default achieves the same optimisation honestly, and the simulation then agrees.full_case 承諾每一個未列出的 case item 都是 don't care。如果這個承諾是假的,閘會做出 RTL 模擬從未顯示過的事 — 而你還刻意關掉了工具告訴你的能力。寫一個明確的 default 可以誠實地達成同樣的最佳化,而且模擬也會一致。full_case は列挙されていないケースアイテムがすべてドントケアだと約束する。その約束が偽なら、ゲートは RTL シミュレーションが一度も見せなかったふるまいをする — しかもあなたは、それを知らせるツールの能力をわざわざ無効にしている。明示的な default を書けば同じ最適化が正直に達成され、シミュレーションも一致する。parallel_case promises that no two case items can match at once, so the tool may build a balanced mux rather than a priority chain. If the promise is false — which is exactly when it is tempting, in a casez with overlapping items — RTL simulation gives the first match while the gates give an arbitrary one or a wired-OR of several.parallel_case 承諾沒有兩個 case item 會同時相符,所以工具可以建平衡多工器而不是優先權鏈。如果這個承諾是假的 — 而那恰好正是它最誘人的時候,也就是在 item 重疊的 casez 裡 — RTL 模擬會給第一個相符者,而閘會給出任意一個,或給出好幾個的 wired-OR。parallel_case は 2 つのケースアイテムが同時に一致することはないと約束し、ツールが優先順位の連鎖ではなく平衡したマルチプレクサを作れるようにする。その約束が偽のとき — それはまさにこれを使いたくなるとき、つまりアイテムが重なる casez のとき — RTL シミュレーションは最初の一致を返すが、ゲートは任意の一つ、あるいは複数のワイヤード OR を返す。Do not use full_case or parallel_case. Everything they can achieve can be stated in the RTL itself — a default branch for the first, non-overlapping case items for the second — in a way the simulator also sees. Reserve pragmas for things that genuinely have no RTL expression, such as dont_touch on a net you need to probe (section 2.1), and remove even those before the production run.不要用 full_case 或 parallel_case。它們能達成的一切,都能在 RTL 本身裡講清楚 — 前者用一個 default 分支,後者用不重疊的 case item — 而且是以模擬器也看得見的方式。把 pragma 留給真正沒有 RTL 表達方式的東西,例如對一條你需要探測的 net 下 dont_touch(2.1 節),而且連那些也要在正式執行前拿掉。full_case や parallel_case を使ってはならない。これらが達成できることはすべて RTL 自身で — 前者は default 分岐で、後者は重ならないケースアイテムで — しかもシミュレータにも見える形で表現できる。プラグマは RTL に本当に表現手段がないもの、たとえばプローブしたい net への dont_touch(2.1 節)に限って使い、それさえ製品向けの実行前には外すこと。
wand/wor become AND/OR gates; write the gate yourself.wand/wor 變成 AND/OR 閘;請自己把閘寫出來。wand/wor は AND/OR ゲートになる。ゲートは自分で書くこと。reg means "procedurally assigned", not "flip-flop".reg 的意思是「以程序方式被指定」,不是「正反器」。reg は「手続き的に代入される」の意味であり、「フリップフロップ」ではない。integer is 32 bits and signed; it shrinks only if the unused bits are provably unobservable.integer 是 32 位元且有號;只有在未使用的位元可被證明無法觀測時才會縮小。integer は 32 ビットで符号付き。未使用ビットが観測不能だと証明できるときにだけ縮む。{ }.替常數指定位寬;未指定位寬的十進位常數是 32 位元有號,而且在 { } 內是自決定的。定数にはサイズを付けること。サイズ指定のない 10 進定数は 32 ビット符号付きで、{ } の中では自己決定である。real, time, realtime are testbench-only.real、time、realtime 只能用在測試平台。real、time、realtime はテストベンチ専用である。casex/casez items and as a conditional default, where it means don't care.只有在 casex/casez 的 item 裡與條件式的預設分支中才合法,在那裡它代表 don't care。合法なのは casex/casez のアイテム内と条件文のデフォルトだけで、そこではドントケアを意味する。a == 1'bx is always false; a === 1'bx is not synthesisable.a == 1'bx 永遠為假;a === 1'bx 不可合成。a == 1'bx は常に偽。a === 1'bx は合成不可である。casez, wildcards in the items only; never casex.用 casez,萬用字元只放在 item 裡;絕不要用 casex。casez を使い、ワイルドカードはアイテムのみに置く。casex は決して使わない。>>> sign-extends only if the operand is signed.>>> 只有在運算元為有號時才做符號擴展。>>> が符号拡張するのはオペランドが符号付きのときだけである。==: XOR per bit + NOR tree, $3n$ GE.==:每位元一顆 XOR 加一棵 NOR 樹,$3n$ GE。==:ビットごとの XOR と NOR ツリーで $3n$ GE。<: comparator $2n$–$4n$ GE, or reuse an adder.<:比較器 $2n$–$4n$ GE,或重用既有加法器。<:比較器で $2n$〜$4n$ GE、または既存の加算器を再利用。?:: $n$ muxes; chains stay chains unless exclusivity is provable.?::$n$ 顆多工器;除非互斥性可被證明,否則鏈就維持是鏈。?::マルチプレクサ $n$ 個。排他性が証明できない限り連鎖は連鎖のまま。a+b+c+d is a 3-deep chain; parentheses make it 2-deep for the same area.a+b+c+d 是三層的鏈;加括號可以在面積相同下變成兩層。a+b+c+d は深さ 3 の連鎖。括弧を付ければ同じ面積で深さ 2 になる。= then read in the same block: the new value, so a wire.= 之後在同一區塊讀取:讀到新值,所以是 wire。= の後に同じブロックで読む:新しい値を見るので配線。<=, or a read from another block: the previous cycle, so a flip-flop.<=,或從另一個區塊讀取:讀到前一週期的值,所以是正反器。<=、または別のブロックからの読み出し:前サイクルの値を見るのでフリップフロップ。# in synthesisable code, ever.可合成程式碼裡永遠不要有 #。合成可能なコードに # を書かないこと、決して。a[i] into a wire instead of a mux.展開把 a[i] 變成一條線而不是一顆多工器。展開は a[i] をマルチプレクサではなく配線に変える。specify ignored; directives honoured; pragmas are dangerous.specify 被忽略;指令被遵守;pragma 很危險。specify は無視、指令は守られる、プラグマは危険。| Trap陷阱罠 | Symptom症狀症状 | Fix修法修正 |
|---|---|---|
casex with x reaching the selectorcasex 而 x 到達了選擇子セレクタに x が届く casex | RTL simulation is clean, gates differRTL 模擬很乾淨,閘級卻不同RTL シミュレーションはきれいだがゲートは異なる | Use casez, wildcards in items only改用 casez,萬用字元只放在 item 裡casez を使い、ワイルドカードはアイテムのみ |
if (a == 1'bx) | Branch silently never taken分支靜默地永不成立分岐が黙って一度も実行されない | Add a real valid bit加一個真實的 valid 位元実在の valid ビットを追加する |
(a + b) >> 1 for an average用 (a + b) >> 1 求平均平均に (a + b) >> 1 を使う | Wraps on large inputs輸入很大時會回繞大きな入力でラップする | Widen by one bit before adding相加前先加寬一位元加算の前に 1 ビット広げる |
a / 4 on a signed value對有號值做 a / 4符号付きの値への a / 4 | Off by one on negatives負數時差一負の数で 1 ずれる | Bias before shifting, or keep it unsigned移位前先加偏移,或維持無號シフト前にバイアスを加えるか、符号なしのままにする |
a >> s where s could have been a parametera >> s,而 s 本來可以是參數s をパラメータにできたのに a >> s と書く | $n\log_2 n$ muxes appear from nowhere憑空冒出 $n\log_2 n$ 顆多工器どこからともなく $n\log_2 n$ 個のマルチプレクサが現れる | Make the shift amount a parameter把位移量做成參數シフト量をパラメータにする |
Unsized literal inside { }{ } 內未指定位寬的常數{ } の中のサイズ指定のないリテラル | Bus 32 bits too wide匯流排寬了 32 位元バスが 32 ビット広すぎる | Always size literals常數一律指定位寬リテラルには常にサイズを付ける |
| Mixing signed and unsigned operands混用有號與無號運算元符号付きと符号なしのオペランドの混在 | Negative numbers become huge positives負數變成巨大的正數負の数が巨大な正の数になる | Keep signedness uniform; use $signed()讓有號性一致;使用 $signed()符号属性を揃える。$signed() を使う |
| Blocking assignment read by another block阻塞式指定被另一個區塊讀取ブロッキング代入が別のブロックに読まれる | Simulator-dependent behaviour行為依模擬器而異シミュレータ依存のふるまい | Non-blocking in every clocked block每個時脈區塊都用非阻塞式すべてのクロックブロックでノンブロッキングを使う |
| Synchronous branch not last同步分支不在最後同期分岐が最後にない | Synthesis error, or an inferred gated clock合成錯誤,或推論出時脈閘控合成エラー、またはゲーテッドクロックの推論 | Asynchronous conditions first, always非同步條件永遠放最前面非同期条件を常に先頭に置く |
| Loop bound depends on a signal迴圈邊界取決於某個訊號ループの上限が信号に依存する | Simulates fine, synthesis fails模擬得很好,合成失敗シミュレーションは通るが合成が失敗する | Bound the loop and mask the body限定迴圈次數並為主體加遮罩ループを上限で縛り、本体をマスクする |
A closing thought that ties the lecture together. Almost every rule here is a consequence, not an axiom. Ports are retained because an interface is a contract. x cannot be tested because x is not a state. A non-static loop cannot be synthesised because circuits have a fixed size. A barrel shifter costs $n\log_2 n$ muxes because that is how many it takes to select among $n$ permutations in $\log_2 n$ binary decisions. The synchronous branch must be last because that is the order in which a flip-flop cell resolves its inputs. If you can reconstruct the reason, you will never need to remember the rule — and when you meet a construct the lecture did not cover, you will be able to work out what it builds.最後一個把整講串起來的想法。這裡幾乎每一條規則都是結果,而不是公理。埠被保留,是因為介面是契約。x 無法被測試,是因為 x 不是一個狀態。非靜態迴圈無法合成,是因為電路的大小是固定的。桶式移位器要花 $n\log_2 n$ 顆多工器,是因為用 $\log_2 n$ 次二元決策在 $n$ 種置換中選一個就需要這麼多。同步分支必須放最後,是因為正反器元件就是照那個順序解析它的輸入。只要你能重建出理由,你就永遠不需要記住規則 — 而當你遇到這一講沒涵蓋的構件時,你也能自己推出它會建出什麼。講義全体を結ぶ最後の一言。ここにある規則はほとんどすべて公理ではなく帰結である。ポートが残されるのはインタフェースが契約だから。x を検査できないのは x が状態ではないから。非静的ループが合成できないのは回路の大きさが固定だから。バレルシフタに $n\log_2 n$ 個のマルチプレクサがかかるのは、$\log_2 n$ 回の二値判断で $n$ 通りの並べ替えから一つを選ぶのにそれだけ必要だから。同期分岐が最後でなければならないのは、フリップフロップのセルが入力を解決する順序がそうだからである。理由を再構成できるなら、規則を覚える必要は決してない — そして講義が扱わなかった構文に出会ったときも、それが何を作るのか自分で導き出せる。